Skip to content

[API Proposal]: Params-based Math.Min and Math.Max #115478

Closed as not planned
Closed as not planned
@khellang

Description

@khellang

Background and motivation

Now that we have params ReadOnlySpan<T>, is it worth revisiting #47456 for variadic versions of Math.Min and Math.Max?

If you want to get the max value from multiple variables, you usually end up with some unwieldy code, like

var maxValue = Math.Max(Math.Max(Math.Max(val1, val2), val3), val4);

Otherwise you'd have to allocate an array and use System.Linq.Enumerable.Max, i.e.

var maxValue = new[] { val1, val2, val3, val4 }.Max();

The implementation would basically be the Span-path from the Enumerable.Max implementation:

if (span.IsEmpty)
{
ThrowHelper.ThrowNoElementsException();
}
if (!Vector128.IsHardwareAccelerated || !Vector128<T>.IsSupported || span.Length < Vector128<T>.Count)
{
value = span[0];
for (int i = 1; i < span.Length; i++)
{
if (TMinMax.Compare(span[i], value))
{
value = span[i];
}
}
}
else if (!Vector256.IsHardwareAccelerated || !Vector256<T>.IsSupported || span.Length < Vector256<T>.Count)
{
ref T current = ref MemoryMarshal.GetReference(span);
ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector128<T>.Count);
Vector128<T> best = Vector128.LoadUnsafe(ref current);
current = ref Unsafe.Add(ref current, Vector128<T>.Count);
while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart))
{
best = TMinMax.Compare(best, Vector128.LoadUnsafe(ref current));
current = ref Unsafe.Add(ref current, Vector128<T>.Count);
}
best = TMinMax.Compare(best, Vector128.LoadUnsafe(ref lastVectorStart));
value = best[0];
for (int i = 1; i < Vector128<T>.Count; i++)
{
if (TMinMax.Compare(best[i], value))
{
value = best[i];
}
}
}
else if (!Vector512.IsHardwareAccelerated || !Vector512<T>.IsSupported || span.Length < Vector512<T>.Count)
{
ref T current = ref MemoryMarshal.GetReference(span);
ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector256<T>.Count);
Vector256<T> best = Vector256.LoadUnsafe(ref current);
current = ref Unsafe.Add(ref current, Vector256<T>.Count);
while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart))
{
best = TMinMax.Compare(best, Vector256.LoadUnsafe(ref current));
current = ref Unsafe.Add(ref current, Vector256<T>.Count);
}
best = TMinMax.Compare(best, Vector256.LoadUnsafe(ref lastVectorStart));
value = best[0];
for (int i = 1; i < Vector256<T>.Count; i++)
{
if (TMinMax.Compare(best[i], value))
{
value = best[i];
}
}
}
else
{
ref T current = ref MemoryMarshal.GetReference(span);
ref T lastVectorStart = ref Unsafe.Add(ref current, span.Length - Vector512<T>.Count);
Vector512<T> best = Vector512.LoadUnsafe(ref current);
current = ref Unsafe.Add(ref current, Vector512<T>.Count);
while (Unsafe.IsAddressLessThan(ref current, ref lastVectorStart))
{
best = TMinMax.Compare(best, Vector512.LoadUnsafe(ref current));
current = ref Unsafe.Add(ref current, Vector512<T>.Count);
}
best = TMinMax.Compare(best, Vector512.LoadUnsafe(ref lastVectorStart));
value = best[0];
for (int i = 1; i < Vector512<T>.Count; i++)
{
if (TMinMax.Compare(best[i], value))
{
value = best[i];
}
}
}

These methods could even call the new Math overloads.

API Proposal

namespace System;

public static class Math
{
    public static byte Max(params ReadOnlySpan<byte> values);
    public static byte Max(params ReadOnlySpan<decimal> values);
    public static byte Max(params ReadOnlySpan<double> values);
    public static byte Max(params ReadOnlySpan<short> values);
    public static byte Max(params ReadOnlySpan<int> values);
    public static byte Max(params ReadOnlySpan<nint> values);
    public static byte Max(params ReadOnlySpan<sbyte> values);
    public static byte Max(params ReadOnlySpan<float> values);
    public static byte Max(params ReadOnlySpan<ushort> values);
    public static byte Max(params ReadOnlySpan<uint> values);
    public static byte Max(params ReadOnlySpan<ulong> values);
    public static byte Max(params ReadOnlySpan<nuint> values);

    public static byte Min(params ReadOnlySpan<byte> values);
    public static byte Min(params ReadOnlySpan<decimal> values);
    public static byte Min(params ReadOnlySpan<double> values);
    public static byte Min(params ReadOnlySpan<short> values);
    public static byte Min(params ReadOnlySpan<int> values);
    public static byte Min(params ReadOnlySpan<nint> values);
    public static byte Min(params ReadOnlySpan<sbyte> values);
    public static byte Min(params ReadOnlySpan<float> values);
    public static byte Min(params ReadOnlySpan<ushort> values);
    public static byte Min(params ReadOnlySpan<uint> values);
    public static byte Min(params ReadOnlySpan<ulong> values);
    public static byte Min(params ReadOnlySpan<nuint> values);
}

API Usage

var maxValue = Math.Max(val1, val2, val3, val4);

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions