Fixing up the System.Text.Json to handle floats/doubles according to the underlying...
authorTanner Gooding <tagoo@outlook.com>
Mon, 1 Jul 2019 20:06:12 +0000 (13:06 -0700)
committerGitHub <noreply@github.com>
Mon, 1 Jul 2019 20:06:12 +0000 (13:06 -0700)
* Fixing some doc comments and adjusting Utf8JsonWriter to try and roundtrip float/double on .NET Framework.

* Fixing up various tests to handle framework vs core differences for float/double

* Changing the Utf8JsonWriter to replicate the .NET Core 3.0 Utf8Formatter.TryFormat method.

* Modifying some tests to enforce netfx and netcore test the same things.

* Fixing up some code comments to clarify .NET Core 3.0 or higher vs anything else.

* Adding code comments explaining why we duplicate the netcoreapp3.0 Utf8Formatter logic.

* Removing three usages of var.

* Responding to more PR feedback.

* ifdef out the ISpanFormattable parts of the TryFormatDouble and TryFormatSingle shims for netfx

* Removing an unnecessary using that was added during a rebase.

* Changing some RangePassFloatingPoint scenarios to epect JsonException rather than FormatException

* Removing dependency on Span from the TryFormatSingle/TryFormatDouble fallback paths

* Resolving some more PR feedback.

Commit migrated from https://github.com/dotnet/corefx/commit/ea33aae808f5f7d26109db5571640d1c4a9ee700

src/libraries/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.TryGet.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs
src/libraries/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs
src/libraries/System.Text.Json/tests/JsonDocumentTests.cs
src/libraries/System.Text.Json/tests/JsonElementWriteTests.cs
src/libraries/System.Text.Json/tests/JsonNumberTestData.cs
src/libraries/System.Text.Json/tests/JsonTestHelper.cs
src/libraries/System.Text.Json/tests/Serialization/Value.ReadTests.cs
src/libraries/System.Text.Json/tests/Utf8JsonReaderTests.TryGet.cs

index a9c0e39..b6a8484 100644 (file)
@@ -311,7 +311,7 @@ namespace System.Text.Json
         /// <seealso cref="TokenType" />
         /// </exception>
         /// <exception cref="FormatException">
-        /// Thrown if the JSON token value represents a number less than <see cref="float.MinValue"/> or greater 
+        /// On any framework that is not .NET Core 3.0 or higher, thrown if the JSON token value represents a number less than <see cref="float.MinValue"/> or greater 
         /// than <see cref="float.MaxValue"/>.
         /// </exception>
         public float GetSingle()
@@ -334,7 +334,7 @@ namespace System.Text.Json
         /// <seealso cref="TokenType" />
         /// </exception>
         /// <exception cref="FormatException">
-        /// Thrown if the JSON token value represents a number less than <see cref="double.MinValue"/> or greater 
+        /// On any framework that is not .NET Core 3.0 or higher, thrown if the JSON token value represents a number less than <see cref="double.MinValue"/> or greater 
         /// than <see cref="double.MaxValue"/>.
         /// </exception>
         public double GetDouble()
index 432247f..be4f952 100644 (file)
@@ -5,6 +5,7 @@
 using System.Buffers;
 using System.Buffers.Text;
 using System.Diagnostics;
+using System.Globalization;
 
 namespace System.Text.Json
 {
@@ -18,7 +19,8 @@ namespace System.Text.Json
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
         /// <remarks>
-        /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
+        /// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> on .NET Core 3 or higher
+        /// and 'G17' on any other framework.
         /// </remarks>
         public void WriteNumberValue(double value)
         {
@@ -88,9 +90,55 @@ namespace System.Text.Json
                 BytesPending += indent;
             }
 
-            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            bool result = TryFormatDouble(value, output.Slice(BytesPending), out int bytesWritten);
             Debug.Assert(result);
             BytesPending += bytesWritten;
         }
