Skip to content

Commit

Permalink
[RISC-V] Test HalfTest: fixed payload preservation testcase for RISC-V (
Browse files Browse the repository at this point in the history
#96888)

* Fixed payload preservation testcase in halftest for RISC-V

* Apply comments

* Add AssertExtentions.Equal function to test bit identity of two floating numbers

* Add descriptions for equal functions and fix ToStringRoundtrip test's assert argument

* Apply comments

* Fix CI failure

* Apply comments

---------

Co-authored-by: Denis Paranichev <48580269+DenisParal@users.noreply.github.com>
Co-authored-by: d.paranichev <d.paranichev@partner.samsung.com>
  • Loading branch information
3 people authored Feb 5, 2024
1 parent 67604d3 commit bb2376c
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 117 deletions.
289 changes: 178 additions & 111 deletions src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,126 @@ public ItemCount(int original, int remain)
}
}

static unsafe bool IsNegativeZero(float value)
{
return (*(uint*)(&value)) == 0x80000000;
}

static unsafe bool IsPositiveZero(float value)
{
return (*(uint*)(&value)) == 0x00000000;
}

static unsafe bool IsNegativeZero(double value)
{
return (*(ulong*)(&value)) == 0x8000000000000000;
}

static unsafe bool IsPositiveZero(double value)
{
return (*(ulong*)(&value)) == 0x0000000000000000;
}

#if NET6_0_OR_GREATER
static unsafe bool IsNegativeZero(Half value)
{
return (*(ushort*)(&value)) == 0x8000;
}

static unsafe bool IsPositiveZero(Half value)
{
return (*(ushort*)(&value)) == 0x0000;
}
#endif

// We have a custom ToString here to ensure that edge cases (specifically +-0.0,
// but also NaN and +-infinity) are correctly and consistently represented.
static string ToStringPadded(float value)
{
if (float.IsNaN(value))
{
return "NaN".PadLeft(10);
}
else if (float.IsPositiveInfinity(value))
{
return "+\u221E".PadLeft(10);
}
else if (float.IsNegativeInfinity(value))
{
return "-\u221E".PadLeft(10);
}
else if (IsNegativeZero(value))
{
return "-0.0".PadLeft(10);
}
else if (IsPositiveZero(value))
{
return "+0.0".PadLeft(10);
}
else
{
return $"{value,10:G9}";
}
}

static string ToStringPadded(double value)
{
if (double.IsNaN(value))
{
return "NaN".PadLeft(20);
}
else if (double.IsPositiveInfinity(value))
{
return "+\u221E".PadLeft(20);
}
else if (double.IsNegativeInfinity(value))
{
return "-\u221E".PadLeft(20);
}
else if (IsNegativeZero(value))
{
return "-0.0".PadLeft(20);
}
else if (IsPositiveZero(value))
{
return "+0.0".PadLeft(20);
}
else
{
return $"{value,20:G17}";
}
}

#if NET6_0_OR_GREATER
static string ToStringPadded(Half value)
{
if (Half.IsNaN(value))
{
return "NaN".PadLeft(5);
}
else if (Half.IsPositiveInfinity(value))
{
return "+\u221E".PadLeft(5);
}
else if (Half.IsNegativeInfinity(value))
{
return "-\u221E".PadLeft(5);
}
else if (IsNegativeZero(value))
{
return "-0.0".PadLeft(5);
}
else if (IsPositiveZero(value))
{
return "+0.0".PadLeft(5);
}
else
{
return $"{value,5:G5}";
}
}
#endif

/// <summary>Verifies that two <see cref="double"/> values are equal, within the <paramref name="allowedVariance"/>.</summary>
/// <param name="expected">The expected value</param>
/// <param name="actual">The value to be compared against</param>
Expand Down Expand Up @@ -774,46 +894,6 @@ public static void Equal(double expected, double actual, double variance)
{
throw EqualException.ForMismatchedValues(ToStringPadded(expected), ToStringPadded(actual));
}

static unsafe bool IsNegativeZero(double value)
{
return (*(ulong*)(&value)) == 0x8000000000000000;
}

static unsafe bool IsPositiveZero(double value)
{
return (*(ulong*)(&value)) == 0x0000000000000000;
}

