@@ -158,6 +158,32 @@ export class Decimal128 extends BSONValue {
158158 * @param representation - a numeric string representation.
159159 */
160160 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 } ) {
161187 // Parse state tracking
162188 let isNegative = false ;
163189 let sawSign = false ;
@@ -351,59 +377,147 @@ export class Decimal128 extends BSONValue {
351377 exponent = exponent - 1 ;
352378 }
353379
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 ) {
358384 exponent = EXPONENT_MIN ;
385+ significantDigits = 0 ;
359386 break ;
360387 }
361388
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+ }
363408 }
364409
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 ;
371421 }
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 ;
377426 }
378- // adjust to round
379- lastDigit = lastDigit - 1 ;
380- }
381427
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+ }
388443
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+ }
397465 }
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+ }
401500 }
402501
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 ) ;
404517
405- if ( roundDigit !== 0 ) {
406- invalidErr ( representation , 'inexact rounding' ) ;
518+ if ( roundDigit !== 0 ) {
519+ invalidErr ( representation , 'inexact rounding' ) ;
520+ }
407521 }
408522 }
409523
@@ -507,7 +621,6 @@ export class Decimal128 extends BSONValue {
507621 // Return the new Decimal128
508622 return new Decimal128 ( buffer ) ;
509623 }
510-
511624 /** Create a string representation of the raw Decimal128 value */
512625 toString ( ) : string {
513626 // Note: bits in this routine are referred to starting at 0,
0 commit comments