+
+        private static bool TryFormatDouble(double value, Span<byte> destination, out int bytesWritten)
+        {
+            // Frameworks that are not .NET Core 3.0 or higher do not produce roundtrippable strings by
+            // default. Further, the Utf8Formatter on older frameworks does not support taking a precision
+            // specifier for 'G' nor does it represent other formats such as 'R'. As such, we duplicate
+            // the .NET Core 3.0 logic of forwarding to the UTF16 formatter and transcoding it back to UTF8,
+            // with some additional changes to remove dependencies on Span APIs which don't exist downlevel.
+
+#if BUILDING_INBOX_LIBRARY
+            return Utf8Formatter.TryFormat(value, destination, out bytesWritten);
+#else
+            const string FormatString = "G17";
+
+            string utf16Text = value.ToString(FormatString, CultureInfo.InvariantCulture);
+
+            // Copy the value to the destination, if it's large enough.
+
+            if (utf16Text.Length > destination.Length)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            try
+            {
+                byte[] bytes = Encoding.UTF8.GetBytes(utf16Text);
+
+                if (bytes.Length > destination.Length)
+                {
+                    bytesWritten = 0;
+                    return false;
+                }
+
+                bytes.CopyTo(destination);
+                bytesWritten = bytes.Length;
+
+                return true;
+            }
+            catch
+            {
+                bytesWritten = 0;
+                return false;
+            }
+#endif
+        }
     }
 }
index 23fcc73..186f163 100644 (file)
@@ -5,6 +5,7 @@
 using System.Buffers;
 using System.Buffers.Text;
 using System.Diagnostics;
+using System.Globalization;
 
 namespace System.Text.Json
 {
@@ -18,7 +19,8 @@ namespace System.Text.Json
         /// Thrown if this would result in an invalid JSON to be written (while validation is enabled).
         /// </exception>
         /// <remarks>
-        /// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> (i.e. 'G').
+        /// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> on .NET Core 3 or higher
+        /// and 'G9' on any other framework.
         /// </remarks>
         public void WriteNumberValue(float value)
         {
@@ -88,9 +90,55 @@ namespace System.Text.Json
                 BytesPending += indent;
             }
 
-            bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten);
+            bool result = TryFormatSingle(value, output.Slice(BytesPending), out int bytesWritten);
             Debug.Assert(result);
             BytesPending += bytesWritten;
         }
+
+        private static bool TryFormatSingle(float value, Span<byte> destination, out int bytesWritten)
+        {
+            // Frameworks that are not .NET Core 3.0 or higher do not produce roundtrippable strings by
+            // default. Further, the Utf8Formatter on older frameworks does not support taking a precision
+            // specifier for 'G' nor does it represent other formats such as 'R'. As such, we duplicate
+            // the .NET Core 3.0 logic of forwarding to the UTF16 formatter and transcoding it back to UTF8,
+            // with some additional changes to remove dependencies on Span APIs which don't exist downlevel.
+
+#if BUILDING_INBOX_LIBRARY
+            return Utf8Formatter.TryFormat(value, destination, out bytesWritten);
+#else
+            const string FormatString = "G9";
+
+            string utf16Text = value.ToString(FormatString, CultureInfo.InvariantCulture);
+
+            // Copy the value to the destination, if it's large enough.
+
+            if (utf16Text.Length > destination.Length)
+            {
+                bytesWritten = 0;
+                return false;
+            }
+
+            try
+            {
+                byte[] bytes = Encoding.UTF8.GetBytes(utf16Text);
+
+                if (bytes.Length > destination.Length)
+                {
+                    bytesWritten = 0;
+                    return false;
+                }
+
+                bytes.CopyTo(destination);
+                bytesWritten = bytes.Length;
+
+                return true;
+            }
+            catch
+            {
+                bytesWritten = 0;
+                return false;
+            }
+#endif
+        }
     }
 }
