Skip to content

Commit

Permalink
fix(NODE-5546): fix fromString implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
W-A-James committed Aug 23, 2023
1 parent 097190b commit a806d53
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 62 deletions.
89 changes: 32 additions & 57 deletions src/decimal128.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export class Decimal128 extends BSONValue {
static fromString(representation: string): Decimal128 {
// Parse state tracking
let isNegative = false;
let sawSign = false;
let sawRadix = false;
let foundNonZero = false;

Expand Down Expand Up @@ -187,8 +188,6 @@ export class Decimal128 extends BSONValue {

// Exponent
let exponent = 0;
// loop index over array
let i = 0;
// The high 17 digits of the significand
let significandHigh = new Long(0, 0);
// The low 17 digits of the significand
Expand Down Expand Up @@ -241,6 +240,7 @@ export class Decimal128 extends BSONValue {

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

Expand All @@ -263,7 +263,7 @@ export class Decimal128 extends BSONValue {
continue;
}

if (nDigitsStored < 34) {
if (nDigitsStored < MAX_DIGITS) {
if (representation[index] !== '0' || foundNonZero) {
if (!foundNonZero) {
firstNonZero = nDigitsRead;
Expand Down Expand Up @@ -320,7 +320,11 @@ export class Decimal128 extends BSONValue {
lastDigit = nDigitsStored - 1;
significantDigits = nDigits;
if (significantDigits !== 1) {
while (digits[firstNonZero + significantDigits - 1] === 0) {
while (
representation[
firstNonZero + significantDigits - 1 + Number(sawSign) + Number(sawRadix)
] === '0'
) {
significantDigits = significantDigits - 1;
}
}
Expand All @@ -331,7 +335,7 @@ export class Decimal128 extends BSONValue {
// to represent user input

// Overflow prevention
if (exponent <= radixPosition && radixPosition - exponent > 1 << 14) {
if (exponent <= radixPosition && radixPosition > exponent + (1 << 14)) {
exponent = EXPONENT_MIN;
} else {
exponent = exponent - radixPosition;
Expand All @@ -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) {
// Check if we have a zero then just hard clamp, otherwise fail
const digitsString = digits.join('');
if (digitsString.match(/^0+$/)) {
if (significantDigits === 0) {
exponent = EXPONENT_MAX;
break;
}
Expand All @@ -357,85 +360,57 @@ export class Decimal128 extends BSONValue {

while (exponent < EXPONENT_MIN || nDigitsStored < nDigits) {
// Shift last digit. can only do this if < significant digits than # stored.
if (lastDigit === 0 && significantDigits < nDigitsStored) {
exponent = EXPONENT_MIN;
significantDigits = 0;
break;
if (lastDigit === 0) {
if (significantDigits === 0) {
exponent = EXPONENT_MIN;
break;
}

invalidErr(representation, 'exponent underflow');
}

if (nDigitsStored < nDigits) {
if (
representation[nDigits - 1 + Number(sawSign) + Number(sawRadix)] !== '0' &&
significantDigits !== 0
) {
invalidErr(representation, 'inexact rounding not allowed');
}
// adjust to match digits not stored
nDigits = nDigits - 1;
} else {
if (digits[lastDigit] !== 0) {
invalidErr(representation, 'inexact rounding not allowed');
}
// adjust to round
lastDigit = lastDigit - 1;
}

if (exponent < EXPONENT_MAX) {
exponent = exponent + 1;
} else {
// Check if we have a zero then just hard clamp, otherwise fail
const digitsString = digits.join('');
if (digitsString.match(/^0+$/)) {
exponent = EXPONENT_MAX;
break;
}
invalidErr(representation, 'overflow');
}
}

// Round
// We've normalized the exponent, but might still need to round.
if (lastDigit - firstDigit + 1 < significantDigits) {
let endOfString = nDigitsRead;

// If we have seen a radix point, 'string' is 1 longer than we have
// documented with ndigits_read, so inc the position of the first nonzero
// digit and the position that digits are read to.
if (sawRadix) {
firstNonZero = firstNonZero + 1;
endOfString = endOfString + 1;
}
// if negative, we need to increment again to account for - sign at start.
if (isNegative) {
// if saw sign, we need to increment again to account for - or + sign at start.
if (sawSign) {
firstNonZero = firstNonZero + 1;
endOfString = endOfString + 1;
}

const roundDigit = parseInt(representation[firstNonZero + endOfString + 1], 10);
let roundBit = 0;

if (roundDigit >= 5) {
roundBit = 1;
if (roundDigit === 5) {
roundBit = digits[lastDigit] % 2 === 1 ? 1 : 0;
for (i = firstNonZero + lastDigit + 2; i < endOfString; i++) {
if (parseInt(representation[i], 10)) {
roundBit = 1;
break;
}
}
}
}
const roundDigit = parseInt(representation[firstNonZero + lastDigit + 1], 10);

if (roundBit) {
let dIdx = lastDigit;

for (; dIdx >= 0; dIdx--) {
if (++digits[dIdx] > 9) {
digits[dIdx] = 0;

// overflowed most significant digit
if (dIdx === 0) {
if (exponent < EXPONENT_MAX) {
exponent = exponent + 1;
digits[dIdx] = 1;
} else {
return new Decimal128(isNegative ? INF_NEGATIVE_BUFFER : INF_POSITIVE_BUFFER);
}
}
}
}
if (roundDigit !== 0) {
invalidErr(representation, 'inexact rounding not allowed');
}
}

Expand Down
5 changes: 0 additions & 5 deletions test/node/bson_corpus.spec.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,8 @@ function normalize(cEJ) {

const parseErrorForDecimal128 = scenario => {
// TODO(NODE-3637): remove regex of skipped tests and and add errors to d128 parsing
const skipRegex = /dqbsr|Inexact/;
for (const parseError of scenario.parseErrors) {
it(parseError.description, function () {
if (skipRegex.test(parseError.description)) {
this.skip();
}

expect(
() => BSON.Decimal128.fromString(parseError.string),
`Decimal.fromString('${parseError.string}') should throw`
Expand Down

0 comments on commit a806d53

Please sign in to comment.