Skip to content

Commit

Permalink
Use canonical NaN representation for NaN values
Browse files Browse the repository at this point in the history
RFC 7049 (CBOR) specifies "If NaN is an allowed value, it must always be represented as 0xf97e00". The only exception is when the user explicitly requests precision (FP size) is preserved.

The problem occurred for x86, C# defines NaN as 0.0/0.0 which yields -NaN on x86 FP units, which gets encoded as 0xf9fe00.

Fixes issue dotnet#92080
  • Loading branch information
tomeksowi committed Oct 6, 2023
1 parent 1c6d909 commit 5b6f257
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public partial class CborWriter
/// <para>The written data is not accepted under the current conformance mode.</para></exception>
public void WriteSingle(float value)
{
if (float.IsNaN(value))
{
value = BitConverter.Int32BitsToSingle(0x7fc00000); // canonical NaN as per RFC 7049
}

if (!CborConformanceModeHelpers.RequiresPreservingFloatPrecision(ConformanceMode) &&
TryConvertSingleToHalf(value, out var half))
{
Expand All @@ -38,6 +43,11 @@ public void WriteSingle(float value)
/// <para>The written data is not accepted under the current conformance mode.</para></exception>
public void WriteDouble(double value)
{
if (double.IsNaN(value))
{
value = BitConverter.Int64BitsToDouble(0x7ff8000000000000); // canonical NaN as per RFC 7049
}

if (!CborConformanceModeHelpers.RequiresPreservingFloatPrecision(ConformanceMode) &&
TryConvertDoubleToSingle(value, out float single))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public partial class CborWriterTests
[InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "98190102030405060708090a0b0c0d0e0f101112131415161718181819")]
[InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "840120604107")]
[InlineData(new object[] { "lorem", "ipsum", "dolor" }, "83656c6f72656d65697073756d65646f6c6f72")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "84f4f6f9fe00f97c00")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "84f4f6f97e00f97c00")]
public static void WriteArray_SimpleValues_HappyPath(object[] values, string expectedHexEncoding)
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
Expand Down Expand Up @@ -48,7 +48,7 @@ public static void WriteArray_NestedValues_HappyPath(object[] values, string exp
[InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff")]
[InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "9f0120604107ff")]
[InlineData(new object[] { "lorem", "ipsum", "dolor" }, "9f656c6f72656d65697073756d65646f6c6f72ff")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "9ff4f6f9fe00f97c00ff")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "9ff4f6f97e00f97c00ff")]
public static void WriteArray_IndefiniteLength_NoPatching_HappyPath(object[] values, string expectedHexEncoding)
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
Expand Down Expand Up @@ -82,7 +82,7 @@ public static void WriteArray_IndefiniteLength_NoPatching_NestedValues_HappyPath
[InlineData(new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }, "98190102030405060708090a0b0c0d0e0f101112131415161718181819")]
[InlineData(new object[] { 1, -1, "", new byte[] { 7 } }, "840120604107")]
[InlineData(new object[] { "lorem", "ipsum", "dolor" }, "83656c6f72656d65697073756d65646f6c6f72")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "84f4f6f9fe00f97c00")]
[InlineData(new object?[] { false, null, float.NaN, double.PositiveInfinity }, "84f4f6f97e00f97c00")]
public static void WriteArray_IndefiniteLength_WithPatching_HappyPath(object[] values, string expectedHexEncoding)
{
byte[] expectedEncoding = expectedHexEncoding.HexToByteArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public partial class CborWriterTests
[InlineData(3.4028234663852886e+38, "fa7f7fffff")]
[InlineData(float.PositiveInfinity, "f97c00")]
[InlineData(float.NegativeInfinity, "f9fc00")]
[InlineData(float.NaN, "f9fe00")]
[InlineData(float.NaN, "f97e00")]
public static void WriteSingle_SingleValue_HappyPath(float input, string hexExpectedEncoding)
{
byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
Expand All @@ -26,9 +26,9 @@ public static void WriteSingle_SingleValue_HappyPath(float input, string hexExpe
}

[Theory]
[InlineData(float.NaN, "f9fe00", CborConformanceMode.Lax)]
[InlineData(float.NaN, "f9fe00", CborConformanceMode.Strict)]
[InlineData(float.NaN, "f9fe00", CborConformanceMode.Canonical)]
[InlineData(float.NaN, "f97e00", CborConformanceMode.Lax)]
[InlineData(float.NaN, "f97e00", CborConformanceMode.Strict)]
[InlineData(float.NaN, "f97e00", CborConformanceMode.Canonical)]
public static void WriteSingle_NonCtapConformance_ShouldMinimizePrecision(float input, string hexExpectedEncoding, CborConformanceMode mode)
{
byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
Expand All @@ -42,7 +42,7 @@ public static void WriteSingle_NonCtapConformance_ShouldMinimizePrecision(float
[InlineData(3.4028234663852886e+38, "fa7f7fffff")]
[InlineData(float.PositiveInfinity, "fa7f800000")]
[InlineData(float.NegativeInfinity, "faff800000")]
[InlineData(float.NaN, "faffc00000")]
[InlineData(float.NaN, "fa7fc00000")]
public static void WriteSingle_Ctap2Conformance_ShouldPreservePrecision(float input, string hexExpectedEncoding)
{
byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
Expand All @@ -58,7 +58,7 @@ public static void WriteSingle_Ctap2Conformance_ShouldPreservePrecision(float in
[InlineData(3.1415926, "fb400921fb4d12d84a")]
[InlineData(double.PositiveInfinity, "f97c00")]
[InlineData(double.NegativeInfinity, "f9fc00")]
[InlineData(double.NaN, "f9fe00")]
[InlineData(double.NaN, "f97e00")]
public static void WriteDouble_SingleValue_HappyPath(double input, string hexExpectedEncoding)
{
byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
Expand All @@ -68,9 +68,9 @@ public static void WriteDouble_SingleValue_HappyPath(double input, string hexExp
}

[Theory]
[InlineData(double.NaN, "f9fe00", CborConformanceMode.Lax)]
[InlineData(double.NaN, "f9fe00", CborConformanceMode.Strict)]
[InlineData(double.NaN, "f9fe00", CborConformanceMode.Canonical)]
[InlineData(double.NaN, "f97e00", CborConformanceMode.Lax)]
[InlineData(double.NaN, "f97e00", CborConformanceMode.Strict)]
[InlineData(double.NaN, "f97e00", CborConformanceMode.Canonical)]
[InlineData(65505, "fa477fe100", CborConformanceMode.Lax)]
[InlineData(65505, "fa477fe100", CborConformanceMode.Strict)]
[InlineData(65505, "fa477fe100", CborConformanceMode.Canonical)]
Expand All @@ -89,7 +89,7 @@ public static void WriteDouble_NonCtapConformance_ShouldMinimizePrecision(double
[InlineData(3.1415926, "fb400921fb4d12d84a")]
[InlineData(double.PositiveInfinity, "fb7ff0000000000000")]
[InlineData(double.NegativeInfinity, "fbfff0000000000000")]
[InlineData(double.NaN, "fbfff8000000000000")]
[InlineData(double.NaN, "fb7ff8000000000000")]
public static void WriteDouble_Ctap2Conformance_ShouldPreservePrecision(double input, string hexExpectedEncoding)
{
byte[] expectedEncoding = hexExpectedEncoding.HexToByteArray();
Expand Down

0 comments on commit 5b6f257

Please sign in to comment.