Skip to content

Commit

Permalink
[Release/5.0] Port Fix conversions from subnormal System.Half values …
Browse files Browse the repository at this point in the history
…to .NET 5.0 (#46641)

* Half conversion test cases for subnormal corner cases (#46523)

* Half conversion test cases for subnormal corner cases

* Single/Double->Half rounding corner test cases

* Single/Double->Half conversion subnormal rounding corner test cases

* Fix conversions from subnormal System.Half values (#46536)

* Fix conversions from subnormal System.Half values

* Fix conversions from subnormal System.Single/Double to System.Half values

* Fixing the HalfTests parameters

Co-authored-by: Emile Cormier <ecorm@users.noreply.github.com>
  • Loading branch information
tannergooding and ecorm committed Jan 13, 2021
1 parent 988fa3b commit 17fc554
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 5 deletions.
5 changes: 3 additions & 2 deletions src/libraries/System.Private.CoreLib/src/System/Half.cs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ private static ushort RoundPackToHalf(bool sign, short exp, ushort sig)
{
sig = (ushort)ShiftRightJam(sig, -exp);
exp = 0;
roundBits = sig & 0xF;
}
else if (exp > 0x1D || sig + RoundIncrement >= 0x8000) // Overflow
{
Expand Down Expand Up @@ -685,10 +686,10 @@ private static double CreateDoubleNaN(bool sign, ulong significand)
}

private static float CreateSingle(bool sign, byte exp, uint sig)
=> BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << float.SignShift) | ((uint)exp << float.ExponentShift) | sig));
=> BitConverter.Int32BitsToSingle((int)(((sign ? 1U : 0U) << float.SignShift) + ((uint)exp << float.ExponentShift) + sig));

private static double CreateDouble(bool sign, ushort exp, ulong sig)
=> BitConverter.Int64BitsToDouble((long)(((sign ? 1UL : 0UL) << double.SignShift) | ((ulong)exp << double.ExponentShift) | sig));
=> BitConverter.Int64BitsToDouble((long)(((sign ? 1UL : 0UL) << double.SignShift) + ((ulong)exp << double.ExponentShift) + sig));

#endregion
}
Expand Down
122 changes: 119 additions & 3 deletions src/libraries/System.Runtime/tests/System/HalfTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ public static IEnumerable<object[]> ExplicitConversion_ToSingle_TestData()
(UInt16BitsToHalf(0b1_11111_1010101010), BitConverter.Int32BitsToSingle(unchecked((int)0xFFD54000))), // Negative Signalling NaN - Should preserve payload
(Half.Epsilon, 1/16777216f), // PosEpsilon = 0.000000059605...
(UInt16BitsToHalf(0), 0), // 0
(UInt16BitsToHalf(0b1_00000_0000000000), -0f), // -0
(UInt16BitsToHalf(0b1_00000_0000000000), -0f), // -0
(UInt16BitsToHalf(0b0_10000_1001001000), 3.140625f), // 3.140625
(UInt16BitsToHalf(0b1_10000_1001001000), -3.140625f), // -3.140625
(UInt16BitsToHalf(0b0_10000_0101110000), 2.71875f), // 2.71875
Expand All @@ -376,6 +376,16 @@ public static IEnumerable<object[]> ExplicitConversion_ToSingle_TestData()
(UInt16BitsToHalf(0b1_01111_1000000000), -1.5f), // -1.5
(UInt16BitsToHalf(0b0_01111_1000000001), 1.5009765625f), // 1.5009765625
(UInt16BitsToHalf(0b1_01111_1000000001), -1.5009765625f), // -1.5009765625
(UInt16BitsToHalf(0b0_00001_0000000000), BitConverter.Int32BitsToSingle(0x38800000)), // smallest normal
(UInt16BitsToHalf(0b0_00000_1111111111), BitConverter.Int32BitsToSingle(0x387FC000)), // largest subnormal
(UInt16BitsToHalf(0b0_00000_1000000000), BitConverter.Int32BitsToSingle(0x38000000)), // middle subnormal
(UInt16BitsToHalf(0b0_00000_0111111111), BitConverter.Int32BitsToSingle(0x37FF8000)), // just below middle subnormal
(UInt16BitsToHalf(0b0_00000_0000000001), BitConverter.Int32BitsToSingle(0x33800000)), // smallest subnormal
(UInt16BitsToHalf(0b1_00000_0000000001), BitConverter.Int32BitsToSingle(unchecked((int)0xB3800000))), // highest negative subnormal
(UInt16BitsToHalf(0b1_00000_0111111111), BitConverter.Int32BitsToSingle(unchecked((int)0xB7FF8000))), // just above negative middle subnormal
(UInt16BitsToHalf(0b1_00000_1000000000), BitConverter.Int32BitsToSingle(unchecked((int)0xB8000000))), // negative middle subnormal
(UInt16BitsToHalf(0b1_00000_1111111111), BitConverter.Int32BitsToSingle(unchecked((int)0xB87FC000))), // lowest negative subnormal
(UInt16BitsToHalf(0b1_00001_0000000000), BitConverter.Int32BitsToSingle(unchecked((int)0xB8800000))) // highest negative normal
};