// We have a custom ToString here to ensure that edge cases (specifically +-0.0,
// but also NaN and +-infinity) are correctly and consistently represented.
static string ToStringPadded(double value)
{
if (double.IsNaN(value))
{
return "NaN".PadLeft(20);
}
else if (double.IsPositiveInfinity(value))
{
return "+\u221E".PadLeft(20);
}
else if (double.IsNegativeInfinity(value))
{
return "-\u221E".PadLeft(20);
}
else if (IsNegativeZero(value))
{
return "-0.0".PadLeft(20);
}
else if (IsPositiveZero(value))
{
return "+0.0".PadLeft(20);
}
else
{
return $"{value,20:G17}";
}
}
}

/// <summary>Verifies that two <see cref="float"/> values are equal, within the <paramref name="variance"/>.</summary>
Expand Down Expand Up @@ -927,46 +1007,6 @@ public static void Equal(float expected, float actual, float variance)
{
throw EqualException.ForMismatchedValues(ToStringPadded(expected), ToStringPadded(actual));
}

static unsafe bool IsNegativeZero(float value)
{
return (*(uint*)(&value)) == 0x80000000;
}

static unsafe bool IsPositiveZero(float value)
{
return (*(uint*)(&value)) == 0x00000000;
}

// We have a custom ToString here to ensure that edge cases (specifically +-0.0,
// but also NaN and +-infinity) are correctly and consistently represented.
static string ToStringPadded(float value)
{
if (float.IsNaN(value))
{
return "NaN".PadLeft(10);
}
else if (float.IsPositiveInfinity(value))
{
return "+\u221E".PadLeft(10);
}
else if (float.IsNegativeInfinity(value))
{
return "-\u221E".PadLeft(10);
}
else if (IsNegativeZero(value))
{
return "-0.0".PadLeft(10);
}
else if (IsPositiveZero(value))
{
return "+0.0".PadLeft(10);
}
else
{
return $"{value,10:G9}";
}
}
}

#if NET6_0_OR_GREATER
Expand Down Expand Up @@ -1081,46 +1121,73 @@ public static void Equal(Half expected, Half actual, Half variance)
{
throw EqualException.ForMismatchedValues(ToStringPadded(expected), ToStringPadded(actual));
}
}
#endif

static unsafe bool IsNegativeZero(Half value)
/// <summary>Verifies that two <see cref="double"/> values's binary representations are identical.</summary>
/// <param name="expected">The expected value</param>
/// <param name="actual">The value to be compared against</param>
/// <exception cref="EqualException">Thrown when the representations are not identical</exception>
public static void Equal(double expected, double actual)
{
if (BitConverter.DoubleToInt64Bits(expected) == BitConverter.DoubleToInt64Bits(actual))
{
return (*(ushort*)(&value)) == 0x8000;
return;
}

static unsafe bool IsPositiveZero(Half value)
if (PlatformDetection.IsRiscV64Process && double.IsNaN(expected) && double.IsNaN(actual))
{
return (*(ushort*)(&value)) == 0x0000;
// RISC-V does not preserve payload
return;
}

// We have a custom ToString here to ensure that edge cases (specifically +-0.0,
// but also NaN and +-infinity) are correctly and consistently represented.
static string ToStringPadded(Half value)
throw EqualException.ForMismatchedValues(ToStringPadded(expected), ToStringPadded(actual));
}

/// <summary>Verifies that two <see cref="float"/> values's binary representations are identical.</summary>
/// <param name="expected">The expected value</param>
/// <param name="actual">The value to be compared against</param>
/// <exception cref="EqualException">Thrown when the representations are not identical</exception>
public static void Equal(float expected, float actual)
{
static unsafe int SingleToInt32Bits(float value)
{
if (Half.IsNaN(value))
{
return "NaN".PadLeft(5);
}
else if (Half.IsPositiveInfinity(value))
{
return "+\u221E".PadLeft(5);
}
else if (Half.IsNegativeInfinity(value))
{
return "-\u221E".PadLeft(5);
}
else if (IsNegativeZero(value))
{
return "-0.0".PadLeft(5);
}
else if (IsPositiveZero(value))
{
return "+0.0".PadLeft(5);
}
else
{
return $"{value,5:G5}";
}
return *(int*)&value;
}

if (SingleToInt32Bits(expected) == SingleToInt32Bits(actual))
{
return;
}

if (PlatformDetection.IsRiscV64Process && float.IsNaN(expected) && float.IsNaN(actual))
{
// RISC-V does not preserve payload
return;
}

throw EqualException.ForMismatchedValues(ToStringPadded(expected), ToStringPadded(actual));
}

