From 0f09aa9c4414f4201685c7a71866ede53a9f4e30 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Feb 2021 10:48:01 -0800 Subject: [PATCH 1/7] Track trailing zeros only for floating point numbers --- .../System.Private.CoreLib/src/System/Number.Parsing.cs | 4 ++-- src/libraries/System.Runtime/tests/System/Int32Tests.cs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index a07153cad25eca..eb9e7e0837afdc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -358,7 +358,7 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty { number.Scale++; } - else if (digCount < maxDigCount) + else if (digCount < maxDigCount && number.Kind == NumberBufferKind.FloatingPoint) { // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part, so we keep a count of the number of trailing zeros and update digCount later if (ch == '0') @@ -402,7 +402,7 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty } else { - // We have trailing 0s in the fractional part. We can strip it all out + // If we have trailing 0s in the fractional part, we can strip it out number.DigitsCount = digEnd - numberOfTrailingZeros; } number.Digits[number.DigitsCount] = (byte)('\0'); diff --git a/src/libraries/System.Runtime/tests/System/Int32Tests.cs b/src/libraries/System.Runtime/tests/System/Int32Tests.cs index f64df4bcc31035..fca800ca2921ff 100644 --- a/src/libraries/System.Runtime/tests/System/Int32Tests.cs +++ b/src/libraries/System.Runtime/tests/System/Int32Tests.cs @@ -355,6 +355,12 @@ public static IEnumerable Parse_Valid_TestData() yield return new object[] { "123123", NumberStyles.AllowLeadingSign, new NumberFormatInfo() { NegativeSign = "123" }, -123 }; yield return new object[] { "123123", NumberStyles.AllowLeadingSign, new NumberFormatInfo() { PositiveSign = "12312" }, 3 }; yield return new object[] { "123123", NumberStyles.AllowLeadingSign, new NumberFormatInfo() { NegativeSign = "12312" }, -3 }; + + // Test trailing zeros + yield return new object[] { "3.00", NumberStyles.Number, CultureInfo.InvariantCulture, 3 }; + yield return new object[] { "3.00000000", NumberStyles.Number, CultureInfo.InvariantCulture, 3 }; + yield return new object[] { "3.000000000", NumberStyles.Number, CultureInfo.InvariantCulture, 3 }; + yield return new object[] { "3.0000000000", NumberStyles.Number, CultureInfo.InvariantCulture, 3 }; } [Theory] From c4252fa98785f1ab7fe5b343182cc79064a1516d Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Feb 2021 12:27:04 -0800 Subject: [PATCH 2/7] Undo previous unit test change --- .../System.Formats.Cbor/tests/Writer/CborWriterTests.Tag.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.Tag.cs b/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.Tag.cs index 88cb2f0ee95c2f..b9cc53ceb237b1 100644 --- a/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.Tag.cs +++ b/src/libraries/System.Formats.Cbor/tests/Writer/CborWriterTests.Tag.cs @@ -158,7 +158,7 @@ public static void WriteInteger_SingleValue_HappyPath(string valueString, string [InlineData("1", "c4820001")] [InlineData("-1", "c4820020")] [InlineData("1.1", "c482200b")] - [InlineData("1.000", "c4820001")] + [InlineData("1.000", "c482221903e8")] [InlineData("273.15", "c48221196ab3")] [InlineData("79228162514264337593543950335", "c48200c24cffffffffffffffffffffffff")] // decimal.MaxValue [InlineData("7922816251426433759354395033.5", "c48220c24cffffffffffffffffffffffff")] From 032d6d88ec72fb4233b00b7ea7c49a2f62c553d8 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Feb 2021 13:11:15 -0800 Subject: [PATCH 3/7] Add a roundtrip unit test --- src/libraries/System.Runtime/tests/System/DecimalTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index e4f3c9d8012924..06335b5dce842c 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -1500,6 +1500,14 @@ public static void ToString_InvalidFormat_ThrowsFormatException() Assert.Throws(() => f.ToString("E" + intMaxPlus1String)); } + [Theory] + [InlineData("3.00")] + public void TestRoundTripDecimalToString(string input) + { + decimal d = Decimal.Parse(input); + string dString = d.ToString(); + Assert.Equal(input, dString); + } public static IEnumerable Truncate_TestData() { yield return new object[] { 123m, 123m }; From a6f41aa8a802659d7b2998bb437d47f46dacfe02 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Feb 2021 15:16:40 -0800 Subject: [PATCH 4/7] Move check outside the loop --- .../System.Private.CoreLib/src/System/Number.Parsing.cs | 9 +++++++-- .../System.Runtime/tests/System/DecimalTests.cs | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index eb9e7e0837afdc..03e5504a3e1072 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -358,7 +358,7 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty { number.Scale++; } - else if (digCount < maxDigCount && number.Kind == NumberBufferKind.FloatingPoint) + else if (digCount < maxDigCount) { // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part, so we keep a count of the number of trailing zeros and update digCount later if (ch == '0') @@ -395,6 +395,11 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty } bool negExp = false; + if (number.Kind != NumberBufferKind.FloatingPoint) + { + numberOfTrailingZeros = 0; + } + if (digEnd == maxDigCount && numberOfTrailingZeros > 0 && number.HasNonZeroTail) { // We have a non-zero digit in the input past maxDigitCount. We already handle this properly. @@ -2025,7 +2030,7 @@ private static bool TrailingZeros(ReadOnlySpan value, int index) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static unsafe char* MatchNegativeSignChars(char* p, char* pEnd, NumberFormatInfo info) { - char *ret = MatchChars(p, pEnd, info.NegativeSign); + char* ret = MatchChars(p, pEnd, info.NegativeSign); if (ret == null && info.AllowHyphenDuringParsing && p < pEnd && *p == '-') { ret = p + 1; diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index 06335b5dce842c..67edcbb30a5b47 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -1504,7 +1504,7 @@ public static void ToString_InvalidFormat_ThrowsFormatException() [InlineData("3.00")] public void TestRoundTripDecimalToString(string input) { - decimal d = Decimal.Parse(input); + decimal d = Decimal.Parse(input, NumberStyles.Number, NumberFormatInfo.InvariantInfo); string dString = d.ToString(); Assert.Equal(input, dString); } From 1e6ea43018fbfca52306be9b43b0d58d81a8256f Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Mon, 22 Feb 2021 17:19:00 -0800 Subject: [PATCH 5/7] Globalization --- src/libraries/System.Runtime/tests/System/DecimalTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/DecimalTests.cs b/src/libraries/System.Runtime/tests/System/DecimalTests.cs index 67edcbb30a5b47..35069a81e61611 100644 --- a/src/libraries/System.Runtime/tests/System/DecimalTests.cs +++ b/src/libraries/System.Runtime/tests/System/DecimalTests.cs @@ -1505,7 +1505,7 @@ public static void ToString_InvalidFormat_ThrowsFormatException() public void TestRoundTripDecimalToString(string input) { decimal d = Decimal.Parse(input, NumberStyles.Number, NumberFormatInfo.InvariantInfo); - string dString = d.ToString(); + string dString = d.ToString(CultureInfo.InvariantCulture); Assert.Equal(input, dString); } public static IEnumerable Truncate_TestData() From 9897a27aaace156628735acdcb06938e3a24ce15 Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Tue, 23 Feb 2021 16:11:42 -0800 Subject: [PATCH 6/7] Also fix #48648 and unit tests --- .../src/System/Number.Parsing.cs | 36 +++++++++---------- .../tests/System/DoubleTests.cs | 6 ++++ .../System.Runtime/tests/System/HalfTests.cs | 5 +++ .../tests/System/SingleTests.cs | 6 ++++ 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index 03e5504a3e1072..6db1ba83889f6e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -358,9 +358,10 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty { number.Scale++; } - else if (digCount < maxDigCount) + + if (digCount < maxDigCount) { - // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part, so we keep a count of the number of trailing zeros and update digCount later + // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part for floating point numbers, so we keep a count of the number of trailing zeros and update digCount later if (ch == '0') { numberOfTrailingZeros++; @@ -395,22 +396,8 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty } bool negExp = false; - if (number.Kind != NumberBufferKind.FloatingPoint) - { - numberOfTrailingZeros = 0; - } - - if (digEnd == maxDigCount && numberOfTrailingZeros > 0 && number.HasNonZeroTail) - { - // We have a non-zero digit in the input past maxDigitCount. We already handle this properly. - number.DigitsCount = digEnd; - } - else - { - // If we have trailing 0s in the fractional part, we can strip it out - number.DigitsCount = digEnd - numberOfTrailingZeros; - } - number.Digits[number.DigitsCount] = (byte)('\0'); + number.DigitsCount = digEnd; + number.Digits[digEnd] = (byte)('\0'); if ((state & StateDigits) != 0) { if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) @@ -454,6 +441,19 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty ch = p < strEnd ? *p : '\0'; } } + + if (number.Kind == NumberBufferKind.FloatingPoint && !number.HasNonZeroTail) + { + // Adjust the number buffer for trailing zeros + int numberOfFractionalDigits = digEnd - number.Scale; + if (numberOfFractionalDigits > 0) + { + numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits); + number.DigitsCount = digEnd - numberOfTrailingZeros; + number.Digits[number.DigitsCount] = (byte)('\0'); + } + } + while (true) { if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.cs index 8403fe131d64cd..0c198e00117472 100644 --- a/src/libraries/System.Runtime/tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System/DoubleTests.cs @@ -290,6 +290,12 @@ public static IEnumerable Parse_Valid_TestData() yield return new object[] { "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 0.0 }; yield return new object[] { "0.005", defaultStyle, invariantFormat, 0.005 }; yield return new object[] { "0.0500", defaultStyle, invariantFormat, 0.05 }; + yield return new object[] { "6250000000000000000000000000000000e-12", defaultStyle, invariantFormat, 6.25e21 }; + yield return new object[] { "6250000e0", defaultStyle, invariantFormat, 6.25e6 }; + yield return new object[] { "6250100e-5", defaultStyle, invariantFormat, 62.501 }; + yield return new object[] { "625010.00e-4", defaultStyle, invariantFormat, 62.501 }; + yield return new object[] { "62500e-4", defaultStyle, invariantFormat, 6.25 }; + yield return new object[] { "62500", defaultStyle, invariantFormat, 62500.0 }; yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, 123.1 }; yield return new object[] { (1000.0).ToString("N0"), NumberStyles.AllowThousands, null, 1000.0 }; diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index e851f28815fbec..926034b5b70919 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -685,6 +685,11 @@ public static IEnumerable Parse_Valid_TestData() yield return new object[] { "0.000000000000000000", defaultStyle, invariantFormat, 0.0f }; yield return new object[] { "0.005", defaultStyle, invariantFormat, 0.005f }; yield return new object[] { "0.0400", defaultStyle, invariantFormat, 0.04f }; + yield return new object[] { "1200e0", defaultStyle, invariantFormat, 1200.0f }; + yield return new object[] { "120100e-4", defaultStyle, invariantFormat, 12.01f }; + yield return new object[] { "12010.00e-4", defaultStyle, invariantFormat, 1.201f }; + yield return new object[] { "12000e-4", defaultStyle, invariantFormat, 1.2f }; + yield return new object[] { "1200", defaultStyle, invariantFormat, 1200.0f }; yield return new object[] { (123.1f).ToString(), NumberStyles.AllowDecimalPoint, null, 123.1f }; yield return new object[] { (1000.0f).ToString("N0"), NumberStyles.AllowThousands, null, 1000.0f }; diff --git a/src/libraries/System.Runtime/tests/System/SingleTests.cs b/src/libraries/System.Runtime/tests/System/SingleTests.cs index 1fa4916c172e80..962621d05c15bf 100644 --- a/src/libraries/System.Runtime/tests/System/SingleTests.cs +++ b/src/libraries/System.Runtime/tests/System/SingleTests.cs @@ -295,6 +295,12 @@ public static IEnumerable Parse_Valid_TestData() yield return new object[] { "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, 0.0f }; yield return new object[] { "0.005", defaultStyle, invariantFormat, 0.005f }; yield return new object[] { "0.0500", defaultStyle, invariantFormat, 0.05f }; + yield return new object[] { "6250000000000000000000000000000000e-12", defaultStyle, invariantFormat, 6.25e21f }; + yield return new object[] { "6250000e0", defaultStyle, invariantFormat, 6.25e6f }; + yield return new object[] { "6250100e-5", defaultStyle, invariantFormat, 62.501f }; + yield return new object[] { "625010.00e-4", defaultStyle, invariantFormat, 62.501f }; + yield return new object[] { "62500e-4", defaultStyle, invariantFormat, 6.25f }; + yield return new object[] { "62500", defaultStyle, invariantFormat, 62500.0f }; yield return new object[] { (123.1f).ToString(), NumberStyles.AllowDecimalPoint, null, 123.1f }; yield return new object[] { (1000.0f).ToString("N0"), NumberStyles.AllowThousands, null, 1000.0f }; From f27bd8cf79ee9d3379bc21df29ad81ac4c103fcf Mon Sep 17 00:00:00 2001 From: Prashanth Govindarajan Date: Fri, 26 Feb 2021 13:06:59 -0800 Subject: [PATCH 7/7] Assert and unit test --- .../System.Private.CoreLib/src/System/Number.Parsing.cs | 1 + src/libraries/System.Runtime/tests/System/DoubleTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index 6db1ba83889f6e..382eb1ccf82a55 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -449,6 +449,7 @@ private static unsafe bool TryParseNumber(ref char* str, char* strEnd, NumberSty if (numberOfFractionalDigits > 0) { numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits); + Debug.Assert(numberOfTrailingZeros >= 0); number.DigitsCount = digEnd - numberOfTrailingZeros; number.Digits[number.DigitsCount] = (byte)('\0'); } diff --git a/src/libraries/System.Runtime/tests/System/DoubleTests.cs b/src/libraries/System.Runtime/tests/System/DoubleTests.cs index 0c198e00117472..d0bfcfb41419ca 100644 --- a/src/libraries/System.Runtime/tests/System/DoubleTests.cs +++ b/src/libraries/System.Runtime/tests/System/DoubleTests.cs @@ -296,6 +296,7 @@ public static IEnumerable Parse_Valid_TestData() yield return new object[] { "625010.00e-4", defaultStyle, invariantFormat, 62.501 }; yield return new object[] { "62500e-4", defaultStyle, invariantFormat, 6.25 }; yield return new object[] { "62500", defaultStyle, invariantFormat, 62500.0 }; + yield return new object[] { "10e-3", defaultStyle, invariantFormat, 0.01 }; yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, 123.1 }; yield return new object[] { (1000.0).ToString("N0"), NumberStyles.AllowThousands, null, 1000.0 };