Skip to content

Commit 48adb84

Browse files
Fixing the handling of Positive NaN in Math.Min for float/double (#70795)
* Adding tests validating Positive NaN for Max, MaxMagnitude, Min, and MinMagnitude * Fixing the handling of Positive NaN in Math.Min for float/double * Fixing the Max/Min code comments to use greater and lesser * Adding a code comment clarifying the sign toggling behavior
1 parent 1a02f0d commit 48adb84

File tree

4 files changed

+251
-21
lines changed

4 files changed

+251
-21
lines changed

src/libraries/System.Private.CoreLib/src/System/Math.cs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -888,8 +888,8 @@ public static double Max(double val1, double val2)
888888
// This matches the IEEE 754:2019 `maximum` function
889889
//
890890
// It propagates NaN inputs back to the caller and
891-
// otherwise returns the larger of the inputs. It
892-
// treats +0 as larger than -0 as per the specification.
891+
// otherwise returns the greater of the inputs. It
892+
// treats +0 as greater than -0 as per the specification.
893893

894894
if (val1 != val2)
895895
{
@@ -946,8 +946,8 @@ public static float Max(float val1, float val2)
946946
// This matches the IEEE 754:2019 `maximum` function
947947
//
948948
// It propagates NaN inputs back to the caller and
949-
// otherwise returns the larger of the inputs. It
950-
// treats +0 as larger than -0 as per the specification.
949+
// otherwise returns the greater of the inputs. It
950+
// treats +0 as greater than -0 as per the specification.
951951

952952
if (val1 != val2)
953953
{
@@ -999,8 +999,8 @@ public static double MaxMagnitude(double x, double y)
999999
// This matches the IEEE 754:2019 `maximumMagnitude` function
10001000
//
10011001
// It propagates NaN inputs back to the caller and
1002-
// otherwise returns the input with a larger magnitude.
1003-
// It treats +0 as larger than -0 as per the specification.
1002+
// otherwise returns the input with a greater magnitude.
1003+
// It treats +0 as greater than -0 as per the specification.
10041004

10051005
double ax = Abs(x);
10061006
double ay = Abs(y);
@@ -1037,12 +1037,17 @@ public static double Min(double val1, double val2)
10371037
// This matches the IEEE 754:2019 `minimum` function
10381038
//
10391039
// It propagates NaN inputs back to the caller and
1040-
// otherwise returns the larger of the inputs. It
1041-
// treats +0 as larger than -0 as per the specification.
1040+
// otherwise returns the lesser of the inputs. It
1041+
// treats +0 as lesser than -0 as per the specification.
10421042

1043-
if (val1 != val2 && !double.IsNaN(val1))
1043+
if (val1 != val2)
10441044
{
1045-
return val1 < val2 ? val1 : val2;
1045+
if (!double.IsNaN(val1))
1046+
{
1047+
return val1 < val2 ? val1 : val2;
1048+
}
1049+
1050+
return val1;
10461051
}
10471052

10481053
return double.IsNegative(val1) ? val1 : val2;
@@ -1090,12 +1095,17 @@ public static float Min(float val1, float val2)
10901095
// This matches the IEEE 754:2019 `minimum` function
10911096
//
10921097
// It propagates NaN inputs back to the caller and
1093-
// otherwise returns the larger of the inputs. It
1094-
// treats +0 as larger than -0 as per the specification.
1098+
// otherwise returns the lesser of the inputs. It
1099+
// treats +0 as lesser than -0 as per the specification.
10951100

1096-
if (val1 != val2 && !float.IsNaN(val1))
1101+
if (val1 != val2)
10971102
{
1098-
return val1 < val2 ? val1 : val2;
1103+
if (!float.IsNaN(val1))
1104+
{
1105+
return val1 < val2 ? val1 : val2;
1106+
}
1107+
1108+
return val1;
10991109
}
11001110

11011111
return float.IsNegative(val1) ? val1 : val2;
@@ -1138,8 +1148,8 @@ public static double MinMagnitude(double x, double y)
11381148
// This matches the IEEE 754:2019 `minimumMagnitude` function
11391149
//
11401150
// It propagates NaN inputs back to the caller and
1141-
// otherwise returns the input with a larger magnitude.
1142-
// It treats +0 as larger than -0 as per the specification.
1151+
// otherwise returns the input with a lesser magnitude.
1152+
// It treats +0 as lesser than -0 as per the specification.
11431153

11441154
double ax = Abs(x);
11451155
double ay = Abs(y);

src/libraries/System.Private.CoreLib/src/System/MathF.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,8 @@ public static float MaxMagnitude(float x, float y)
252252
// This matches the IEEE 754:2019 `maximumMagnitude` function
253253
//
254254
// It propagates NaN inputs back to the caller and
255-
// otherwise returns the input with a larger magnitude.
256-
// It treats +0 as larger than -0 as per the specification.
255+
// otherwise returns the input with a greater magnitude.
256+
// It treats +0 as greater than -0 as per the specification.
257257

258258
float ax = Abs(x);
259259
float ay = Abs(y);
@@ -282,8 +282,8 @@ public static float MinMagnitude(float x, float y)
282282
// This matches the IEEE 754:2019 `minimumMagnitude` function
283283
//
284284
// It propagates NaN inputs back to the caller and
285-
// otherwise returns the input with a larger magnitude.
286-
// It treats +0 as larger than -0 as per the specification.
285+
// otherwise returns the input with a lesser magnitude.
286+
// It treats +0 as lesser than -0 as per the specification.
287287

288288
float ax = Abs(x);
289289
float ay = Abs(y);

src/libraries/System.Runtime.Extensions/tests/System/Math.cs

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,29 @@ public static void Max_Decimal()
796796
public static void Max_Double_NotNetFramework(double x, double y, double expectedResult)
797797
{
798798
AssertExtensions.Equal(expectedResult, Math.Max(x, y), 0.0);
799+
800+
if (double.IsNaN(x))
801+
{
802+
// Toggle the sign of the NaN to validate both +NaN and -NaN behave the same.
803+
// Negate should work as well but the JIT may constant fold or do other tricks
804+
// and normalize to a single NaN form so we do bitwise tricks to ensure we test
805+
// the right thing.
806+
807+
ulong bits = BitConverter.DoubleToUInt64Bits(x);
808+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
809+
x = BitConverter.UInt64BitsToDouble(bits);
810+
811+
AssertExtensions.Equal(expectedResult, Math.Max(x, y), 0.0);
812+
}
813+
814+
if (double.IsNaN(y))
815+
{
816+
ulong bits = BitConverter.DoubleToUInt64Bits(y);
817+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
818+
y = BitConverter.UInt64BitsToDouble(bits);
819+
820+
AssertExtensions.Equal(expectedResult, Math.Max(x, y), 0.0);
821+
}
799822
}
800823

801824
[Fact]
@@ -854,6 +877,29 @@ public static void Max_SByte()
854877
public static void Max_Single_NotNetFramework(float x, float y, float expectedResult)
855878
{
856879
AssertExtensions.Equal(expectedResult, Math.Max(x, y), 0.0f);
880+
881+
if (float.IsNaN(x))
882+
{
883+
// Toggle the sign of the NaN to validate both +NaN and -NaN behave the same.
884+
// Negate should work as well but the JIT may constant fold or do other tricks
885+
// and normalize to a single NaN form so we do bitwise tricks to ensure we test
886+
// the right thing.
887+
888+
uint bits = BitConverter.SingleToUInt32Bits(x);
889+
bits ^= BitConverter.SingleToUInt32Bits(-0.0f);
890+
x = BitConverter.UInt32BitsToSingle(bits);
891+
892+
AssertExtensions.Equal(expectedResult, Math.Max(x, y), 0.0f);
893+
}
894+
895+
if (float.IsNaN(y))
896+
{
897+
uint bits = BitConverter.SingleToUInt32Bits(y);
898+
bits ^= BitConverter.SingleToUInt32Bits(-0.0f);
899+
y = BitConverter.UInt32BitsToSingle(bits);
900+
901+
AssertExtensions.Equal(expectedResult, Math.Max(x, y), 0.0f);
902+
}
857903
}
858904

859905
[Fact]
@@ -919,6 +965,29 @@ public static void Min_Decimal()
919965
public static void Min_Double_NotNetFramework(double x, double y, double expectedResult)
920966
{
921967
AssertExtensions.Equal(expectedResult, Math.Min(x, y), 0.0);
968+
969+
if (double.IsNaN(x))
970+
{
971+
// Toggle the sign of the NaN to validate both +NaN and -NaN behave the same.
972+
// Negate should work as well but the JIT may constant fold or do other tricks
973+
// and normalize to a single NaN form so we do bitwise tricks to ensure we test
974+
// the right thing.
975+
976+
ulong bits = BitConverter.DoubleToUInt64Bits(x);
977+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
978+
x = BitConverter.UInt64BitsToDouble(bits);
979+
980+
AssertExtensions.Equal(expectedResult, Math.Min(x, y), 0.0);
981+
}
982+
983+
if (double.IsNaN(y))
984+
{
985+
ulong bits = BitConverter.DoubleToUInt64Bits(y);
986+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
987+
y = BitConverter.UInt64BitsToDouble(bits);
988+
989+
AssertExtensions.Equal(expectedResult, Math.Min(x, y), 0.0);
990+
}
922991
}
923992

924993
[Fact]
@@ -977,6 +1046,29 @@ public static void Min_SByte()
9771046
public static void Min_Single_NotNetFramework(float x, float y, float expectedResult)
9781047
{
9791048
AssertExtensions.Equal(expectedResult, Math.Min(x, y), 0.0f);
1049+
1050+
if (float.IsNaN(x))
1051+
{
1052+
// Toggle the sign of the NaN to validate both +NaN and -NaN behave the same.
1053+
// Negate should work as well but the JIT may constant fold or do other tricks
1054+
// and normalize to a single NaN form so we do bitwise tricks to ensure we test
1055+
// the right thing.
1056+
1057+
uint bits = BitConverter.SingleToUInt32Bits(x);
1058+
bits ^= BitConverter.SingleToUInt32Bits(-0.0f);
1059+
x = BitConverter.UInt32BitsToSingle(bits);
1060+
1061+
AssertExtensions.Equal(expectedResult, Math.Min(x, y), 0.0f);
1062+
}
1063+
1064+
if (float.IsNaN(y))
1065+
{
1066+
uint bits = BitConverter.SingleToUInt32Bits(y);
1067+
bits ^= BitConverter.SingleToUInt32Bits(-0.0f);
1068+
y = BitConverter.UInt32BitsToSingle(bits);
1069+
1070+
AssertExtensions.Equal(expectedResult, Math.Min(x, y), 0.0f);
1071+
}
9801072
}
9811073

9821074
[Fact]
@@ -2566,6 +2658,29 @@ public static void Log2(double value, double expectedResult, double allowedVaria
25662658
public static void MaxMagnitude(double x, double y, double expectedResult)
25672659
{
25682660
AssertExtensions.Equal(expectedResult, Math.MaxMagnitude(x, y), 0.0);
2661+
2662+
if (double.IsNaN(x))
2663+
{
2664+
// Toggle the sign of the NaN to validate both +NaN and -NaN behave the same.
2665+
// Negate should work as well but the JIT may constant fold or do other tricks
2666+
// and normalize to a single NaN form so we do bitwise tricks to ensure we test
2667+
// the right thing.
2668+
2669+
ulong bits = BitConverter.DoubleToUInt64Bits(x);
2670+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
2671+
x = BitConverter.UInt64BitsToDouble(bits);
2672+
2673+
AssertExtensions.Equal(expectedResult, Math.MaxMagnitude(x, y), 0.0);
2674+
}
2675+
2676+
if (double.IsNaN(y))
2677+
{
2678+
ulong bits = BitConverter.DoubleToUInt64Bits(y);
2679+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
2680+
y = BitConverter.UInt64BitsToDouble(bits);
2681+
2682+
AssertExtensions.Equal(expectedResult, Math.MaxMagnitude(x, y), 0.0);
2683+
}
25692684
}
25702685

25712686
[Theory]
@@ -2589,6 +2704,29 @@ public static void MaxMagnitude(double x, double y, double expectedResult)
25892704
public static void MinMagnitude(double x, double y, double expectedResult)
25902705
{
25912706
AssertExtensions.Equal(expectedResult, Math.MinMagnitude(x, y), 0.0);
2707+
2708+
if (double.IsNaN(x))
2709+
{
2710+
// Toggle the sign of the NaN to validate both +NaN and -NaN behave the same.
2711+
// Negate should work as well but the JIT may constant fold or do other tricks
2712+
// and normalize to a single NaN form so we do bitwise tricks to ensure we test
2713+
// the right thing.
2714+
2715+
ulong bits = BitConverter.DoubleToUInt64Bits(x);
2716+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
2717+
x = BitConverter.UInt64BitsToDouble(bits);
2718+
2719+
AssertExtensions.Equal(expectedResult, Math.MinMagnitude(x, y), 0.0);
2720+
}
2721+
2722+
if (double.IsNaN(y))
2723+
{
2724+
ulong bits = BitConverter.DoubleToUInt64Bits(y);
2725+
bits ^= BitConverter.DoubleToUInt64Bits(-0.0);
2726+
y = BitConverter.UInt64BitsToDouble(bits);
2727+
2728+
AssertExtensions.Equal(expectedResult, Math.MinMagnitude(x, y), 0.0);
2729+
}
25922730
}
25932731

25942732
[Theory]
@@ -2657,7 +2795,7 @@ public static void MinMagnitude(double x, double y, double expectedResult)
26572795
[InlineData( 9.267056966972586, 2, 37.06822786789034, CrossPlatformMachineEpsilon * 100)]
26582796
[InlineData( 0.5617597462207241, 5, 17.97631187906317, CrossPlatformMachineEpsilon * 100)]
26592797
[InlineData( 0.7741522965913037, 6, 49.545746981843436, CrossPlatformMachineEpsilon * 100)]
2660-
[InlineData( -0.6787637026394024, 7, -86.88175393784351, CrossPlatformMachineEpsilon * 100)]
2798+
[InlineData( -0.6787637026394024, 7, -86.88175393784351, CrossPlatformMachineEpsilon * 100)]
26612799
[InlineData( -6.531673581913484, 1, -13.063347163826968, CrossPlatformMachineEpsilon * 100)]
26622800
[InlineData( 9.267056966972586, 2, 37.06822786789034, CrossPlatformMachineEpsilon * 100)]
26632801
[InlineData( 0.5617597462207241, 5, 17.97631187906317, CrossPlatformMachineEpsilon * 100)]

0 commit comments

Comments
 (0)