Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify and optimize Math(F).Round #98186

Merged
merged 2 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 30 additions & 66 deletions src/libraries/System.Private.CoreLib/src/System/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,86 +1298,50 @@ public static double Round(double value, int digits)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Round(double value, MidpointRounding mode)
{
// Inline single-instruction modes
if (RuntimeHelpers.IsKnownConstant((int)mode))
switch (mode)
{
if (mode == MidpointRounding.ToEven)
return Round(value);

// For ARM/ARM64 we can lower it down to a single instruction FRINTA
// For other platforms we use a fast managed implementation
if (mode == MidpointRounding.AwayFromZero)
{
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value above (for positive numbers) or below (for negative numbers)
case MidpointRounding.AwayFromZero:
// For ARM/ARM64 we can lower it down to a single instruction FRINTA
if (AdvSimd.IsSupported)
return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalar(value)).ToScalar();
return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(value)).ToScalar();
// For other platforms we use a fast managed implementation
// manually fold BitDecrement(0.5)
return Truncate(value + CopySign(0.49999999999999994, value));
}
}

return Round(value, 0, mode);
}
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value with an even least significant digit
case MidpointRounding.ToEven:
return Round(value);
// Directed rounding: Round to the nearest value, toward to zero
case MidpointRounding.ToZero:
return Truncate(value);
// Directed Rounding: Round down to the next value, toward negative infinity
case MidpointRounding.ToNegativeInfinity:
return Floor(value);
// Directed rounding: Round up to the next value, toward positive infinity
case MidpointRounding.ToPositiveInfinity:
return Ceiling(value);

public static unsafe double Round(double value, int digits, MidpointRounding mode)
{
if ((digits < 0) || (digits > maxRoundingDigits))
{
throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits);
default:
ThrowHelper.ThrowArgumentException_InvalidEnumValue(mode);
return default;
}
}

if (mode < MidpointRounding.ToEven || mode > MidpointRounding.ToPositiveInfinity)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Round(double value, int digits, MidpointRounding mode)
{
if ((uint)digits > maxRoundingDigits)
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
ThrowHelper.ThrowArgumentOutOfRange_RoundingDigits(nameof(digits));
}

if (Abs(value) < doubleRoundLimit)
{
double power10 = RoundPower10Double[digits];

value *= power10;

switch (mode)
{
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value with an even least significant digit
case MidpointRounding.ToEven:
{
value = Round(value);
break;
}
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value above (for positive numbers) or below (for negative numbers)
case MidpointRounding.AwayFromZero:
{
// manually fold BitDecrement(0.5)
value = Truncate(value + CopySign(0.49999999999999994, value));
break;
}
// Directed rounding: Round to the nearest value, toward to zero
case MidpointRounding.ToZero:
{
value = Truncate(value);
break;
}
// Directed Rounding: Round down to the next value, toward negative infinity
case MidpointRounding.ToNegativeInfinity:
{
value = Floor(value);
break;
}
// Directed rounding: Round up to the next value, toward positive infinity
case MidpointRounding.ToPositiveInfinity:
{
value = Ceiling(value);
break;
}
default:
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
}
}

value /= power10;
value = Round(value * power10, mode) / power10;
}

return value;
Expand Down
96 changes: 30 additions & 66 deletions src/libraries/System.Private.CoreLib/src/System/MathF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,86 +408,50 @@ public static float Round(float x, int digits)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Round(float x, MidpointRounding mode)
{
// Inline single-instruction modes
if (RuntimeHelpers.IsKnownConstant((int)mode))
switch (mode)
{
if (mode == MidpointRounding.ToEven)
return Round(x);

// For ARM/ARM64 we can lower it down to a single instruction FRINTA
// For other platforms we use a fast managed implementation
if (mode == MidpointRounding.AwayFromZero)
{
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value above (for positive numbers) or below (for negative numbers)
case MidpointRounding.AwayFromZero:
// For ARM/ARM64 we can lower it down to a single instruction FRINTA
if (AdvSimd.IsSupported)
return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(x)).ToScalar();
// manually fold BitDecrement(0.5f)
// For other platforms we use a fast managed implementation
// manually fold BitDecrement(0.5)
return Truncate(x + CopySign(0.49999997f, x));
}
}

