-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix parsing of trailing zeros: TryParseNumber #47666
Conversation
Tagging subscribers to this area: @tannergooding, @pgovind Issue DetailsFixes #46827 We're currently not throwing away the trailing zeros from the fractional part of a number while parsing. This leads us to erroneously round a floating point number sometimes. This PR fixes that. I've also updated the CheckConsistency method to detect trailing zeros.
|
src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs
Outdated
Show resolved
Hide resolved
Do we have perf tests.. can you get numbers? |
@@ -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", "c482221903e8")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with cbor. Do we know why this changed in size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nor am I. I just figured that we are parsing correctly now and returning 1
. So I looked at the test case 2 lines above for 1
and copied that. I can go back and see what value we're returning without the bug fix, but I'm pretty sure it is wrong somehow and this PR is fixing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be good to get confirmation.
The number of hex digits implies it was over the length 32-bits, but it also looks like there is some general encoding (compression?) here so it's unclear whether the change is actually correct (I would guess it is though).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eiriktsarpalis for thoughts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original CBOR encodes 1000e-3
, while the new one is 1e0
, so I presume this is in line with the intended change in the parser.
src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs
Outdated
Show resolved
Hide resolved
I'm reasonably confident that CI will be green now, so I got some perf numbers:
I'm a little skeptical about these perf numbers. This PR touches only the |
src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Number.NumberBuffer.cs
Outdated
Show resolved
Hide resolved
int numberOfTrailingZeros = 0; | ||
for (int i = DigitsCount - (int)fractionalDigitsPresent; i < DigitsCount; i++) | ||
// For a number like 1.23000, verify that we don't store trailing zeros in Digits | ||
// However, if the number of digits exceeds maxDigCount and rounding is required, we store the trailing zeros in the buffer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it require any more complex changes to just trim in this case as well?
I don't remember if this is going to have "cost" when dealing with the internal BigInteger
(I think it does) and so it might be better to trim and have HasNonZeroTail
correctly set if it doesn't require more complex changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it does require more changes, we can just log it as a "investigate later" issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started investigating this a little bit last week. It looks like the number of fractional digits present is used in calculating a fastExponent
which in turns determines whether we use the fast/slow path to convert a number to float/double. Also, HasNonZeroTail
is only used in the slow path currently. For this change, I decided to just be conservative, but yea I'll log an "investigate later" issue. Would be something to work on in December (at the least).
Hello @pgovind! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
@@ -374,7 +374,7 @@ internal static unsafe void DecimalToNumber(ref decimal d, ref NumberBuffer numb | |||
} | |||
*dst = (byte)('\0'); | |||
|
|||
number.CheckConsistency(); | |||
number.CheckConsistency(skipTrailingZeroCheck: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because decimal
needs to care about the number of trailing zeros, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I'm not uber certain. I see this in the log:
// NumberBuffer for 2060.ToString()
2060: Length 4, Scale 1, ....
I first assumed this would be represented as 2.060*10^3, but it's not. Then there's other cases where Scale is -1. So, for now, I'm turning off the consistency check for Decimal and we can figure out how to turn it back on later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that for decimal, 1000.0
and 1000.00
are represented differently.
> decimal.GetBits(1000.0m)
int[4] { 10000, 0, 0, 65536 }
> decimal.GetBits(1000.00m)
int[4] { 100000, 0, 0, 131072 }
because the number of zeros are actually tracked
I removed the trailing zero check in |
CI failures are unrelated. Merging. |
Blazor (both .NET Core and WASM) has a couple of tests where it attempts to verify that decimal -> string conversions roundtrip without any modifications. With this change, trailing 0s for decimal values are trimmed.
|
Found a more egregious issue:
Before: 3
|
I stepped out for something, I’ll look at this later tonight. In the meanwhile, tagging @tannergooding and @jeffhandley since this code change already made it to the P2 snap |
We will definitely need to fix this exception--that egregious issue is release-blocking for sure. So we'll need to get either a fix for that, or we'll need to revert the change and try again in Preview 3. The original issue that this spawns from is marked as a breaking change, but we need to look at these trailing zeroes cases more closely. |
@jeffhandley : Just a note that this PR fixes #46827 which is just a straight bug. I'm looking into the issue now |
Oh, I see -- I thought it was related to #46874. |
Alright, the bug occurs because of this line: number.DigitsCount = digEnd - numberOfTrailingZeros; digEnd is 1 and numberOfTrailingZeros = 2, so we end up with -1. The correct value here should be Currently, this bug only affects @jeffhandley : Just wondering about time line here? After I put a PR up, I'm not sure when it'll get approved and merged :) |
The fix could land early- to mid-week in the release branch next week. We'll want to get @pranavkm's validation of the fix from his scenarios through a private build and probably from the release branch bits too, with that completed before the end of next week. |
Fixes #46827
We're currently not throwing away the trailing zeros from the fractional part of a number while parsing. This leads us to erroneously round a floating point number sometimes. This PR fixes that. I've also updated the CheckConsistency method to detect trailing zeros.