index 60d372d..2ba3006 100644 (file)
@@ -1539,8 +1539,6 @@ namespace System.Text.Json.Tests
         [Fact]
         public static void ReadTooPreciseDouble()
         {
-            // If https://github.com/dotnet/corefx/issues/33997 gets resolved as the reader throwing,
-            // this test would need to expect FormatException from GetDouble, and false from TryGet.
             using (JsonDocument doc = JsonDocument.Parse("    1e+100000002"))
             {
                 JsonElement root = doc.RootElement;
@@ -1631,8 +1629,6 @@ namespace System.Text.Json.Tests
         [Fact]
         public static void ReadArrayWithComments()
         {
-            // If https://github.com/dotnet/corefx/issues/33997 gets resolved as the reader throwing,
-            // this test would need to expect FormatException from GetDouble, and false from TryGet.
             var options = new JsonDocumentOptions
             {
                 CommentHandling = JsonCommentHandling.Skip,
index 232da36..7866ef6 100644 (file)
@@ -50,7 +50,7 @@ namespace System.Text.Json.Tests
             // confirm the printing precision of double, like
             //
             //double precisePi = double.Parse(PrecisePi);
-            //Assert.NotEqual(PrecisePi, precisePi.ToString("R"));
+            //Assert.NotEqual(PrecisePi, precisePi.ToString(JsonTestHelper.DoubleFormatString));
 
             WriteSimpleValue(indented, PrecisePi);
         }
@@ -64,12 +64,10 @@ namespace System.Text.Json.Tests
             // https://tools.ietf.org/html/rfc7159#section-6
             const string OneQuarticGoogol = "1e400";
 
-            // To confirm that this test is doing what it intends, one could
-            // confirm the printing precision of double, like
-            //
-            //double oneQuarticGoogol = double.Parse(OneQuarticGoogol);
-            //Assert.NotEqual(OneQuarticGoogol, oneQuarticGoogol.ToString("R"));
-
+            // This just validates we write the literal number 1e400 even though it is too
+            // large to be represented by System.Double and would be converted to
+            // PositiveInfinity instead (or throw if using double.Parse on frameworks
+            // older than .NET Core 3.0).
             WriteSimpleValue(indented, OneQuarticGoogol);
         }
 
index f39e2ee..6c4a4c6 100644 (file)
@@ -175,8 +175,8 @@ namespace System.Text.Json.Tests
                 0.000,
                 1.1234e1,
                 -1.1234e1,
-                1.79769313486231E+308,  // double.MaxValue doesn't round trip
-                -1.79769313486231E+308  // double.MinValue doesn't round trip
+                double.MaxValue,
+                double.MinValue
             };
             for (int i = 0; i < numberOfItems / 2; i++)
             {
@@ -275,19 +275,22 @@ namespace System.Text.Json.Tests
             {
                 // Use InvariantCulture to format the numbers to make sure they retain the decimal point '.'
                 builder.Append("\"double").Append(i).Append("\": ");
-                var str = string.Format(CultureInfo.InvariantCulture, "{0}, ", Doubles[i]);
+                const string Format = "{0:" + JsonTestHelper.DoubleFormatString + "}, ";
+                string str = string.Format(CultureInfo.InvariantCulture, Format, Doubles[i]);
                 builder.AppendFormat(CultureInfo.InvariantCulture, "{0}", str);
             }
             for (int i = 0; i < Floats.Count; i++)
             {
+                // Use InvariantCulture to format the numbers to make sure they retain the decimal point '.'
                 builder.Append("\"float").Append(i).Append("\": ");
-                var str = string.Format(CultureInfo.InvariantCulture, "{0}, ", Floats[i]);
+                const string Format = "{0:" + JsonTestHelper.SingleFormatString + "}, ";
+                string str = string.Format(CultureInfo.InvariantCulture, Format, Floats[i]);
                 builder.AppendFormat(CultureInfo.InvariantCulture, "{0}", str);
             }
             for (int i = 0; i < Decimals.Count; i++)
             {
                 builder.Append("\"decimal").Append(i).Append("\": ");
-                var str = string.Format(CultureInfo.InvariantCulture, "{0}, ", Decimals[i]);
+                string str = string.Format(CultureInfo.InvariantCulture, "{0}, ", Decimals[i]);
                 builder.AppendFormat(CultureInfo.InvariantCulture, "{0}", str);
             }
 
@@ -297,6 +300,5 @@ namespace System.Text.Json.Tests
             string jsonString = builder.ToString();
             JsonData = Encoding.UTF8.GetBytes(jsonString);
         }
-
     }
 }