foreach ((Half original, float expected) in data)
Expand Down Expand Up @@ -412,15 +422,25 @@ public static IEnumerable<object[]> ExplicitConversion_ToDouble_TestData()
(UInt16BitsToHalf(0b1_11111_1010101010), BitConverter.Int64BitsToDouble(unchecked((long)0xFFFAA800_00000000))), // Negative Signalling NaN - Should preserve payload
(Half.Epsilon, 1/16777216d), // PosEpsilon = 0.000000059605...
(UInt16BitsToHalf(0), 0d), // 0
(UInt16BitsToHalf(0b1_00000_0000000000), -0d), // -0
(UInt16BitsToHalf(0b1_00000_0000000000), -0d), // -0
(UInt16BitsToHalf(0b0_10000_1001001000), 3.140625d), // 3.140625
(UInt16BitsToHalf(0b1_10000_1001001000), -3.140625d), // -3.140625
(UInt16BitsToHalf(0b0_10000_0101110000), 2.71875d), // 2.71875
(UInt16BitsToHalf(0b1_10000_0101110000), -2.71875d), // -2.71875
(UInt16BitsToHalf(0b0_01111_1000000000), 1.5d), // 1.5
(UInt16BitsToHalf(0b1_01111_1000000000), -1.5d), // -1.5
(UInt16BitsToHalf(0b0_01111_1000000001), 1.5009765625d), // 1.5009765625
(UInt16BitsToHalf(0b1_01111_1000000001), -1.5009765625d) // -1.5009765625
(UInt16BitsToHalf(0b1_01111_1000000001), -1.5009765625d), // -1.5009765625
(UInt16BitsToHalf(0b0_00001_0000000000), BitConverter.Int64BitsToDouble(0x3F10000000000000)), // smallest normal
(UInt16BitsToHalf(0b0_00000_1111111111), BitConverter.Int64BitsToDouble(0x3F0FF80000000000)), // largest subnormal
(UInt16BitsToHalf(0b0_00000_1000000000), BitConverter.Int64BitsToDouble(0x3F00000000000000)), // middle subnormal
(UInt16BitsToHalf(0b0_00000_0111111111), BitConverter.Int64BitsToDouble(0x3EFFF00000000000)), // just below middle subnormal
(UInt16BitsToHalf(0b0_00000_0000000001), BitConverter.Int64BitsToDouble(0x3E70000000000000)), // smallest subnormal
(UInt16BitsToHalf(0b1_00000_0000000001), BitConverter.Int64BitsToDouble(unchecked((long)0xBE70000000000000))), // highest negative subnormal
(UInt16BitsToHalf(0b1_00000_0111111111), BitConverter.Int64BitsToDouble(unchecked((long)0xBEFFF00000000000))), // just above negative middle subnormal
(UInt16BitsToHalf(0b1_00000_1000000000), BitConverter.Int64BitsToDouble(unchecked((long)0xBF00000000000000))), // negative middle subnormal
(UInt16BitsToHalf(0b1_00000_1111111111), BitConverter.Int64BitsToDouble(unchecked((long)0xBF0FF80000000000))), // lowest negative subnormal
(UInt16BitsToHalf(0b1_00001_0000000000), BitConverter.Int64BitsToDouble(unchecked((long)0xBF10000000000000))) // highest negative normal
};