return Round(x, 0, mode);
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value with an even least significant digit
case MidpointRounding.ToEven:
return Round(x);
// Directed rounding: Round to the nearest value, toward to zero
case MidpointRounding.ToZero:
return Truncate(x);
// Directed Rounding: Round down to the next value, toward negative infinity
case MidpointRounding.ToNegativeInfinity:
return Floor(x);
// Directed rounding: Round up to the next value, toward positive infinity
case MidpointRounding.ToPositiveInfinity:
return Ceiling(x);

default:
ThrowHelper.ThrowArgumentException_InvalidEnumValue(mode);
return default;
}
}

public static unsafe float Round(float x, int digits, MidpointRounding mode)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Round(float x, int digits, MidpointRounding mode)
{
if ((digits < 0) || (digits > maxRoundingDigits))
{
throw new ArgumentOutOfRangeException(nameof(digits), SR.ArgumentOutOfRange_RoundingDigits_MathF);
}

if (mode < MidpointRounding.ToEven || mode > MidpointRounding.ToPositiveInfinity)
if ((uint)digits > maxRoundingDigits)
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
ThrowHelper.ThrowArgumentOutOfRange_RoundingDigits_MathF(nameof(digits));
}

if (Abs(x) < singleRoundLimit)
{
float power10 = RoundPower10Single[digits];

x *= power10;

switch (mode)
{
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value with an even least significant digit
case MidpointRounding.ToEven:
{
x = Round(x);
break;
}
// Rounds to the nearest value; if the number falls midway,
// it is rounded to the nearest value above (for positive numbers) or below (for negative numbers)
case MidpointRounding.AwayFromZero:
{
// manually fold BitDecrement(0.5f)
x = Truncate(x + CopySign(0.49999997f, x));
break;
}
// Directed rounding: Round to the nearest value, toward to zero
case MidpointRounding.ToZero:
{
x = Truncate(x);
break;
}
// Directed Rounding: Round down to the next value, toward negative infinity
case MidpointRounding.ToNegativeInfinity:
{
x = Floor(x);
break;
}
// Directed rounding: Round up to the next value, toward positive infinity
case MidpointRounding.ToPositiveInfinity:
{
x = Ceiling(x);
break;
}
default:
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, mode, nameof(MidpointRounding)), nameof(mode));
}
}

x /= power10;
x = Round(x * power10, mode) / power10;
}

return x;
Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ internal static void ThrowArgumentException_InvalidTimeSpanStyles()
throw new ArgumentException(SR.Argument_InvalidTimeSpanStyles, "styles");
}

[DoesNotReturn]
internal static void ThrowArgumentException_InvalidEnumValue<TEnum>(TEnum value, [CallerArgumentExpression(nameof(value))] string argumentName = "")
{
throw new ArgumentException(SR.Format(SR.Argument_InvalidEnumValue, value, typeof(TEnum).Name), argumentName);
}

[DoesNotReturn]
internal static void ThrowArgumentException_OverlapAlignmentMismatch()
{
Expand Down Expand Up @@ -230,6 +236,18 @@ internal static void ThrowArgumentOutOfRange_TimeSpanTooLong()
throw new ArgumentOutOfRangeException(null, SR.Overflow_TimeSpanTooLong);
}

[DoesNotReturn]
internal static void ThrowArgumentOutOfRange_RoundingDigits(string name)
{
throw new ArgumentOutOfRangeException(name, SR.ArgumentOutOfRange_RoundingDigits);
}

[DoesNotReturn]
internal static void ThrowArgumentOutOfRange_RoundingDigits_MathF(string name)
{
throw new ArgumentOutOfRangeException(name, SR.ArgumentOutOfRange_RoundingDigits_MathF);
}

[DoesNotReturn]
internal static void ThrowArgumentOutOfRange_Range<T>(string parameterName, T value, T minInclusive, T maxInclusive)
{
Expand Down
Loading