index 490e377..2c06356 100644 (file)
@@ -17,6 +17,14 @@ namespace System.Text.Json
 {
     internal static class JsonTestHelper
     {
+#if BUILDING_INBOX_LIBRARY
+        public const string DoubleFormatString = null;
+        public const string SingleFormatString = null;
+#else
+        public const string DoubleFormatString = "G17";
+        public const string SingleFormatString = "G9";
+#endif
+
         public static string NewtonsoftReturnStringHelper(TextReader reader)
         {
             var sb = new StringBuilder();
index f49269c..2f6a234 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Globalization;
+using System.Runtime.CompilerServices;
 using Xunit;
 
 namespace System.Text.Json.Serialization.Tests
@@ -203,41 +204,34 @@ namespace System.Text.Json.Serialization.Tests
         }
 
         [Fact]
-        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Skipped since NETFX has different semantics and bugs with floating point.")]
         public static void RangePassFloatingPoint()
         {
             // Verify overflow\underflow.
-            // On NETFX these throw.
-            Assert.True(float.IsNegativeInfinity(JsonSerializer.Deserialize<float>(double.MinValue.ToString(CultureInfo.InvariantCulture))));
-            Assert.True(float.IsPositiveInfinity(JsonSerializer.Deserialize<float>(double.MaxValue.ToString(CultureInfo.InvariantCulture))));
-            Assert.True(float.IsNegativeInfinity(JsonSerializer.Deserialize<float?>(double.MinValue.ToString(CultureInfo.InvariantCulture)).Value));
-            Assert.True(float.IsPositiveInfinity(JsonSerializer.Deserialize<float?>(double.MaxValue.ToString(CultureInfo.InvariantCulture)).Value));
+            AssertFloatingPointBehavior(netcoreExpectedValue: float.NegativeInfinity, () => JsonSerializer.Deserialize<float>(float.MinValue.ToString(CultureInfo.InvariantCulture) + "0"));
+            AssertFloatingPointBehavior(netcoreExpectedValue: float.PositiveInfinity, () => JsonSerializer.Deserialize<float>(float.MaxValue.ToString(CultureInfo.InvariantCulture) + "0"));
+            AssertFloatingPointBehavior(netcoreExpectedValue: float.NegativeInfinity, () => JsonSerializer.Deserialize<float?>(float.MinValue.ToString(CultureInfo.InvariantCulture) + "0").Value);
+            AssertFloatingPointBehavior(netcoreExpectedValue: float.PositiveInfinity, () => JsonSerializer.Deserialize<float?>(float.MaxValue.ToString(CultureInfo.InvariantCulture) + "0").Value);
 
-            Assert.True(double.IsNegativeInfinity(JsonSerializer.Deserialize<double>(double.MinValue.ToString(CultureInfo.InvariantCulture) + "0")));
-            Assert.True(double.IsPositiveInfinity(JsonSerializer.Deserialize<double>(double.MaxValue.ToString(CultureInfo.InvariantCulture) + "0")));
-            Assert.True(double.IsNegativeInfinity(JsonSerializer.Deserialize<double?>(double.MinValue.ToString(CultureInfo.InvariantCulture) + "0").Value));
-            Assert.True(double.IsPositiveInfinity(JsonSerializer.Deserialize<double?>(double.MaxValue.ToString(CultureInfo.InvariantCulture) + "0").Value));
+            AssertFloatingPointBehavior(netcoreExpectedValue: double.NegativeInfinity, () => JsonSerializer.Deserialize<double>(double.MinValue.ToString(CultureInfo.InvariantCulture) + "0"));
+            AssertFloatingPointBehavior(netcoreExpectedValue: double.PositiveInfinity, () => JsonSerializer.Deserialize<double>(double.MaxValue.ToString(CultureInfo.InvariantCulture) + "0"));
+            AssertFloatingPointBehavior(netcoreExpectedValue: double.NegativeInfinity, () => JsonSerializer.Deserialize<double?>(double.MinValue.ToString(CultureInfo.InvariantCulture) + "0").Value);
+            AssertFloatingPointBehavior(netcoreExpectedValue: double.PositiveInfinity, () => JsonSerializer.Deserialize<double?>(double.MaxValue.ToString(CultureInfo.InvariantCulture) + "0").Value);
 
             // Verify sign is correct.
-            // On NETFX a value of -0 does not keep the sign.
-            Assert.Equal(0x0000000000000000ul, (ulong)BitConverter.DoubleToInt64Bits(JsonSerializer.Deserialize<double>("0")));
-            Assert.Equal(0x8000000000000000ul, (ulong)BitConverter.DoubleToInt64Bits(JsonSerializer.Deserialize<double>("-0")));
-            Assert.Equal(0x8000000000000000ul, (ulong)BitConverter.DoubleToInt64Bits(JsonSerializer.Deserialize<double>("-0.0")));
+            AssertFloatingPointBehavior(netfxExpectedValue: 0x00000000u, netcoreExpectedValue: 0x00000000u, () => (uint)SingleToInt32Bits(JsonSerializer.Deserialize<float>("0")));
+            AssertFloatingPointBehavior(netfxExpectedValue: 0x00000000u, netcoreExpectedValue: 0x80000000u, () => (uint)SingleToInt32Bits(JsonSerializer.Deserialize<float>("-0")));
+            AssertFloatingPointBehavior(netfxExpectedValue: 0x00000000u, netcoreExpectedValue: 0x80000000u, () => (uint)SingleToInt32Bits(JsonSerializer.Deserialize<float>("-0.0")));
 
-#if BUILDING_INBOX_LIBRARY
-            // Verify sign is correct; SingleToInt32Bits not available on netfx.
-            Assert.Equal(0x00000000u, (uint)BitConverter.SingleToInt32Bits(JsonSerializer.Deserialize<float>("0")));
-            Assert.Equal(0x80000000u, (uint)BitConverter.SingleToInt32Bits(JsonSerializer.Deserialize<float>("-0")));
-            Assert.Equal(0x80000000u, (uint)BitConverter.SingleToInt32Bits(JsonSerializer.Deserialize<float>("-0.0")));
-#endif
+            AssertFloatingPointBehavior(netfxExpectedValue: 0x0000000000000000ul, netcoreExpectedValue: 0x0000000000000000ul, () => (ulong)BitConverter.DoubleToInt64Bits(JsonSerializer.Deserialize<double>("0")));
+            AssertFloatingPointBehavior(netfxExpectedValue: 0x0000000000000000ul, netcoreExpectedValue: 0x8000000000000000ul, () => (ulong)BitConverter.DoubleToInt64Bits(JsonSerializer.Deserialize<double>("-0")));
+            AssertFloatingPointBehavior(netfxExpectedValue: 0x0000000000000000ul, netcoreExpectedValue: 0x8000000000000000ul, () => (ulong)BitConverter.DoubleToInt64Bits(JsonSerializer.Deserialize<double>("-0.0")));
 
             // Verify Round-tripping.
-            // On NETFX round tripping is not supported.
-            Assert.Equal(float.MaxValue, JsonSerializer.Deserialize<float>(float.MaxValue.ToString(CultureInfo.InvariantCulture)));
-            Assert.Equal(float.MaxValue, JsonSerializer.Deserialize<float?>(float.MaxValue.ToString(CultureInfo.InvariantCulture)));
+            Assert.Equal(float.MaxValue, JsonSerializer.Deserialize<float>(float.MaxValue.ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture)));
+            Assert.Equal(float.MaxValue, JsonSerializer.Deserialize<float?>(float.MaxValue.ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture)));
 
-            Assert.Equal(double.MaxValue, JsonSerializer.Deserialize<double>(double.MaxValue.ToString(CultureInfo.InvariantCulture)));
-            Assert.Equal(double.MaxValue, JsonSerializer.Deserialize<double?>(double.MaxValue.ToString(CultureInfo.InvariantCulture)));
+            Assert.Equal(double.MaxValue, JsonSerializer.Deserialize<double>(double.MaxValue.ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture)));
+            Assert.Equal(double.MaxValue, JsonSerializer.Deserialize<double?>(double.MaxValue.ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture)));
         }
 
         [Fact]
