opusdev/vector-similarity-api
1
1import { BSONValue } from './bson_value';2import { BSONError } from './error';3import { Long } from './long';4import { type InspectFn, defaultInspect, isUint8Array } from './parser/utils';5import { ByteUtils } from './utils/byte_utils';6 7const PARSE_STRING_REGEXP = /^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/;8const PARSE_INF_REGEXP = /^(\+|-)?(Infinity|inf)$/i;9const PARSE_NAN_REGEXP = /^(\+|-)?NaN$/i;10 11const EXPONENT_MAX = 6111;12const EXPONENT_MIN = -6176;13const EXPONENT_BIAS = 6176;14const MAX_DIGITS = 34;15 16// Nan value bits as 32 bit values (due to lack of longs)17const NAN_BUFFER = ByteUtils.fromNumberArray(18 [19 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0020 ].reverse()21);22// Infinity value bits 32 bit values (due to lack of longs)23const INF_NEGATIVE_BUFFER = ByteUtils.fromNumberArray(24 [25 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0026 ].reverse()27);28const INF_POSITIVE_BUFFER = ByteUtils.fromNumberArray(29 [30 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0031 ].reverse()32);33 34const EXPONENT_REGEX = /^([-+])?(\d+)?$/;35 36// Extract least significant 5 bits37const COMBINATION_MASK = 0x1f;38// Extract least significant 14 bits39const EXPONENT_MASK = 0x3fff;40// Value of combination field for Inf41const COMBINATION_INFINITY = 30;42// Value of combination field for NaN43const COMBINATION_NAN = 31;44 45// Detect if the value is a digit46function isDigit(value: string): boolean {47 return !isNaN(parseInt(value, 10));48}49 50// Divide two uint128 values51function divideu128(value: { parts: [number, number, number, number] }) {52 const DIVISOR = Long.fromNumber(1000 * 1000 * 1000);53 let _rem = Long.fromNumber(0);54 55 if (!value.parts[0] && !value.parts[1] && !value.parts[2] && !value.parts[3]) {56 return { quotient: value, rem: _rem };57 }58 59 for (let i = 0; i <= 3; i++) {60 // Adjust remainder to match value of next dividend61 _rem = _rem.shiftLeft(32);62 // Add the divided to _rem63 _rem = _rem.add(new Long(value.parts[i], 0));64 value.parts[i] = _rem.div(DIVISOR).low;65 _rem = _rem.modulo(DIVISOR);66 }67 68 return { quotient: value, rem: _rem };69}70 71// Multiply two Long values and return the 128 bit value72function multiply64x2(left: Long, right: Long): { high: Long; low: Long } {73 if (!left && !right) {74 return { high: Long.fromNumber(0), low: Long.fromNumber(0) };75 }76 77 const leftHigh = left.shiftRightUnsigned(32);78 const leftLow = new Long(left.getLowBits(), 0);79 const rightHigh = right.shiftRightUnsigned(32);80 const rightLow = new Long(right.getLowBits(), 0);81 82 let productHigh = leftHigh.multiply(rightHigh);83 let productMid = leftHigh.multiply(rightLow);84 const productMid2 = leftLow.multiply(rightHigh);85 let productLow = leftLow.multiply(rightLow);86 87 productHigh = productHigh.add(productMid.shiftRightUnsigned(32));88 productMid = new Long(productMid.getLowBits(), 0)89 .add(productMid2)90 .add(productLow.shiftRightUnsigned(32));91 92 productHigh = productHigh.add(productMid.shiftRightUnsigned(32));93 productLow = productMid.shiftLeft(32).add(new Long(productLow.getLowBits(), 0));94 95 // Return the 128 bit result96 return { high: productHigh, low: productLow };97}98 99function lessThan(left: Long, right: Long): boolean {100 // Make values unsigned101 const uhleft = left.high >>> 0;102 const uhright = right.high >>> 0;103 104 // Compare high bits first105 if (uhleft < uhright) {106 return true;107 } else if (uhleft === uhright) {108 const ulleft = left.low >>> 0;109 const ulright = right.low >>> 0;110 if (ulleft < ulright) return true;111 }112 113 return false;114}115 116function invalidErr(string: string, message: string) {117 throw new BSONError(`"${string}" is not a valid Decimal128 string - ${message}`);118}119 120/** @public */121export interface Decimal128Extended {122 $numberDecimal: string;123}124 125/**126 * A class representation of the BSON Decimal128 type.127 * @public128 * @category BSONType129 */130export class Decimal128 extends BSONValue {131 get _bsontype(): 'Decimal128' {132 return 'Decimal128';133 }134 135 readonly bytes!: Uint8Array;136 137 /**138 * @param bytes - a buffer containing the raw Decimal128 bytes in little endian order,139 * or a string representation as returned by .toString()140 */141 constructor(bytes: Uint8Array | string) {142 super();143 if (typeof bytes === 'string') {144 this.bytes = Decimal128.fromString(bytes).bytes;145 } else if (bytes instanceof Uint8Array || isUint8Array(bytes)) {146 if (bytes.byteLength !== 16) {147 throw new BSONError('Decimal128 must take a Buffer of 16 bytes');148 }149 this.bytes = bytes;150 } else {151 throw new BSONError('Decimal128 must take a Buffer or string');152 }153 }154 155 /**156 * Create a Decimal128 instance from a string representation157 *158 * @param representation - a numeric string representation.159 */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 34166 * significant digits167 *168 * @example Example of a number that will be rounded169 * ```ts170 * > let d = Decimal128.fromString('37.499999999999999196428571428571375')171 * Uncaught:172 * BSONError: "37.499999999999999196428571428571375" is not a valid Decimal128 string - inexact rounding173 * 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 }) {187 // Parse state tracking188 let isNegative = false;189 let sawSign = false;190 let sawRadix = false;191 let foundNonZero = false;192 193 // Total number of significant digits (no leading or trailing zero)194 let significantDigits = 0;195 // Total number of significand digits read196 let nDigitsRead = 0;197 // Total number of digits (no leading zeros)198 let nDigits = 0;199 // The number of the digits after radix200 let radixPosition = 0;201 // The index of the first non-zero in *str*202 let firstNonZero = 0;203 204 // Digits Array205 const digits = [0];206 // The number of digits in digits207 let nDigitsStored = 0;208 // Insertion pointer for digits209 let digitsInsert = 0;210 // The index of the last digit211 let lastDigit = 0;212 213 // Exponent214 let exponent = 0;215 // The high 17 digits of the significand216 let significandHigh = new Long(0, 0);217 // The low 17 digits of the significand218 let significandLow = new Long(0, 0);219 // The biased exponent220 let biasedExponent = 0;221 222 // Read index223 let index = 0;224 225 // Naively prevent against REDOS attacks.226 // TODO: implementing a custom parsing for this, or refactoring the regex would yield227 // further gains.228 if (representation.length >= 7000) {229 throw new BSONError('' + representation + ' not a valid Decimal128 string');230 }231 232 // Results233 const stringMatch = representation.match(PARSE_STRING_REGEXP);234 const infMatch = representation.match(PARSE_INF_REGEXP);235 const nanMatch = representation.match(PARSE_NAN_REGEXP);236 237 // Validate the string238 if ((!stringMatch && !infMatch && !nanMatch) || representation.length === 0) {239 throw new BSONError('' + representation + ' not a valid Decimal128 string');240 }241 242 if (stringMatch) {243 // full_match = stringMatch[0]244 // sign = stringMatch[1]245 246 const unsignedNumber = stringMatch[2];247 // stringMatch[3] is undefined if a whole number (ex "1", 12")248 // but defined if a number w/ decimal in it (ex "1.0, 12.2")249 250 const e = stringMatch[4];251 const expSign = stringMatch[5];252 const expNumber = stringMatch[6];253 254 // they provided e, but didn't give an exponent number. for ex "1e"255 if (e && expNumber === undefined) invalidErr(representation, 'missing exponent power');256 257 // they provided e, but didn't give a number before it. for ex "e1"258 if (e && unsignedNumber === undefined) invalidErr(representation, 'missing exponent base');259 260 if (e === undefined && (expSign || expNumber)) {261 invalidErr(representation, 'missing e before exponent');262 }263 }264 265 // Get the negative or positive sign266 if (representation[index] === '+' || representation[index] === '-') {267 sawSign = true;268 isNegative = representation[index++] === '-';269 }270 271 // Check if user passed Infinity or NaN272 if (!isDigit(representation[index]) && representation[index] !== '.') {273 if (representation[index] === 'i' || representation[index] === 'I') {274 return new Decimal128(isNegative ? INF_NEGATIVE_BUFFER : INF_POSITIVE_BUFFER);275 } else if (representation[index] === 'N') {276 return new Decimal128(NAN_BUFFER);277 }278 }279 280 // Read all the digits281 while (isDigit(representation[index]) || representation[index] === '.') {282 if (representation[index] === '.') {283 if (sawRadix) invalidErr(representation, 'contains multiple periods');284 285 sawRadix = true;286 index = index + 1;287 continue;288 }289 290 if (nDigitsStored < MAX_DIGITS) {291 if (representation[index] !== '0' || foundNonZero) {292 if (!foundNonZero) {293 firstNonZero = nDigitsRead;294 }295 296 foundNonZero = true;297 298 // Only store 34 digits299 digits[digitsInsert++] = parseInt(representation[index], 10);300 nDigitsStored = nDigitsStored + 1;301 }302 }303 304 if (foundNonZero) nDigits = nDigits + 1;305 if (sawRadix) radixPosition = radixPosition + 1;306 307 nDigitsRead = nDigitsRead + 1;308 index = index + 1;309 }310 311 if (sawRadix && !nDigitsRead)312 throw new BSONError('' + representation + ' not a valid Decimal128 string');313 314 // Read exponent if exists315 if (representation[index] === 'e' || representation[index] === 'E') {316 // Read exponent digits317 const match = representation.substr(++index).match(EXPONENT_REGEX);318 319 // No digits read320 if (!match || !match[2]) return new Decimal128(NAN_BUFFER);321 322 // Get exponent323 exponent = parseInt(match[0], 10);324 325 // Adjust the index326 index = index + match[0].length;327 }328 329 // Return not a number330 if (representation[index]) return new Decimal128(NAN_BUFFER);331 332 // Done reading input333 // Find first non-zero digit in digits334 if (!nDigitsStored) {335 digits[0] = 0;336 nDigits = 1;337 nDigitsStored = 1;338 significantDigits = 0;339 } else {340 lastDigit = nDigitsStored - 1;341 significantDigits = nDigits;342 if (significantDigits !== 1) {343 while (344 representation[345 firstNonZero + significantDigits - 1 + Number(sawSign) + Number(sawRadix)346 ] === '0'347 ) {348 significantDigits = significantDigits - 1;349 }350 }351 }352 353 // Normalization of exponent354 // Correct exponent based on radix position, and shift significand as needed355 // to represent user input356 357 // Overflow prevention358 if (exponent <= radixPosition && radixPosition > exponent + (1 << 14)) {359 exponent = EXPONENT_MIN;360 } else {361 exponent = exponent - radixPosition;362 }363 364 // Attempt to normalize the exponent365 while (exponent > EXPONENT_MAX) {366 // Shift exponent to significand and decrease367 lastDigit = lastDigit + 1;368 if (lastDigit >= MAX_DIGITS) {369 // Check if we have a zero then just hard clamp, otherwise fail370 if (significantDigits === 0) {371 exponent = EXPONENT_MAX;372 break;373 }374 375 invalidErr(representation, 'overflow');376 }377 exponent = exponent - 1;378 }379 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 }388 389 if (nDigitsStored < nDigits) {390 // adjust to match digits not stored391 nDigits = nDigits - 1;392 } else {393 // adjust to round394 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 fail401 const digitsString = digits.join('');402 if (digitsString.match(/^0+$/)) {403 exponent = EXPONENT_MAX;404 break;405 }406 invalidErr(representation, 'overflow');407 }408 }409 410 // Round411 // 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 have416 // documented with ndigits_read, so inc the position of the first nonzero417 // 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 }427 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 }443 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 digit452 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 }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 }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 !== 0482 ) {483 invalidErr(representation, 'inexact rounding');484 }485 // adjust to match digits not stored486 nDigits = nDigits - 1;487 } else {488 if (digits[lastDigit] !== 0) {489 invalidErr(representation, 'inexact rounding');490 }491 // adjust to round492 lastDigit = lastDigit - 1;493 }494 495 if (exponent < EXPONENT_MAX) {496 exponent = exponent + 1;497 } else {498 invalidErr(representation, 'overflow');499 }500 }501 502 // Round503 // 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 have506 // documented with ndigits_read, so inc the position of the first nonzero507 // 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');520 }521 }522 }523 524 // Encode significand525 // The high 17 digits of the significand526 significandHigh = Long.fromNumber(0);527 // The low 17 digits of the significand528 significandLow = Long.fromNumber(0);529 530 // read a zero531 if (significantDigits === 0) {532 significandHigh = Long.fromNumber(0);533 significandLow = Long.fromNumber(0);534 } else if (lastDigit < 17) {535 let dIdx = 0;536 significandLow = Long.fromNumber(digits[dIdx++]);537 significandHigh = new Long(0, 0);538 539 for (; dIdx <= lastDigit; dIdx++) {540 significandLow = significandLow.multiply(Long.fromNumber(10));541 significandLow = significandLow.add(Long.fromNumber(digits[dIdx]));542 }543 } else {544 let dIdx = 0;545 significandHigh = Long.fromNumber(digits[dIdx++]);546 547 for (; dIdx <= lastDigit - 17; dIdx++) {548 significandHigh = significandHigh.multiply(Long.fromNumber(10));549 significandHigh = significandHigh.add(Long.fromNumber(digits[dIdx]));550 }551 552 significandLow = Long.fromNumber(digits[dIdx++]);553 554 for (; dIdx <= lastDigit; dIdx++) {555 significandLow = significandLow.multiply(Long.fromNumber(10));556 significandLow = significandLow.add(Long.fromNumber(digits[dIdx]));557 }558 }559 560 const significand = multiply64x2(significandHigh, Long.fromString('100000000000000000'));561 significand.low = significand.low.add(significandLow);562 563 if (lessThan(significand.low, significandLow)) {564 significand.high = significand.high.add(Long.fromNumber(1));565 }566 567 // Biased exponent568 biasedExponent = exponent + EXPONENT_BIAS;569 const dec = { low: Long.fromNumber(0), high: Long.fromNumber(0) };570 571 // Encode combination, exponent, and significand.572 if (573 significand.high.shiftRightUnsigned(49).and(Long.fromNumber(1)).equals(Long.fromNumber(1))574 ) {575 // Encode '11' into bits 1 to 3576 dec.high = dec.high.or(Long.fromNumber(0x3).shiftLeft(61));577 dec.high = dec.high.or(578 Long.fromNumber(biasedExponent).and(Long.fromNumber(0x3fff).shiftLeft(47))579 );580 dec.high = dec.high.or(significand.high.and(Long.fromNumber(0x7fffffffffff)));581 } else {582 dec.high = dec.high.or(Long.fromNumber(biasedExponent & 0x3fff).shiftLeft(49));583 dec.high = dec.high.or(significand.high.and(Long.fromNumber(0x1ffffffffffff)));584 }585 586 dec.low = significand.low;587 588 // Encode sign589 if (isNegative) {590 dec.high = dec.high.or(Long.fromString('9223372036854775808'));591 }592 593 // Encode into a buffer594 const buffer = ByteUtils.allocateUnsafe(16);595 index = 0;596 597 // Encode the low 64 bits of the decimal598 // Encode low bits599 buffer[index++] = dec.low.low & 0xff;600 buffer[index++] = (dec.low.low >> 8) & 0xff;601 buffer[index++] = (dec.low.low >> 16) & 0xff;602 buffer[index++] = (dec.low.low >> 24) & 0xff;603 // Encode high bits604 buffer[index++] = dec.low.high & 0xff;605 buffer[index++] = (dec.low.high >> 8) & 0xff;606 buffer[index++] = (dec.low.high >> 16) & 0xff;607 buffer[index++] = (dec.low.high >> 24) & 0xff;608 609 // Encode the high 64 bits of the decimal610 // Encode low bits611 buffer[index++] = dec.high.low & 0xff;612 buffer[index++] = (dec.high.low >> 8) & 0xff;613 buffer[index++] = (dec.high.low >> 16) & 0xff;614 buffer[index++] = (dec.high.low >> 24) & 0xff;615 // Encode high bits616 buffer[index++] = dec.high.high & 0xff;617 buffer[index++] = (dec.high.high >> 8) & 0xff;618 buffer[index++] = (dec.high.high >> 16) & 0xff;619 buffer[index++] = (dec.high.high >> 24) & 0xff;620 621 // Return the new Decimal128622 return new Decimal128(buffer);623 }624 /** Create a string representation of the raw Decimal128 value */625 toString(): string {626 // Note: bits in this routine are referred to starting at 0,627 // from the sign bit, towards the coefficient.628 629 // decoded biased exponent (14 bits)630 let biased_exponent;631 // the number of significand digits632 let significand_digits = 0;633 // the base-10 digits in the significand634 const significand = new Array<number>(36);635 for (let i = 0; i < significand.length; i++) significand[i] = 0;636 // read pointer into significand637 let index = 0;638 639 // true if the number is zero640 let is_zero = false;641 642 // the most significant significand bits (50-46)643 let significand_msb;644 // temporary storage for significand decoding645 let significand128: { parts: [number, number, number, number] } = { parts: [0, 0, 0, 0] };646 // indexing variables647 let j, k;648 649 // Output string650 const string: string[] = [];651 652 // Unpack index653 index = 0;654 655 // Buffer reference656 const buffer = this.bytes;657 658 // Unpack the low 64bits into a long659 // bits 96 - 127660 const low =661 buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);662 // bits 64 - 95663 const midl =664 buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);665 666 // Unpack the high 64bits into a long667 // bits 32 - 63668 const midh =669 buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);670 // bits 0 - 31671 const high =672 buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);673 674 // Unpack index675 index = 0;676 677 // Create the state of the decimal678 const dec = {679 low: new Long(low, midl),680 high: new Long(midh, high)681 };682 683 if (dec.high.lessThan(Long.ZERO)) {684 string.push('-');685 }686 687 // Decode combination field and exponent688 // bits 1 - 5689 const combination = (high >> 26) & COMBINATION_MASK;690 691 if (combination >> 3 === 3) {692 // Check for 'special' values693 if (combination === COMBINATION_INFINITY) {694 return string.join('') + 'Infinity';695 } else if (combination === COMBINATION_NAN) {696 return 'NaN';697 } else {698 biased_exponent = (high >> 15) & EXPONENT_MASK;699 significand_msb = 0x08 + ((high >> 14) & 0x01);700 }701 } else {702 significand_msb = (high >> 14) & 0x07;703 biased_exponent = (high >> 17) & EXPONENT_MASK;704 }705 706 // unbiased exponent707 const exponent = biased_exponent - EXPONENT_BIAS;708 709 // Create string of significand digits710 711 // Convert the 114-bit binary number represented by712 // (significand_high, significand_low) to at most 34 decimal713 // digits through modulo and division.714 significand128.parts[0] = (high & 0x3fff) + ((significand_msb & 0xf) << 14);715 significand128.parts[1] = midh;716 significand128.parts[2] = midl;717 significand128.parts[3] = low;718 719 if (720 significand128.parts[0] === 0 &&721 significand128.parts[1] === 0 &&722 significand128.parts[2] === 0 &&723 significand128.parts[3] === 0724 ) {725 is_zero = true;726 } else {727 for (k = 3; k >= 0; k--) {728 let least_digits = 0;729 // Perform the divide730 const result = divideu128(significand128);731 significand128 = result.quotient;732 least_digits = result.rem.low;733 734 // We now have the 9 least significant digits (in base 2).735 // Convert and output to string.736 if (!least_digits) continue;737 738 for (j = 8; j >= 0; j--) {739 // significand[k * 9 + j] = Math.round(least_digits % 10);740 significand[k * 9 + j] = least_digits % 10;741 // least_digits = Math.round(least_digits / 10);742 least_digits = Math.floor(least_digits / 10);743 }744 }745 }746 747 // Output format options:748 // Scientific - [-]d.dddE(+/-)dd or [-]dE(+/-)dd749 // Regular - ddd.ddd750 751 if (is_zero) {752 significand_digits = 1;753 significand[index] = 0;754 } else {755 significand_digits = 36;756 while (!significand[index]) {757 significand_digits = significand_digits - 1;758 index = index + 1;759 }760 }761 762 // the exponent if scientific notation is used763 const scientific_exponent = significand_digits - 1 + exponent;764 765 // The scientific exponent checks are dictated by the string conversion766 // specification and are somewhat arbitrary cutoffs.767 //768 // We must check exponent > 0, because if this is the case, the number769 // has trailing zeros. However, we *cannot* output these trailing zeros,770 // because doing so would change the precision of the value, and would771 // change stored data if the string converted number is round tripped.772 if (scientific_exponent >= 34 || scientific_exponent <= -7 || exponent > 0) {773 // Scientific format774 775 // if there are too many significant digits, we should just be treating numbers776 // as + or - 0 and using the non-scientific exponent (this is for the "invalid777 // representation should be treated as 0/-0" spec cases in decimal128-1.json)778 if (significand_digits > 34) {779 string.push(`${0}`);780 if (exponent > 0) string.push(`E+${exponent}`);781 else if (exponent < 0) string.push(`E${exponent}`);782 return string.join('');783 }784 785 string.push(`${significand[index++]}`);786 significand_digits = significand_digits - 1;787 788 if (significand_digits) {789 string.push('.');790 }791 792 for (let i = 0; i < significand_digits; i++) {793 string.push(`${significand[index++]}`);794 }795 796 // Exponent797 string.push('E');798 if (scientific_exponent > 0) {799 string.push(`+${scientific_exponent}`);800 } else {801 string.push(`${scientific_exponent}`);802 }803 } else {804 // Regular format with no decimal place805 if (exponent >= 0) {806 for (let i = 0; i < significand_digits; i++) {807 string.push(`${significand[index++]}`);808 }809 } else {810 let radix_position = significand_digits + exponent;811 812 // non-zero digits before radix813 if (radix_position > 0) {814 for (let i = 0; i < radix_position; i++) {815 string.push(`${significand[index++]}`);816 }817 } else {818 string.push('0');819 }820 821 string.push('.');822 // add leading zeros after radix823 while (radix_position++ < 0) {824 string.push('0');825 }826 827 for (let i = 0; i < significand_digits - Math.max(radix_position - 1, 0); i++) {828 string.push(`${significand[index++]}`);829 }830 }831 }832 833 return string.join('');834 }835 836 toJSON(): Decimal128Extended {837 return { $numberDecimal: this.toString() };838 }839 840 /** @internal */841 toExtendedJSON(): Decimal128Extended {842 return { $numberDecimal: this.toString() };843 }844 845 /** @internal */846 static fromExtendedJSON(doc: Decimal128Extended): Decimal128 {847 return Decimal128.fromString(doc.$numberDecimal);848 }849 850 inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {851 inspect ??= defaultInspect;852 const d128string = inspect(this.toString(), options);853 return `new Decimal128(${d128string})`;854 }855}856 