#if NET6_0_OR_GREATER
/// <summary>Verifies that two <see cref="Half"/> values's binary representations are identical.</summary>
/// <param name="expected">The expected value</param>
/// <param name="actual">The value to be compared against</param>
/// <exception cref="EqualException">Thrown when the representations are not identical</exception>
public static void Equal(Half expected, Half actual)
{
if (BitConverter.HalfToInt16Bits(expected) == BitConverter.HalfToInt16Bits(actual))
{
return;
}

if (PlatformDetection.IsRiscV64Process && Half.IsNaN(expected) && Half.IsNaN(actual))
{
// RISC-V does not preserve payload
return;
}

throw EqualException.ForMismatchedValues(ToStringPadded(expected), ToStringPadded(actual));
}
#endif
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ public static IEnumerable<object[]> ExplicitConversion_ToSingle_TestData()
public static void ExplicitConversion_ToSingle(Half value, float expected) // Check the underlying bits for verifying NaNs
{
float f = (float)value;
Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(f));
AssertExtensions.Equal(expected, f);
}

public static IEnumerable<object[]> ExplicitConversion_ToDouble_TestData()
Expand Down Expand Up @@ -466,7 +466,7 @@ public static IEnumerable<object[]> ExplicitConversion_ToDouble_TestData()
public static void ExplicitConversion_ToDouble(Half value, double expected) // Check the underlying bits for verifying NaNs
{
double d = (double)value;
Assert.Equal(BitConverter.DoubleToInt64Bits(expected), BitConverter.DoubleToInt64Bits(d));
AssertExtensions.Equal(expected, d);
}

// ---------- Start of To-half conversion tests ----------
Expand Down Expand Up @@ -554,7 +554,7 @@ public static IEnumerable<object[]> ExplicitConversion_FromSingle_TestData()
public static void ExplicitConversion_FromSingle(float f, Half expected) // Check the underlying bits for verifying NaNs
{
Half h = (Half)f;
Assert.Equal(BitConverter.HalfToUInt16Bits(expected), BitConverter.HalfToUInt16Bits(h));
AssertExtensions.Equal(expected, h);
}

public static IEnumerable<object[]> ExplicitConversion_FromDouble_TestData()
Expand Down Expand Up @@ -648,7 +648,7 @@ public static IEnumerable<object[]> ExplicitConversion_FromDouble_TestData()
public static void ExplicitConversion_FromDouble(double d, Half expected) // Check the underlying bits for verifying NaNs
{
Half h = (Half)d;
Assert.Equal(BitConverter.HalfToUInt16Bits(expected), BitConverter.HalfToUInt16Bits(h));
AssertExtensions.Equal(expected, h);
}

public static IEnumerable<object[]> Parse_Valid_TestData()
Expand Down Expand Up @@ -1105,7 +1105,7 @@ public static void ToStringRoundtrip(object o_value)
{
float value = o_value is float floatValue ? floatValue : (float)(Half)o_value;
Half result = Half.Parse(value.ToString());
Assert.Equal(BitConverter.HalfToUInt16Bits((Half)value), BitConverter.HalfToUInt16Bits(result));
AssertExtensions.Equal((Half)value, result);
}

[Theory]
Expand All @@ -1114,7 +1114,7 @@ public static void ToStringRoundtrip_R(object o_value)
{
float value = o_value is float floatValue ? floatValue : (float)(Half)o_value;
Half result = Half.Parse(value.ToString("R"));
Assert.Equal(BitConverter.HalfToUInt16Bits((Half)value), BitConverter.HalfToUInt16Bits(result));
AssertExtensions.Equal((Half)value, result);
}

public static IEnumerable<object[]> RoundTripFloat_CornerCases()
Expand Down

0 comments on commit bb2376c

Please sign in to comment.