diff --git a/src/libraries/System.Private.CoreLib/src/System/Math.cs b/src/libraries/System.Private.CoreLib/src/System/Math.cs index 896f07cf85532..cdf6f3ca27ee9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Math.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Math.cs @@ -1299,6 +1299,18 @@ 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 XARCH we have to use the common path + if (AdvSimd.IsSupported && mode == MidpointRounding.AwayFromZero) + return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalar(value)).ToScalar(); + } + return Round(value, 0, mode); } diff --git a/src/libraries/System.Private.CoreLib/src/System/MathF.cs b/src/libraries/System.Private.CoreLib/src/System/MathF.cs index f4cde2dc7f71a..03d6a70bb8313 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MathF.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MathF.cs @@ -429,6 +429,18 @@ 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 XARCH we have to use the common path + if (AdvSimd.IsSupported && mode == MidpointRounding.AwayFromZero) + return AdvSimd.RoundAwayFromZeroScalar(Vector64.CreateScalarUnsafe(x)).ToScalar(); + } + return Round(x, 0, mode); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index c1a77c2241c7c..ecd0991d2a780 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -122,6 +122,9 @@ internal static bool IsPrimitiveType(this CorElementType et) [Intrinsic] internal static bool IsKnownConstant(char t) => false; + + [Intrinsic] + internal static bool IsKnownConstant(int t) => false; #pragma warning restore IDE0060 } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs index ad98ff3ea7146..da8784caa311a 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Math.cs @@ -3186,5 +3186,95 @@ public static void Round_Float_Constant_Arg() Assert.Equal( 4, MathF.Round( 3.5f, MidpointRounding.AwayFromZero)); Assert.Equal(-4, MathF.Round(-3.5f, MidpointRounding.AwayFromZero)); } + + public static IEnumerable Round_ToEven_TestData() + { + yield return new object[] { 1, 1 }; + yield return new object[] { 0.5, 0 }; + yield return new object[] { 1.5, 2 }; + yield return new object[] { 2.5, 2 }; + yield return new object[] { 3.5, 4 }; + + // Math.Round(var = 0.49999999999999994) returns 1 on ARM32 + if (!PlatformDetection.IsArmProcess) + yield return new object[] { 0.49999999999999994, 0 }; + + yield return new object[] { 1.5, 2 }; + yield return new object[] { 2.5, 2 }; + yield return new object[] { 3.5, 4 }; + yield return new object[] { 4.5, 4 }; + yield return new object[] { 3.141592653589793, 3 }; + yield return new object[] { 2.718281828459045, 3 }; + yield return new object[] { 1385.4557313670111, 1385 }; + yield return new object[] { 3423423.43432, 3423423 }; + yield return new object[] { 535345.5, 535346 }; + yield return new object[] { 535345.50001, 535346 }; + yield return new object[] { 535345.5, 535346 }; + yield return new object[] { 535345.4, 535345 }; + yield return new object[] { 535345.6, 535346 }; + yield return new object[] { -2.718281828459045, -3 }; + yield return new object[] { 10, 10 }; + yield return new object[] { -10, -10 }; + yield return new object[] { -0, -0 }; + yield return new object[] { 0, 0 }; + yield return new object[] { double.NaN, double.NaN }; + yield return new object[] { double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { double.NegativeInfinity, double.NegativeInfinity }; + yield return new object[] { 1.7976931348623157E+308, 1.7976931348623157E+308 }; + yield return new object[] { -1.7976931348623157E+308, -1.7976931348623157E+308 }; + } + + [Theory] + [MemberData(nameof(Round_ToEven_TestData))] + public static void Round_ToEven_0(double value, double expected) + { + // Math.Round has special fast paths when MidpointRounding is a const + // Don't replace it with a variable + Assert.Equal(expected, Math.Round(value, MidpointRounding.ToEven)); + Assert.Equal(expected, Math.Round(value, 0, MidpointRounding.ToEven)); + } + + public static IEnumerable Round_AwayFromZero_TestData() + { + yield return new object[] { 1, 1 }; + yield return new object[] { 0.5, 1 }; + yield return new object[] { 1.5, 2 }; + yield return new object[] { 2.5, 3 }; + yield return new object[] { 3.5, 4 }; + yield return new object[] { 0.49999999999999994, 0 }; + yield return new object[] { 1.5, 2 }; + yield return new object[] { 2.5, 3 }; + yield return new object[] { 3.5, 4 }; + yield return new object[] { 4.5, 5 }; + yield return new object[] { 3.141592653589793, 3 }; + yield return new object[] { 2.718281828459045, 3 }; + yield return new object[] { 1385.4557313670111, 1385 }; + yield return new object[] { 3423423.43432, 3423423 }; + yield return new object[] { 535345.5, 535346 }; + yield return new object[] { 535345.50001, 535346 }; + yield return new object[] { 535345.5, 535346 }; + yield return new object[] { 535345.4, 535345 }; + yield return new object[] { 535345.6, 535346 }; + yield return new object[] { -2.718281828459045, -3 }; + yield return new object[] { 10, 10 }; + yield return new object[] { -10, -10 }; + yield return new object[] { -0, -0 }; + yield return new object[] { 0, 0 }; + yield return new object[] { double.NaN, double.NaN }; + yield return new object[] { double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { double.NegativeInfinity, double.NegativeInfinity }; + yield return new object[] { 1.7976931348623157E+308, 1.7976931348623157E+308 }; + yield return new object[] { -1.7976931348623157E+308, -1.7976931348623157E+308 }; + } + + [Theory] + [MemberData(nameof(Round_AwayFromZero_TestData))] + public static void Round_AwayFromZero_0(double value, double expected) + { + // Math.Round has special fast paths when MidpointRounding is a const + // Don't replace it with a variable + Assert.Equal(expected, Math.Round(value, MidpointRounding.AwayFromZero)); + Assert.Equal(expected, Math.Round(value, 0, MidpointRounding.AwayFromZero)); + } } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/MathF.cs b/src/libraries/System.Runtime.Extensions/tests/System/MathF.cs index 6d834e6ef554d..13d11253c0dd2 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/MathF.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/MathF.cs @@ -1911,5 +1911,91 @@ public static void Truncate() Assert.Equal(3.0f, MathF.Truncate(3.14159f)); Assert.Equal(-3.0f, MathF.Truncate(-3.14159f)); } + + public static IEnumerable Round_ToEven_TestData() + { + yield return new object[] { 1f, 1f }; + yield return new object[] { 0.5f, 0f }; + yield return new object[] { 1.5f, 2f }; + yield return new object[] { 2.5f, 2f }; + yield return new object[] { 3.5f, 4f }; + yield return new object[] { 0.49999997f, 0f }; + yield return new object[] { 1.5f, 2f }; + yield return new object[] { 2.5f, 2f }; + yield return new object[] { 3.5f, 4f }; + yield return new object[] { 4.5f, 4f }; + yield return new object[] { 3.1415927f, 3f }; + yield return new object[] { 2.7182817f, 3f }; + yield return new object[] { 1385.4557f, 1385f }; + yield return new object[] { 3423.4343f, 3423f }; + yield return new object[] { 535345.5f, 535346f }; + yield return new object[] { 535345.5f, 535346f }; + yield return new object[] { 535345.5f, 535346f }; + yield return new object[] { 535345.4f, 535345f }; + yield return new object[] { 535345.6f, 535346f }; + yield return new object[] { -2.7182817f, -3f }; + yield return new object[] { 10f, 10f }; + yield return new object[] { -10f, -10f }; + yield return new object[] { -0f, -0f }; + yield return new object[] { 0f, 0f }; + yield return new object[] { float.NaN, float.NaN }; + yield return new object[] { float.PositiveInfinity, float.PositiveInfinity }; + yield return new object[] { float.NegativeInfinity, float.NegativeInfinity }; + yield return new object[] { 3.4028235E+38f, 3.4028235E+38f }; + yield return new object[] { -3.4028235E+38f, -3.4028235E+38f }; + } + + [Theory] + [MemberData(nameof(Round_ToEven_TestData))] + public static void Round_ToEven_0(float value, float expected) + { + // Math.Round has special fast paths when MidpointRounding is a const + // Don't replace it with a variable + Assert.Equal(expected, MathF.Round(value, MidpointRounding.ToEven)); + Assert.Equal(expected, MathF.Round(value, 0, MidpointRounding.ToEven)); + } + + public static IEnumerable Round_AwayFromZero_TestData() + { + yield return new object[] { 1f, 1f }; + yield return new object[] { 0.5f, 1f }; + yield return new object[] { 1.5f, 2f }; + yield return new object[] { 2.5f, 3f }; + yield return new object[] { 3.5f, 4f }; + yield return new object[] { 0.49999997f, 0f }; + yield return new object[] { 1.5f, 2f }; + yield return new object[] { 2.5f, 3f }; + yield return new object[] { 3.5f, 4f }; + yield return new object[] { 4.5f, 5f }; + yield return new object[] { 3.1415927f, 3f }; + yield return new object[] { 2.7182817f, 3f }; + yield return new object[] { 1385.4557f, 1385f }; + yield return new object[] { 3423.4343f, 3423f }; + yield return new object[] { 535345.5f, 535346f }; + yield return new object[] { 535345.5f, 535346f }; + yield return new object[] { 535345.5f, 535346f }; + yield return new object[] { 535345.4f, 535345f }; + yield return new object[] { 535345.6f, 535346f }; + yield return new object[] { -2.7182817f, -3f }; + yield return new object[] { 10f, 10f }; + yield return new object[] { -10f, -10f }; + yield return new object[] { -0f, -0f }; + yield return new object[] { 0f, 0f }; + yield return new object[] { float.NaN, float.NaN }; + yield return new object[] { float.PositiveInfinity, float.PositiveInfinity }; + yield return new object[] { float.NegativeInfinity, float.NegativeInfinity }; + yield return new object[] { 3.4028235E+38f, 3.4028235E+38f }; + yield return new object[] { -3.4028235E+38f, -3.4028235E+38f }; + } + + [Theory] + [MemberData(nameof(Round_AwayFromZero_TestData))] + public static void Round_AwayFromZero_0(float value, float expected) + { + // Math.Round has special fast paths when MidpointRounding is a const + // Don't replace it with a variable + Assert.Equal(expected, MathF.Round(value, MidpointRounding.AwayFromZero)); + Assert.Equal(expected, MathF.Round(value, 0, MidpointRounding.AwayFromZero)); + } } }