Skip to content

Commit

Permalink
Simplify and optimize Math(F).Round (dotnet#98186)
Browse files Browse the repository at this point in the history
* Simplify and optimize Math(F).Round

* Change AdvSimd check
  • Loading branch information
MichalPetryka committed Feb 9, 2024
1 parent 1bc3570 commit e7f8b66
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 132 deletions.
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

0 comments on commit e7f8b66

Please sign in to comment.