@@ -301,5 +295,38 @@ namespace System.Text.Json.Serialization.Tests
             uri = JsonSerializer.Deserialize<Uri>(@"""~/path""");
             Assert.Equal("~/path", uri.ToString());
         }
-   }
+
+        private static int SingleToInt32Bits(float value)
+        {
+#if BUILDING_INBOX_LIBRARY
+            return BitConverter.SingleToInt32Bits(value);
+#else
+            return Unsafe.As<float, int>(ref value);
+#endif
+        }
+
+        private static void AssertFloatingPointBehavior<T>(T netcoreExpectedValue, Func<T> testCode)
+        {
+            if (PlatformDetection.IsFullFramework)
+            {
+                Assert.Throws<JsonException>(() => testCode());
+            }
+            else
+            {
+                Assert.Equal(netcoreExpectedValue, testCode());
+            }
+        }
+
+        private static void AssertFloatingPointBehavior<T>(T netfxExpectedValue, T netcoreExpectedValue, Func<T> testCode)
+        {
+            if (PlatformDetection.IsFullFramework)
+            {
+                Assert.Equal(netfxExpectedValue, testCode());
+            }
+            else
+            {
+                Assert.Equal(netcoreExpectedValue, testCode());
+            }
+        }
+    }
 }
index ecb9383..7e70b7d 100644 (file)
@@ -111,10 +111,13 @@ namespace System.Text.Json.Tests
                         if (count >= floats.Count)
                             count = 0;
 
