-
Notifications
You must be signed in to change notification settings - Fork 252
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(NODE-5546): decimal 128 fromString performs inexact rounding #613
Conversation
@@ -160,6 +160,7 @@ export class Decimal128 extends BSONValue { | |||
static fromString(representation: string): Decimal128 { | |||
// Parse state tracking | |||
let isNegative = false; | |||
let sawSign = false; |
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 saw the sign
And it opened up my eyes, I saw the sign
Life is demanding without understanding 🎵
src/decimal128.ts
Outdated
@@ -342,10 +346,9 @@ export class Decimal128 extends BSONValue { | |||
// Shift exponent to significand and decrease | |||
lastDigit = lastDigit + 1; | |||
|
|||
if (lastDigit - firstDigit > MAX_DIGITS) { | |||
if (lastDigit - firstDigit >= MAX_DIGITS) { |
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.
Check this with a test case
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.
Looking at it, firstDigit
is always 0; it doesn't change at all throughout the entire function, so I'm going to go ahead and remove all references to it.
With regards to changing to the gte sign, the cases that trigger this branch are clamped zeros with large positive increments (see spec tests in decimal128-1.json), but any number with a sufficiently large exponent can enter this block.
If we consider a number like 1000000000000000000000000000000000e6112
, which has 1 significant digit, 34 digits before the exponent and an exponent that's above max exponent, then what happens when we have a gt sign here is that we end up with the following result:
> Decimal128.fromString('1000000000000000000000000000000000e6112')
new Decimal128("0E+6111")
since we skip the iteration when lastDigit
reaches MAX_DIGITS
and proceed without error despite the fact that we should have overflowed. This leads us to get to our encoding and produce the incorrect result we see above.
With the gte sign in place, we get the following result, which is what we expect
> Decimal128.fromString('1000000000000000000000000000000000e6112')
Uncaught:
BSONError: "1000000000000000000000000000000000e6112" is not a valid Decimal128 string - overflow
at invalidErr (/home/wajames/js-bson/lib/bson.cjs:1402:11)
at Decimal128.fromString (/home/wajames/js-bson/lib/bson.cjs:1540:17)
it seems like we should have spec test that test corner cases like this.
Description
What is changing?
Decimal128.fromString
algorithm to match that in mongo-c-driverDecimal128
spec testsDecimal128
spec testsIs there new documentation needed for these changes?
What is the motivation for this change?
NODE-5546
Release Highlight
Decimal128
constructor now throws when detecting loss of precisionPrior to this release,
Decimal128
would round numbers with more than 34 significant digits and lose precision. Now, on detecting loss of precision,Decimal128
's constructor andDecimal128.fromString
will throw aBSONError
. This behaviour should have been the default as theDecimal128
class was always intended to be high-precision floating point value. As such, silently rounding is undesirable behaviour.Double check the following
npm run check:lint
scripttype(NODE-xxxx)[!]: description
feat(NODE-1234)!: rewriting everything in coffeescript