From e7f8b66f9bd364db8107d9fde215e957ca2863f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Petryka?= <35800402+MichalPetryka@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:51:21 +0100 Subject: [PATCH] Simplify and optimize Math(F).Round (#98186) * Simplify and optimize Math(F).Round * Change AdvSimd check --- .../System.Private.CoreLib/src/System/Math.cs | 96 ++++++------------- .../src/System/MathF.cs | 96 ++++++------------- .../src/System/ThrowHelper.cs | 18 ++++ 3 files changed, 78 insertions(+), 132 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..1d1c50a4e2b55 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -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; diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index 6be7b0544bcd3..cc0795255d0c8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -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; 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) {