-                        var str = string.Format(CultureInfo.InvariantCulture, "{0}", floats[count]);
-                        float expected = float.Parse(str, CultureInfo.InvariantCulture);
+                        string roundTripActual = numberFloat.ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture);
+                        float actual = float.Parse(roundTripActual, CultureInfo.InvariantCulture);
 
-                        Assert.Equal(expected, numberFloat);
+                        string roundTripExpected = floats[count].ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture);
+                        float expected = float.Parse(roundTripExpected, CultureInfo.InvariantCulture);
+
+                        Assert.Equal(expected, actual);
                         count++;
                     }
                     else if (key.StartsWith("double"))
@@ -123,23 +126,13 @@ namespace System.Text.Json.Tests
                         if (count >= doubles.Count)
                             count = 0;
 
-                        string roundTripActual = numberDouble.ToString("R", CultureInfo.InvariantCulture);
+                        string roundTripActual = numberDouble.ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture);
                         double actual = double.Parse(roundTripActual, CultureInfo.InvariantCulture);
 
-                        string roundTripExpected = doubles[count].ToString("R", CultureInfo.InvariantCulture);
+                        string roundTripExpected = doubles[count].ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture);
                         double expected = double.Parse(roundTripExpected, CultureInfo.InvariantCulture);
 
-                        // Temporary work around for precision/round-tripping issues with Utf8Parser
-                        // https://github.com/dotnet/corefx/issues/33360
-                        if (expected != actual)
-                        {
-                            double diff = Math.Abs(expected - actual);
-                            Assert.True(diff < 1E-9 || diff > 1E288);
-                        }
-                        else
-                        {
-                            Assert.Equal(expected, actual);
-                        }
+                        Assert.Equal(expected, actual);
                         count++;
                     }
                     else if (key.StartsWith("decimal"))
@@ -247,10 +240,13 @@ namespace System.Text.Json.Tests
                         if (count >= floats.Count)
                             count = 0;
 
-                        var str = string.Format(CultureInfo.InvariantCulture, "{0}", floats[count]);
-                        float expected = float.Parse(str, CultureInfo.InvariantCulture);
+                        string roundTripActual = json.GetSingle().ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture);
+                        float actual = float.Parse(roundTripActual, CultureInfo.InvariantCulture);
 
-                        Assert.Equal(expected, json.GetSingle());
+                        string roundTripExpected = floats[count].ToString(JsonTestHelper.SingleFormatString, CultureInfo.InvariantCulture);
+                        float expected = float.Parse(roundTripExpected, CultureInfo.InvariantCulture);
+
+                        Assert.Equal(expected, actual);
                         count++;
                     }
                     else if (key.StartsWith("double"))
@@ -258,23 +254,13 @@ namespace System.Text.Json.Tests
                         if (count >= doubles.Count)
                             count = 0;
 
-                        string roundTripActual = json.GetDouble().ToString("R", CultureInfo.InvariantCulture);
+                        string roundTripActual = json.GetDouble().ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture);
                         double actual = double.Parse(roundTripActual, CultureInfo.InvariantCulture);
 
-                        string roundTripExpected = doubles[count].ToString("R", CultureInfo.InvariantCulture);
+                        string roundTripExpected = doubles[count].ToString(JsonTestHelper.DoubleFormatString, CultureInfo.InvariantCulture);
                         double expected = double.Parse(roundTripExpected, CultureInfo.InvariantCulture);
 