foreach ((Half original, double expected) in data)
Expand Down Expand Up @@ -469,6 +489,46 @@ public static IEnumerable<object[]> ExplicitConversion_FromSingle_TestData()
(-1.5f, UInt16BitsToHalf(0b1_01111_1000000000)), // -1.5
(1.5009765625f, UInt16BitsToHalf(0b0_01111_1000000001)), // 1.5009765625
(-1.5009765625f, UInt16BitsToHalf(0b1_01111_1000000001)), // -1.5009765625
(BitConverter.Int32BitsToSingle(0x38800000), UInt16BitsToHalf(0b0_00001_0000000000)), // smallest normal
(BitConverter.Int32BitsToSingle(0x387FC000), UInt16BitsToHalf(0b0_00000_1111111111)), // largest subnormal
(BitConverter.Int32BitsToSingle(0x38000000), UInt16BitsToHalf(0b0_00000_1000000000)), // middle subnormal
(BitConverter.Int32BitsToSingle(0x37FF8000), UInt16BitsToHalf(0b0_00000_0111111111)), // just below middle subnormal
(BitConverter.Int32BitsToSingle(0x33800000), UInt16BitsToHalf(0b0_00000_0000000001)), // smallest subnormal
(BitConverter.Int32BitsToSingle(unchecked((int)0xB3800000)),
UInt16BitsToHalf(0b1_00000_0000000001)), // highest negative subnormal
(BitConverter.Int32BitsToSingle(unchecked((int)0xB7FF8000)),
UInt16BitsToHalf(0b1_00000_0111111111)), // just above negative middle subnormal
(BitConverter.Int32BitsToSingle(unchecked((int)0xB8000000)),
UInt16BitsToHalf(0b1_00000_1000000000)), // negative middle subnormal
(BitConverter.Int32BitsToSingle(unchecked((int)0xB87FC000)),
UInt16BitsToHalf(0b1_00000_1111111111)), // lowest negative subnormal
(BitConverter.Int32BitsToSingle(unchecked((int)0xB8800000)),
UInt16BitsToHalf(0b1_00001_0000000000)), // highest negative normal
(BitConverter.Int32BitsToSingle(0b0_10001001_00000000111000000000001),
UInt16BitsToHalf(0b0_11001_0000000100)), // 1027.5+ULP rounds up
(BitConverter.Int32BitsToSingle(0b0_10001001_00000000111000000000000),
UInt16BitsToHalf(0b0_11001_0000000100)), // 1027.5 rounds to even
(BitConverter.Int32BitsToSingle(0b0_10001001_00000000110111111111111),
UInt16BitsToHalf(0b0_11001_0000000011)), // 1027.5-ULP rounds down
(BitConverter.Int32BitsToSingle(unchecked((int)0b1_10001001_00000000110111111111111)),
UInt16BitsToHalf(0b1_11001_0000000011)), // -1027.5+ULP rounds towards zero
(BitConverter.Int32BitsToSingle(unchecked((int)0b1_10001001_00000000111000000000000)),
UInt16BitsToHalf(0b1_11001_0000000100)), // -1027.5 rounds to even
(BitConverter.Int32BitsToSingle(unchecked((int)0b1_10001001_00000000111000000000001)),
UInt16BitsToHalf(0b1_11001_0000000100)), // -1027.5-ULP rounds away from zero
(BitConverter.Int32BitsToSingle(0b0_01110000_00000001110000000000001),
UInt16BitsToHalf(0b0_00000_1000000100)), // subnormal + ULP rounds up
(BitConverter.Int32BitsToSingle(0b0_01110000_00000001110000000000000),
UInt16BitsToHalf(0b0_00000_1000000100)), // subnormal rounds to even
(BitConverter.Int32BitsToSingle(0b0_01110000_00000001101111111111111),
UInt16BitsToHalf(0b0_00000_1000000011)), // subnormal - ULP rounds down
(BitConverter.Int32BitsToSingle(unchecked((int)0b1_01110000_00000001101111111111111)),
UInt16BitsToHalf(0b1_00000_1000000011)), // neg subnormal + ULP rounds higher
(BitConverter.Int32BitsToSingle(unchecked((int)0b1_01110000_00000001110000000000000)),
UInt16BitsToHalf(0b1_00000_1000000100)), // neg subnormal rounds to even
(BitConverter.Int32BitsToSingle(unchecked((int)0b1_01110000_00000001101111111111111)),
UInt16BitsToHalf(0b1_00000_1000000011)), // neg subnormal - ULP rounds lower,
(BitConverter.Int32BitsToSingle(0x33000000), UInt16BitsToHalf(0b0_00000_000000000)), // (half-precision minimum subnormal / 2) should underflow to zero
};

