Skip to content

Commit a806d53

Browse files
committed
fix(NODE-5546): fix fromString implementation
1 parent 097190b commit a806d53

File tree

2 files changed

+32
-62
lines changed

2 files changed

+32
-62
lines changed

src/decimal128.ts

Lines changed: 32 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export class Decimal128 extends BSONValue {
160160
static fromString(representation: string): Decimal128 {
161161
// Parse state tracking
162162
let isNegative = false;
163+
let sawSign = false;
163164
let sawRadix = false;
164165
let foundNonZero = false;
165166

@@ -187,8 +188,6 @@ export class Decimal128 extends BSONValue {
187188

188189
// Exponent
189190
let exponent = 0;
190-
// loop index over array
191-
let i = 0;
192191
// The high 17 digits of the significand
193192
let significandHigh = new Long(0, 0);
194193
// The low 17 digits of the significand
@@ -241,6 +240,7 @@ export class Decimal128 extends BSONValue {
241240

242241
// Get the negative or positive sign
243242
if (representation[index] === '+' || representation[index] === '-') {
243+
sawSign = true;
244244
isNegative = representation[index++] === '-';
245245
}
246246

@@ -263,7 +263,7 @@ export class Decimal128 extends BSONValue {
263263
continue;
264264
}
265265

266-
if (nDigitsStored < 34) {
266+
if (nDigitsStored < MAX_DIGITS) {
267267
if (representation[index] !== '0' || foundNonZero) {
268268
if (!foundNonZero) {
269269
firstNonZero = nDigitsRead;
@@ -320,7 +320,11 @@ export class Decimal128 extends BSONValue {
320320
lastDigit = nDigitsStored - 1;
321321
significantDigits = nDigits;
322322
if (significantDigits !== 1) {
323-
while (digits[firstNonZero + significantDigits - 1] === 0) {
323+
while (
324+
representation[
325+
firstNonZero + significantDigits - 1 + Number(sawSign) + Number(sawRadix)
326+
] === '0'
327+
) {
324328
significantDigits = significantDigits - 1;
325329
}
326330
}
@@ -331,7 +335,7 @@ export class Decimal128 extends BSONValue {
331335
// to represent user input
332336

333337
// Overflow prevention
334-
if (exponent <= radixPosition && radixPosition - exponent > 1 << 14) {
338+
if (exponent <= radixPosition && radixPosition > exponent + (1 << 14)) {
335339
exponent = EXPONENT_MIN;
336340
} else {
337341
exponent = exponent - radixPosition;
@@ -342,10 +346,9 @@ export class Decimal128 extends BSONValue {
342346
// Shift exponent to significand and decrease
343347
lastDigit = lastDigit + 1;
344348

345-
if (lastDigit - firstDigit > MAX_DIGITS) {
349+
if (lastDigit - firstDigit >= MAX_DIGITS) {
346350
// Check if we have a zero then just hard clamp, otherwise fail
347-
const digitsString = digits.join('');
348-
if (digitsString.match(/^0+$/)) {
351+
if (significantDigits === 0) {
349352
exponent = EXPONENT_MAX;
350353
break;
351354
}
@@ -357,85 +360,57 @@ export class Decimal128 extends BSONValue {
357360

358361
while (exponent < EXPONENT_MIN || nDigitsStored < nDigits) {
359362
// Shift last digit. can only do this if < significant digits than # stored.
360-
if (lastDigit === 0 && significantDigits < nDigitsStored) {
361-
exponent = EXPONENT_MIN;
362-
significantDigits = 0;
363-
break;
363+
if (lastDigit === 0) {
364+
if (significantDigits === 0) {
365+
exponent = EXPONENT_MIN;
366+
break;
367+
}
368+
369+
invalidErr(representation, 'exponent underflow');
364370
}
365371

366372
if (nDigitsStored < nDigits) {
373+
if (
374+
representation[nDigits - 1 + Number(sawSign) + Number(sawRadix)] !== '0' &&
375+
significantDigits !== 0
376+
) {
377+
invalidErr(representation, 'inexact rounding not allowed');
378+
}
367379
// adjust to match digits not stored
368380
nDigits = nDigits - 1;
369381
} else {
382+
if (digits[lastDigit] !== 0) {
383+
invalidErr(representation, 'inexact rounding not allowed');
384+
}
370385
// adjust to round
371386
lastDigit = lastDigit - 1;
372387
}
373388

374389
if (exponent < EXPONENT_MAX) {
375390
exponent = exponent + 1;
376391
} else {
377-
// Check if we have a zero then just hard clamp, otherwise fail
378-
const digitsString = digits.join('');
379-
if (digitsString.match(/^0+$/)) {
380-
exponent = EXPONENT_MAX;
381-
break;
382-
}
383392
invalidErr(representation, 'overflow');
384393
}
385394
}
386395

387396
// Round
388397
// We've normalized the exponent, but might still need to round.
389398
if (lastDigit - firstDigit + 1 < significantDigits) {
390-
let endOfString = nDigitsRead;
391-
392399
// If we have seen a radix point, 'string' is 1 longer than we have
393400
// documented with ndigits_read, so inc the position of the first nonzero
394401
// digit and the position that digits are read to.
395402
if (sawRadix) {
396403
firstNonZero = firstNonZero + 1;
397-
endOfString = endOfString + 1;
398404
}
399-
// if negative, we need to increment again to account for - sign at start.
400-
if (isNegative) {
405+
// if saw sign, we need to increment again to account for - or + sign at start.
406+
if (sawSign) {
401407
firstNonZero = firstNonZero + 1;
402-
endOfString = endOfString + 1;
403408
}
404409

405-
const roundDigit = parseInt(representation[firstNonZero + endOfString + 1], 10);
406-
let roundBit = 0;
407-
408-
if (roundDigit >= 5) {
409-
roundBit = 1;
410-
if (roundDigit === 5) {
411-
roundBit = digits[lastDigit] % 2 === 1 ? 1 : 0;
412-
for (i = firstNonZero + lastDigit + 2; i < endOfString; i++) {
413-
if (parseInt(representation[i], 10)) {
414-
roundBit = 1;
415-
break;
416-
}
417-
}
418-
}
419-
}
410+
const roundDigit = parseInt(representation[firstNonZero + lastDigit + 1], 10);
420411

421-
if (roundBit) {
422-
let dIdx = lastDigit;
423-
424-
for (; dIdx >= 0; dIdx--) {
425-
if (++digits[dIdx] > 9) {
426-
digits[dIdx] = 0;
427-
428-
// overflowed most significant digit
429-
if (dIdx === 0) {
430-
if (exponent < EXPONENT_MAX) {
431-
exponent = exponent + 1;
432-
digits[dIdx] = 1;
433-
} else {
434-
return new Decimal128(isNegative ? INF_NEGATIVE_BUFFER : INF_POSITIVE_BUFFER);
435-
}
436-
}
437-
}
438-
}
412+
if (roundDigit !== 0) {
413+
invalidErr(representation, 'inexact rounding not allowed');
439414
}
440415
}
441416

test/node/bson_corpus.spec.test.js

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,8 @@ function normalize(cEJ) {
5555

5656
const parseErrorForDecimal128 = scenario => {
5757
// TODO(NODE-3637): remove regex of skipped tests and and add errors to d128 parsing
58-
const skipRegex = /dqbsr|Inexact/;
5958
for (const parseError of scenario.parseErrors) {
6059
it(parseError.description, function () {
61-
if (skipRegex.test(parseError.description)) {
62-
this.skip();
63-
}
64-
6560
expect(
6661
() => BSON.Decimal128.fromString(parseError.string),
6762
`Decimal.fromString('${parseError.string}') should throw`

0 commit comments

Comments
 (0)