From 159ec83ea856ccb5974c949efb08e907c65d1c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Thu, 8 Feb 2024 21:51:49 +0100 Subject: [PATCH 1/2] Simplify and optimize Math(F).Round --- .../System.Private.CoreLib/src/System/Math.cs | 102 ++++++----------- .../src/System/MathF.cs | 104 ++++++------------ .../src/System/ThrowHelper.cs | 18 +++ 3 files changed, 87 insertions(+), 137 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index d1ba7b0dca27c..bf743f68caccd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1298,86 +1298,52 @@ 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)) - { - 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) - { - if (AdvSimd.IsSupported) - return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalar(value)).ToScalar(); + switch (mode) + { + // 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) +#pragma warning disable IntrinsicsInSystemPrivateCoreLib + case MidpointRounding.AwayFromZero when AdvSimd.IsSupported: + // For ARM/ARM64 we can lower it down to a single instruction FRINTA + return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(value)).ToScalar(); +#pragma warning restore IntrinsicsInSystemPrivateCoreLib + case MidpointRounding.AwayFromZero: + // 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; diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 6be7b0544bcd3..61408a3f29e40 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -408,86 +408,52 @@ 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)) - { - 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) - { - if (AdvSimd.IsSupported) - return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(x)).ToScalar(); - // manually fold BitDecrement(0.5f) + switch (mode) + { + // 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) +#pragma warning disable IntrinsicsInSystemPrivateCoreLib + case MidpointRounding.AwayFromZero when AdvSimd.IsSupported: + // For ARM/ARM64 we can lower it down to a single instruction FRINTA + return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(x)).ToScalar(); +#pragma warning restore IntrinsicsInSystemPrivateCoreLib + case MidpointRounding.AwayFromZero: + // 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; diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 5108a8b22fb9a..b158acb9c7cfa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -107,6 +107,12 @@ internal static void ThrowArgumentException_InvalidTimeSpanStyles() throw new ArgumentException(SR.Argument_InvalidTimeSpanStyles, "styles"); } + [DoesNotReturn] + internal static void ThrowArgumentException_InvalidEnumValue(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() { @@ -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(string parameterName, T value, T minInclusive, T maxInclusive) { From c82d47d1cfc6b8fb1904e67a83fc831edf3812a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= Date: Thu, 8 Feb 2024 23:00:03 +0100 Subject: [PATCH 2/2] Change AdvSimd check --- src/libraries/System.Private.CoreLib/src/System/Math.cs | 8 +++----- src/libraries/System.Private.CoreLib/src/System/MathF.cs | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index bf743f68caccd..1d1c50a4e2b55 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1302,12 +1302,10 @@ public static double Round(double value, MidpointRounding mode) { // 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) -#pragma warning disable IntrinsicsInSystemPrivateCoreLib - case MidpointRounding.AwayFromZero when AdvSimd.IsSupported: - // For ARM/ARM64 we can lower it down to a single instruction FRINTA - return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(value)).ToScalar(); -#pragma warning restore IntrinsicsInSystemPrivateCoreLib case MidpointRounding.AwayFromZero: + // For ARM/ARM64 we can lower it down to a single instruction FRINTA + if (AdvSimd.IsSupported) + 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)); diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 61408a3f29e40..cc0795255d0c8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -412,12 +412,10 @@ public static float Round(float x, MidpointRounding mode) { // 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) -#pragma warning disable IntrinsicsInSystemPrivateCoreLib - case MidpointRounding.AwayFromZero when AdvSimd.IsSupported: - // For ARM/ARM64 we can lower it down to a single instruction FRINTA - return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(x)).ToScalar(); -#pragma warning restore IntrinsicsInSystemPrivateCoreLib 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(); // For other platforms we use a fast managed implementation // manually fold BitDecrement(0.5) return Truncate(x + CopySign(0.49999997f, x));