foreach ((float original, Half expected) in data)
Expand Down Expand Up @@ -518,6 +578,51 @@ public static IEnumerable<object[]> ExplicitConversion_FromDouble_TestData()
(-1.5d, UInt16BitsToHalf(0b1_01111_1000000000)), // -1.5
(1.5009765625d, UInt16BitsToHalf(0b0_01111_1000000001)), // 1.5009765625
(-1.5009765625d, UInt16BitsToHalf(0b1_01111_1000000001)), // -1.5009765625
(BitConverter.Int64BitsToDouble(0x3F10000000000000),
UInt16BitsToHalf(0b0_00001_0000000000)), // smallest normal
(BitConverter.Int64BitsToDouble(0x3F0FF80000000000),
UInt16BitsToHalf(0b0_00000_1111111111)), // largest subnormal
(BitConverter.Int64BitsToDouble(0x3f00000000000000),
UInt16BitsToHalf(0b0_00000_1000000000)), // middle subnormal
(BitConverter.Int64BitsToDouble(0x3EFFF00000000000),
UInt16BitsToHalf(0b0_00000_0111111111)), // just below middle subnormal
(BitConverter.Int64BitsToDouble(0x3E70000000000000),
UInt16BitsToHalf(0b0_00000_0000000001)), // smallest subnormal
(BitConverter.Int64BitsToDouble(unchecked((long)0xBE70000000000000)),
UInt16BitsToHalf(0b1_00000_0000000001)), // highest negative subnormal
(BitConverter.Int64BitsToDouble(unchecked((long)0xBEFFF00000000000)),
UInt16BitsToHalf(0b1_00000_0111111111)), // just above negative middle subnormal
(BitConverter.Int64BitsToDouble(unchecked((long)0xBF00000000000000)),
UInt16BitsToHalf(0b1_00000_1000000000)), // negative middle subnormal
(BitConverter.Int64BitsToDouble(unchecked((long)0xBF0FF80000000000)),
UInt16BitsToHalf(0b1_00000_1111111111)), // lowest negative subnormal
(BitConverter.Int64BitsToDouble(unchecked((long)0xBF10000000000000)),
UInt16BitsToHalf(0b1_00001_0000000000)), // highest negative normal
(BitConverter.Int64BitsToDouble(0x40900E0000000001),
UInt16BitsToHalf(0b0_11001_0000000100)), // 1027.5+ULP rounds up
(BitConverter.Int64BitsToDouble(0x40900E0000000000),
UInt16BitsToHalf(0b0_11001_0000000100)), // 1027.5 rounds to even
(BitConverter.Int64BitsToDouble(0x40900DFFFFFFFFFF),
UInt16BitsToHalf(0b0_11001_0000000011)), // 1027.5-ULP rounds down
(BitConverter.Int64BitsToDouble(unchecked((long)0xC0900DFFFFFFFFFF)),
UInt16BitsToHalf(0b1_11001_0000000011)), // -1027.5+ULP rounds towards zero
(BitConverter.Int64BitsToDouble(unchecked((long)0xC0900E0000000000)),
UInt16BitsToHalf(0b1_11001_0000000100)), // -1027.5 rounds to even
(BitConverter.Int64BitsToDouble(unchecked((long)0xC0900E0000000001)),
UInt16BitsToHalf(0b1_11001_0000000100)), // -1027.5-ULP rounds away from zero
(BitConverter.Int64BitsToDouble(0x3F001C0000000001),
UInt16BitsToHalf(0b0_00000_1000000100)), // subnormal + ULP rounds up
(BitConverter.Int64BitsToDouble(0x3F001C0000000001),
UInt16BitsToHalf(0b0_00000_1000000100)), // subnormal rounds to even
(BitConverter.Int64BitsToDouble(0x3F001BFFFFFFFFFF),
UInt16BitsToHalf(0b0_00000_1000000011)), // subnormal - ULP rounds down
(BitConverter.Int64BitsToDouble(unchecked((long)0xBF001BFFFFFFFFFF)),
UInt16BitsToHalf(0b1_00000_1000000011)), // neg subnormal + ULP rounds higher
(BitConverter.Int64BitsToDouble(unchecked((long)0xBF001C0000000000)),
UInt16BitsToHalf(0b1_00000_1000000100)), // neg subnormal rounds to even
(BitConverter.Int64BitsToDouble(unchecked((long)0xBF001C0000000001)),
UInt16BitsToHalf(0b1_00000_1000000100)), // neg subnormal - ULP rounds lower
(BitConverter.Int64BitsToDouble(0x3E60000000000000), UInt16BitsToHalf(0b0_00000_000000000)), // (half-precision minimum subnormal / 2) should underflow to zero
};

