@@ -158,6 +158,32 @@ 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 ;
163
189
let sawSign = false ;
@@ -351,59 +377,147 @@ export class Decimal128 extends BSONValue {
351
377
exponent = exponent - 1 ;
352
378
}
353
379
354
- while ( exponent < EXPONENT_MIN || nDigitsStored < nDigits ) {
355
- // Shift last digit. can only do this if < significant digits than # stored.
356
- if ( lastDigit === 0 ) {
357
- if ( significantDigits === 0 ) {
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 ) {
358
384
exponent = EXPONENT_MIN ;
385
+ significantDigits = 0 ;
359
386
break ;
360
387
}
361
388
362
- invalidErr ( representation , 'exponent underflow' ) ;
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
+ }
396
+
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' ) ;
407
+ }
363
408
}
364
409
365
- if ( nDigitsStored < nDigits ) {
366
- if (
367
- representation [ nDigits - 1 + Number ( sawSign ) + Number ( sawRadix ) ] !== '0' &&
368
- significantDigits !== 0
369
- ) {
370
- invalidErr ( representation , 'inexact rounding' ) ;
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 ;
371
421
}
372
- // adjust to match digits not stored
373
- nDigits = nDigits - 1 ;
374
- } else {
375
- if ( digits [ lastDigit ] !== 0 ) {
376
- invalidErr ( representation , 'inexact rounding' ) ;
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 ;
377
426
}
378
- // adjust to round
379
- lastDigit = lastDigit - 1 ;
380
- }
381
427
382
- if ( exponent < EXPONENT_MAX ) {
383
- exponent = exponent + 1 ;
384
- } else {
385
- invalidErr ( representation , 'overflow' ) ;
386
- }
387
- }
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
+ }
388
443
389
- // Round
390
- // We've normalized the exponent, but might still need to round.
391
- if ( lastDigit + 1 < significantDigits ) {
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 ;
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 {
461
+ break ;
462
+ }
463
+ }
464
+ }
397
465
}
398
- // if saw sign, we need to increment again to account for - or + sign at start.
399
- if ( sawSign ) {
400
- firstNonZero = firstNonZero + 1 ;
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
+ }
474
+
475
+ invalidErr ( representation , 'exponent underflow' ) ;
476
+ }
477
+
478
+ if ( nDigitsStored < nDigits ) {
479
+ if (
480
+ representation [ nDigits - 1 + Number ( sawSign ) + Number ( sawRadix ) ] !== '0' &&
481
+ significantDigits !== 0
482
+ ) {
483
+ invalidErr ( representation , 'inexact rounding' ) ;
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
+ }
401
500
}
402
501
403
- const roundDigit = parseInt ( representation [ firstNonZero + lastDigit + 1 ] , 10 ) ;
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 ) ;
404
517
405
- if ( roundDigit !== 0 ) {
406
- invalidErr ( representation , 'inexact rounding' ) ;
518
+ if ( roundDigit !== 0 ) {
519
+ invalidErr ( representation , 'inexact rounding' ) ;
520
+ }
407
521
}
408
522
}
409
523
@@ -507,7 +621,6 @@ export class Decimal128 extends BSONValue {
507
621
// Return the new Decimal128
508
622
return new Decimal128 ( buffer ) ;
509
623
}
510
-
511
624
/** Create a string representation of the raw Decimal128 value */
512
625
toString ( ) : string {
513
626
// Note: bits in this routine are referred to starting at 0,
0 commit comments