-                        // Temporary work around for precision/round-tripping issues with Utf8Parser
-                        // https://github.com/dotnet/corefx/issues/33360
-                        if (expected != actual)
-                        {
-                            double diff = Math.Abs(expected - actual);
-                            Assert.True(diff < 1E-9 || diff > 1E288);
-                        }
-                        else
-                        {
-                            Assert.Equal(expected, actual);
-                        }
+                        Assert.Equal(expected, actual);
                         count++;
                     }
                     else if (key.StartsWith("decimal"))
@@ -614,7 +600,6 @@ namespace System.Text.Json.Tests
         [Theory]
         [InlineData("-4.402823E+38", float.NegativeInfinity, -4.402823E+38)] // float.MinValue - 1
         [InlineData("4.402823E+38", float.PositiveInfinity, 4.402823E+38)]  // float.MaxValue + 1
-        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Utf8Parser does not support parsing really large float and double values to infinity.")]
         public static void TestingTooLargeSingleConversionToInfinity(string jsonString, float expectedFloat, double expectedDouble)
         {
             byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
@@ -624,12 +609,33 @@ namespace System.Text.Json.Tests
             {
                 if (json.TokenType == JsonTokenType.Number)
                 {
-                    Assert.True(json.TryGetSingle(out float floatValue));
-                    Assert.Equal(expectedFloat, floatValue);
+                    if (PlatformDetection.IsFullFramework)
+                    {
+                        // Full framework throws for overflow rather than returning Infinity
+                        // This was fixed for .NET Core 3.0 in order to be IEEE 754 compliant
+
+                        Assert.False(json.TryGetSingle(out float _));
+
+                        try
+                        {
+                            json.GetSingle();
+                            Assert.True(false, $"Expected {nameof(FormatException)}.");
+                        }
+                        catch (FormatException)
+                        {
+                            /* Expected exception */
+                        }
+                    }
+                    else
+                    {
+                        Assert.True(json.TryGetSingle(out float floatValue));
+                        Assert.Equal(expectedFloat, floatValue);
+
+                        Assert.Equal(expectedFloat, json.GetSingle());
+                    }
+
                     Assert.True(json.TryGetDouble(out double doubleValue));
                     Assert.Equal(expectedDouble, doubleValue);
-
-                    Assert.Equal(expectedFloat, json.GetSingle());
                     Assert.Equal(expectedDouble, json.GetDouble());
                 }
             }
@@ -640,7 +646,6 @@ namespace System.Text.Json.Tests
         [Theory]
         [InlineData("-2.79769313486232E+308", double.NegativeInfinity)] // double.MinValue - 1
         [InlineData("2.79769313486232E+308", double.PositiveInfinity)]  // double.MaxValue + 1
-        [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Utf8Parser does not support parsing really large float and double values to infinity.")]
         public static void TestingTooLargeDoubleConversionToInfinity(string jsonString, double expected)
         {
             byte[] dataUtf8 = Encoding.UTF8.GetBytes(jsonString);
@@ -650,10 +655,30 @@ namespace System.Text.Json.Tests
             {
                 if (json.TokenType == JsonTokenType.Number)
                 {
-                    Assert.True(json.TryGetDouble(out double actual));
-                    Assert.Equal(expected, actual);
+                    if (PlatformDetection.IsFullFramework)
+                    {
+                        // Full framework throws for overflow rather than returning Infinity
+                        // This was fixed for .NET Core 3.0 in order to be IEEE 754 compliant
+
+                        Assert.False(json.TryGetDouble(out double _));
 
-                    Assert.Equal(expected, json.GetDouble());
+                        try
+                        {
+                            json.GetDouble();
+                            Assert.True(false, $"Expected {nameof(FormatException)}.");
+                        }
+                        catch (FormatException)
+                        {
+                            /* Expected exception */
+                        }
+                    }
+                    else
+                    {
+                        Assert.True(json.TryGetDouble(out double actual));
+                        Assert.Equal(expected, actual);
+
+                        Assert.Equal(expected, json.GetDouble());
+                    }
                 }
             }