foreach ((double original, Half expected) in data)
Expand Down Expand Up @@ -885,6 +990,17 @@ public static IEnumerable<object[]> ToStringRoundtrip_TestData()
yield return new object[] { MathF.PI };
yield return new object[] { Half.MaxValue };
yield return new object[] { Half.PositiveInfinity };

yield return new object[] { (UInt16BitsToHalf(0b0_00001_0000000000))}; // smallest normal
yield return new object[] { (UInt16BitsToHalf(0b0_00000_1111111111))}; // largest subnormal
yield return new object[] { (UInt16BitsToHalf(0b0_00000_1000000000))}; // middle subnormal
yield return new object[] { (UInt16BitsToHalf(0b0_00000_0111111111))}; // just below middle subnormal
yield return new object[] { (UInt16BitsToHalf(0b0_00000_0000000001))}; // smallest subnormal
yield return new object[] { (UInt16BitsToHalf(0b1_00000_0000000001))}; // highest negative subnormal
yield return new object[] { (UInt16BitsToHalf(0b1_00000_0111111111))}; // just above negative middle subnormal
yield return new object[] { (UInt16BitsToHalf(0b1_00000_1000000000))}; // negative middle subnormal
yield return new object[] { (UInt16BitsToHalf(0b1_00000_1111111111))}; // lowest negative subnormal
yield return new object[] { (UInt16BitsToHalf(0b1_00001_0000000000))}; // highest negative normal
}

[Theory]
Expand Down

0 comments on commit 17fc554

Please sign in to comment.