bson 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "vendor"
15
15
  ],
16
16
  "types": "bson.d.ts",
17
- "version": "5.4.0",
17
+ "version": "5.5.0",
18
18
  "author": {
19
19
  "name": "The MongoDB NodeJS Team",
20
20
  "email": "dbx-node@mongodb.com"
package/src/decimal128.ts CHANGED
@@ -158,8 +158,35 @@ export class Decimal128 extends BSONValue {
158
158
  * @param representation - a numeric string representation.
159
159
  */
160
160
  static fromString(representation: string): Decimal128 {
161
+ return Decimal128._fromString(representation, { allowRounding: false });
162
+ }
163
+
164
+ /**
165
+ * Create a Decimal128 instance from a string representation, allowing for rounding to 34
166
+ * significant digits
167
+ *
168
+ * @example Example of a number that will be rounded
169
+ * ```ts
170
+ * > let d = Decimal128.fromString('37.499999999999999196428571428571375')
171
+ * Uncaught:
172
+ * BSONError: "37.499999999999999196428571428571375" is not a valid Decimal128 string - inexact rounding
173
+ * at invalidErr (/home/wajames/js-bson/lib/bson.cjs:1402:11)
174
+ * at Decimal128.fromStringInternal (/home/wajames/js-bson/lib/bson.cjs:1633:25)
175
+ * at Decimal128.fromString (/home/wajames/js-bson/lib/bson.cjs:1424:27)
176
+ *
177
+ * > d = Decimal128.fromStringWithRounding('37.499999999999999196428571428571375')
178
+ * new Decimal128("37.49999999999999919642857142857138")
179
+ * ```
180
+ * @param representation - a numeric string representation.
181
+ */
182
+ static fromStringWithRounding(representation: string): Decimal128 {
183
+ return Decimal128._fromString(representation, { allowRounding: true });
184
+ }
185
+
186
+ private static _fromString(representation: string, options: { allowRounding: boolean }) {
161
187
  // Parse state tracking
162
188
  let isNegative = false;
189
+ let sawSign = false;
163
190
  let sawRadix = false;
164
191
  let foundNonZero = false;
165
192
 
@@ -180,15 +207,11 @@ export class Decimal128 extends BSONValue {
180
207
  let nDigitsStored = 0;
181
208
  // Insertion pointer for digits
182
209
  let digitsInsert = 0;
183
- // The index of the first non-zero digit
184
- let firstDigit = 0;
185
210
  // The index of the last digit
186
211
  let lastDigit = 0;
187
212
 
188
213
  // Exponent
189
214
  let exponent = 0;
190
- // loop index over array
191
- let i = 0;
192
215
  // The high 17 digits of the significand
193
216
  let significandHigh = new Long(0, 0);
194
217
  // The low 17 digits of the significand
@@ -241,6 +264,7 @@ export class Decimal128 extends BSONValue {
241
264
 
242
265
  // Get the negative or positive sign
243
266
  if (representation[index] === '+' || representation[index] === '-') {
267
+ sawSign = true;
244
268
  isNegative = representation[index++] === '-';
245
269
  }
246
270
 
@@ -263,7 +287,7 @@ export class Decimal128 extends BSONValue {
263
287
  continue;
264
288
  }
265
289
 
266
- if (nDigitsStored < 34) {
290
+ if (nDigitsStored < MAX_DIGITS) {
267
291
  if (representation[index] !== '0' || foundNonZero) {
268
292
  if (!foundNonZero) {
269
293
  firstNonZero = nDigitsRead;
@@ -307,11 +331,7 @@ export class Decimal128 extends BSONValue {
307
331
 
308
332
  // Done reading input
309
333
  // Find first non-zero digit in digits
310
- firstDigit = 0;
311
-
312
334
  if (!nDigitsStored) {
313
- firstDigit = 0;
314
- lastDigit = 0;
315
335
  digits[0] = 0;
316
336
  nDigits = 1;
317
337
  nDigitsStored = 1;
@@ -320,7 +340,11 @@ export class Decimal128 extends BSONValue {
320
340
  lastDigit = nDigitsStored - 1;
321
341
  significantDigits = nDigits;
322
342
  if (significantDigits !== 1) {
323
- while (digits[firstNonZero + significantDigits - 1] === 0) {
343
+ while (
344
+ representation[
345
+ firstNonZero + significantDigits - 1 + Number(sawSign) + Number(sawRadix)
346
+ ] === '0'
347
+ ) {
324
348
  significantDigits = significantDigits - 1;
325
349
  }
326
350
  }
@@ -331,7 +355,7 @@ export class Decimal128 extends BSONValue {
331
355
  // to represent user input
332
356
 
333
357
  // Overflow prevention
334
- if (exponent <= radixPosition && radixPosition - exponent > 1 << 14) {
358
+ if (exponent <= radixPosition && radixPosition > exponent + (1 << 14)) {
335
359
  exponent = EXPONENT_MIN;
336
360
  } else {
337
361
  exponent = exponent - radixPosition;
@@ -341,11 +365,9 @@ export class Decimal128 extends BSONValue {
341
365
  while (exponent > EXPONENT_MAX) {
342
366
  // Shift exponent to significand and decrease
343
367
  lastDigit = lastDigit + 1;
344
-
345
- if (lastDigit - firstDigit > MAX_DIGITS) {
368
+ if (lastDigit >= MAX_DIGITS) {
346
369
  // Check if we have a zero then just hard clamp, otherwise fail
347
- const digitsString = digits.join('');
348
- if (digitsString.match(/^0+$/)) {
370
+ if (significantDigits === 0) {
349
371
  exponent = EXPONENT_MAX;
350
372
  break;
351
373
  }
@@ -355,86 +377,146 @@ export class Decimal128 extends BSONValue {
355
377
  exponent = exponent - 1;
356
378
  }
357
379
 
358
- while (exponent < EXPONENT_MIN || nDigitsStored < nDigits) {
359
- // 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;
364
- }
380
+ if (options.allowRounding) {
381
+ while (exponent < EXPONENT_MIN || nDigitsStored < nDigits) {
382
+ // Shift last digit. can only do this if < significant digits than # stored.
383
+ if (lastDigit === 0 && significantDigits < nDigitsStored) {
384
+ exponent = EXPONENT_MIN;
385
+ significantDigits = 0;
386
+ break;
387
+ }
365
388
 
366
- if (nDigitsStored < nDigits) {
367
- // adjust to match digits not stored
368
- nDigits = nDigits - 1;
369
- } else {
370
- // adjust to round
371
- lastDigit = lastDigit - 1;
372
- }
389
+ if (nDigitsStored < nDigits) {
390
+ // adjust to match digits not stored
391
+ nDigits = nDigits - 1;
392
+ } else {
393
+ // adjust to round
394
+ lastDigit = lastDigit - 1;
395
+ }
373
396
 
374
- if (exponent < EXPONENT_MAX) {
375
- exponent = exponent + 1;
376
- } 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;
397
+ if (exponent < EXPONENT_MAX) {
398
+ exponent = exponent + 1;
399
+ } else {
400
+ // Check if we have a zero then just hard clamp, otherwise fail
401
+ const digitsString = digits.join('');
402
+ if (digitsString.match(/^0+$/)) {
403
+ exponent = EXPONENT_MAX;
404
+ break;
405
+ }
406
+ invalidErr(representation, 'overflow');
382
407
  }
383
- invalidErr(representation, 'overflow');
384
408
  }
385
- }
386
409
 
387
- // Round
388
- // We've normalized the exponent, but might still need to round.
389
- if (lastDigit - firstDigit + 1 < significantDigits) {
390
- let endOfString = nDigitsRead;
391
-
392
- // If we have seen a radix point, 'string' is 1 longer than we have
393
- // documented with ndigits_read, so inc the position of the first nonzero
394
- // digit and the position that digits are read to.
395
- if (sawRadix) {
396
- firstNonZero = firstNonZero + 1;
397
- endOfString = endOfString + 1;
398
- }
399
- // if negative, we need to increment again to account for - sign at start.
400
- if (isNegative) {
401
- firstNonZero = firstNonZero + 1;
402
- endOfString = endOfString + 1;
403
- }
410
+ // Round
411
+ // We've normalized the exponent, but might still need to round.
412
+ if (lastDigit + 1 < significantDigits) {
413
+ let endOfString = nDigitsRead;
414
+
415
+ // If we have seen a radix point, 'string' is 1 longer than we have
416
+ // documented with ndigits_read, so inc the position of the first nonzero
417
+ // digit and the position that digits are read to.
418
+ if (sawRadix) {
419
+ firstNonZero = firstNonZero + 1;
420
+ endOfString = endOfString + 1;
421
+ }
422
+ // if negative, we need to increment again to account for - sign at start.
423
+ if (sawSign) {
424
+ firstNonZero = firstNonZero + 1;
425
+ endOfString = endOfString + 1;
426
+ }
404
427
 
405
- const roundDigit = parseInt(representation[firstNonZero + lastDigit + 1], 10);
406
- let roundBit = 0;
428
+ const roundDigit = parseInt(representation[firstNonZero + lastDigit + 1], 10);
429
+ let roundBit = 0;
430
+
431
+ if (roundDigit >= 5) {
432
+ roundBit = 1;
433
+ if (roundDigit === 5) {
434
+ roundBit = digits[lastDigit] % 2 === 1 ? 1 : 0;
435
+ for (let i = firstNonZero + lastDigit + 2; i < endOfString; i++) {
436
+ if (parseInt(representation[i], 10)) {
437
+ roundBit = 1;
438
+ break;
439
+ }
440
+ }
441
+ }
442
+ }
407
443
 
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;
444
+ if (roundBit) {
445
+ let dIdx = lastDigit;
446
+
447
+ for (; dIdx >= 0; dIdx--) {
448
+ if (++digits[dIdx] > 9) {
449
+ digits[dIdx] = 0;
450
+
451
+ // overflowed most significant digit
452
+ if (dIdx === 0) {
453
+ if (exponent < EXPONENT_MAX) {
454
+ exponent = exponent + 1;
455
+ digits[dIdx] = 1;
456
+ } else {
457
+ return new Decimal128(isNegative ? INF_NEGATIVE_BUFFER : INF_POSITIVE_BUFFER);
458
+ }
459
+ }
460
+ } else {
415
461
  break;
416
462
  }
417
463
  }
418
464
  }
419
465
  }
466
+ } else {
467
+ while (exponent < EXPONENT_MIN || nDigitsStored < nDigits) {
468
+ // Shift last digit. can only do this if < significant digits than # stored.
469
+ if (lastDigit === 0) {
470
+ if (significantDigits === 0) {
471
+ exponent = EXPONENT_MIN;
472
+ break;
473
+ }
420
474
 
421
- if (roundBit) {
422
- let dIdx = lastDigit;
423
-
424
- for (; dIdx >= 0; dIdx--) {
425
- if (++digits[dIdx] > 9) {
426
- digits[dIdx] = 0;
475
+ invalidErr(representation, 'exponent underflow');
476
+ }
427
477
 
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
- }
478
+ if (nDigitsStored < nDigits) {
479
+ if (
480
+ representation[nDigits - 1 + Number(sawSign) + Number(sawRadix)] !== '0' &&
481
+ significantDigits !== 0
482
+ ) {
483
+ invalidErr(representation, 'inexact rounding');
437
484
  }
485
+ // adjust to match digits not stored
486
+ nDigits = nDigits - 1;
487
+ } else {
488
+ if (digits[lastDigit] !== 0) {
489
+ invalidErr(representation, 'inexact rounding');
490
+ }
491
+ // adjust to round
492
+ lastDigit = lastDigit - 1;
493
+ }
494
+
495
+ if (exponent < EXPONENT_MAX) {
496
+ exponent = exponent + 1;
497
+ } else {
498
+ invalidErr(representation, 'overflow');
499
+ }
500
+ }
501
+
502
+ // Round
503
+ // We've normalized the exponent, but might still need to round.
504
+ if (lastDigit + 1 < significantDigits) {
505
+ // If we have seen a radix point, 'string' is 1 longer than we have
506
+ // documented with ndigits_read, so inc the position of the first nonzero
507
+ // digit and the position that digits are read to.
508
+ if (sawRadix) {
509
+ firstNonZero = firstNonZero + 1;
510
+ }
511
+ // if saw sign, we need to increment again to account for - or + sign at start.
512
+ if (sawSign) {
513
+ firstNonZero = firstNonZero + 1;
514
+ }
515
+
516
+ const roundDigit = parseInt(representation[firstNonZero + lastDigit + 1], 10);
517
+
518
+ if (roundDigit !== 0) {
519
+ invalidErr(representation, 'inexact rounding');
438
520
  }
439
521
  }
440
522
  }
@@ -449,8 +531,8 @@ export class Decimal128 extends BSONValue {
449
531
  if (significantDigits === 0) {
450
532
  significandHigh = Long.fromNumber(0);
451
533
  significandLow = Long.fromNumber(0);
452
- } else if (lastDigit - firstDigit < 17) {
453
- let dIdx = firstDigit;
534
+ } else if (lastDigit < 17) {
535
+ let dIdx = 0;
454
536
  significandLow = Long.fromNumber(digits[dIdx++]);
455
537
  significandHigh = new Long(0, 0);
456
538
 
@@ -459,7 +541,7 @@ export class Decimal128 extends BSONValue {
459
541
  significandLow = significandLow.add(Long.fromNumber(digits[dIdx]));
460
542
  }
461
543
  } else {
462
- let dIdx = firstDigit;
544
+ let dIdx = 0;
463
545
  significandHigh = Long.fromNumber(digits[dIdx++]);
464
546
 
465
547
  for (; dIdx <= lastDigit - 17; dIdx++) {
@@ -539,7 +621,6 @@ export class Decimal128 extends BSONValue {
539
621
  // Return the new Decimal128
540
622
  return new Decimal128(buffer);
541
623
  }
542
-
543
624
  /** Create a string representation of the raw Decimal128 value */
544
625
  toString(): string {
545
626
  // Note: bits in this routine are referred to starting at 0,