import { BSONTypeError } from "./error.ts";import { Long } from "./long.ts";
const PARSE_STRING_REGEXP = /^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/;const PARSE_INF_REGEXP = /^(\+|-)?(Infinity|inf)$/i;const PARSE_NAN_REGEXP = /^(\+|-)?NaN$/i;
const EXPONENT_MAX = 6111;const EXPONENT_MIN = -6176;const EXPONENT_BIAS = 6176;const MAX_DIGITS = 34;
const NAN_BUFFER = [ 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,].reverse();const INF_NEGATIVE_BUFFER = [ 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,].reverse();const INF_POSITIVE_BUFFER = [ 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,].reverse();
const EXPONENT_REGEX = /^([-+])?(\d+)?$/;
const COMBINATION_MASK = 0x1f;const EXPONENT_MASK = 0x3fff;const COMBINATION_INFINITY = 30;const COMBINATION_NAN = 31;
function isDigit(value: string): boolean { return !isNaN(parseInt(value, 10));}
function divideu128(value: { parts: [number, number, number, number] }) { const DIVISOR = Long.fromNumber(1000 * 1000 * 1000); let _rem = Long.fromNumber(0);
if ( !value.parts[0] && !value.parts[1] && !value.parts[2] && !value.parts[3] ) { return { quotient: value, rem: _rem }; }
for (let i = 0; i <= 3; i++) { _rem = _rem.shiftLeft(32); _rem = _rem.add(new Long(value.parts[i], 0)); value.parts[i] = _rem.div(DIVISOR).low; _rem = _rem.modulo(DIVISOR); }
return { quotient: value, rem: _rem };}
function multiply64x2(left: Long, right: Long): { high: Long; low: Long } { if (!left && !right) { return { high: Long.fromNumber(0), low: Long.fromNumber(0) }; }
const leftHigh = left.shiftRightUnsigned(32); const leftLow = new Long(left.getLowBits(), 0); const rightHigh = right.shiftRightUnsigned(32); const rightLow = new Long(right.getLowBits(), 0);
let productHigh = leftHigh.multiply(rightHigh); let productMid = leftHigh.multiply(rightLow); const productMid2 = leftLow.multiply(rightHigh); let productLow = leftLow.multiply(rightLow);
productHigh = productHigh.add(productMid.shiftRightUnsigned(32)); productMid = new Long(productMid.getLowBits(), 0) .add(productMid2) .add(productLow.shiftRightUnsigned(32));
productHigh = productHigh.add(productMid.shiftRightUnsigned(32)); productLow = productMid.shiftLeft(32).add( new Long(productLow.getLowBits(), 0), );
return { high: productHigh, low: productLow };}
function lessThan(left: Long, right: Long): boolean { const uhleft = left.high >>> 0; const uhright = right.high >>> 0;
if (uhleft < uhright) { return true; }
if (uhleft === uhright) { const ulleft = left.low >>> 0; const ulright = right.low >>> 0; if (ulleft < ulright) return true; }
return false;}
function invalidErr(string: string, message: string) { throw new BSONTypeError( `"${string}" is not a valid Decimal128 string - ${message}`, );}
export interface Decimal128Extended { $numberDecimal: string;}
export class Decimal128 { _bsontype = "Decimal128"; readonly bytes!: Uint8Array;
constructor(bytes: Uint8Array | string) { this.bytes = typeof bytes === "string" ? Decimal128.fromString(bytes).bytes : bytes; }
static fromString(representation: string): Decimal128 { let isNegative = false; let sawRadix = false; let foundNonZero = false;
let significantDigits = 0; let nDigitsRead = 0; let nDigits = 0; let radixPosition = 0; let firstNonZero = 0;
const digits = [0]; let nDigitsStored = 0; let digitsInsert = 0; let firstDigit = 0; let lastDigit = 0;
let exponent = 0; let i = 0; let significandHigh = new Long(0, 0); let significandLow = new Long(0, 0); let biasedExponent = 0;
let index = 0;
if (representation.length >= 7000) { throw new BSONTypeError( `${representation} not a valid Decimal128 string`, ); }
const stringMatch = representation.match(PARSE_STRING_REGEXP); const infMatch = representation.match(PARSE_INF_REGEXP); const nanMatch = representation.match(PARSE_NAN_REGEXP);
if ( (!stringMatch && !infMatch && !nanMatch) || representation.length === 0 ) { throw new BSONTypeError( `${representation} not a valid Decimal128 string`, ); }
if (stringMatch) {
const unsignedNumber = stringMatch[2];
const e = stringMatch[4]; const expSign = stringMatch[5]; const expNumber = stringMatch[6];
if (e && expNumber === undefined) { invalidErr(representation, "missing exponent power"); }
if (e && unsignedNumber === undefined) { invalidErr(representation, "missing exponent base"); }
if (e === undefined && (expSign || expNumber)) { invalidErr(representation, "missing e before exponent"); } }
if (representation[index] === "+" || representation[index] === "-") { isNegative = representation[index++] === "-"; }
if (!isDigit(representation[index]) && representation[index] !== ".") { if (representation[index] === "i" || representation[index] === "I") { return new Decimal128( new Uint8Array( isNegative ? INF_NEGATIVE_BUFFER : INF_POSITIVE_BUFFER, ), ); } if (representation[index] === "N") { return new Decimal128(new Uint8Array(NAN_BUFFER)); } }
while (isDigit(representation[index]) || representation[index] === ".") { if (representation[index] === ".") { if (sawRadix) invalidErr(representation, "contains multiple periods");
sawRadix = true; index += 1; continue; }
if ( nDigitsStored < 34 && (representation[index] !== "0" || foundNonZero) ) { if (!foundNonZero) { firstNonZero = nDigitsRead; }
foundNonZero = true;
digits[digitsInsert++] = parseInt(representation[index], 10); nDigitsStored += 1; }
if (foundNonZero) nDigits += 1; if (sawRadix) radixPosition += 1;
nDigitsRead += 1; index += 1; }
if (sawRadix && !nDigitsRead) { throw new BSONTypeError( `${representation} not a valid Decimal128 string`, ); }
if (representation[index] === "e" || representation[index] === "E") { const match = representation.substr(++index).match(EXPONENT_REGEX);
if (!match || !match[2]) { return new Decimal128(new Uint8Array(NAN_BUFFER)); }
exponent = parseInt(match[0], 10);
index += match[0].length; }
if (representation[index]) { return new Decimal128(new Uint8Array(NAN_BUFFER)); }
firstDigit = 0;
if (!nDigitsStored) { firstDigit = 0; lastDigit = 0; digits[0] = 0; nDigits = 1; nDigitsStored = 1; significantDigits = 0; } else { lastDigit = nDigitsStored - 1; significantDigits = nDigits; if (significantDigits !== 1) { while (digits[firstNonZero + significantDigits - 1] === 0) { significantDigits -= 1; } } }
exponent = exponent <= radixPosition && radixPosition - exponent > 1 << 14 ? EXPONENT_MIN : exponent - radixPosition;
while (exponent > EXPONENT_MAX) { lastDigit += 1;
if (lastDigit - firstDigit > MAX_DIGITS) { const digitsString = digits.join(""); if (digitsString.match(/^0+$/)) { exponent = EXPONENT_MAX; break; }
invalidErr(representation, "overflow"); } exponent -= 1; }
while (exponent < EXPONENT_MIN || nDigitsStored < nDigits) { if (lastDigit === 0 && significantDigits < nDigitsStored) { exponent = EXPONENT_MIN; significantDigits = 0; break; }
if (nDigitsStored < nDigits) { nDigits -= 1; } else { lastDigit -= 1; }
if (exponent < EXPONENT_MAX) { exponent += 1; } else { const digitsString = digits.join(""); if (digitsString.match(/^0+$/)) { exponent = EXPONENT_MAX; break; } invalidErr(representation, "overflow"); } }
if (lastDigit - firstDigit + 1 < significantDigits) { let endOfString = nDigitsRead;
if (sawRadix) { firstNonZero += 1; endOfString += 1; } if (isNegative) { firstNonZero += 1; endOfString += 1; }
const roundDigit = parseInt( representation[firstNonZero + lastDigit + 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; } } } }
if (roundBit) { let dIdx = lastDigit;
for (; dIdx >= 0; dIdx--) { if (++digits[dIdx] > 9) { digits[dIdx] = 0;
if (dIdx === 0) { if (exponent < EXPONENT_MAX) { exponent += 1; digits[dIdx] = 1; } else { return new Decimal128( new Uint8Array( isNegative ? INF_NEGATIVE_BUFFER : INF_POSITIVE_BUFFER, ), ); } } } } } }
significandHigh = Long.fromNumber(0); significandLow = Long.fromNumber(0);
if (significantDigits === 0) { significandHigh = Long.fromNumber(0); significandLow = Long.fromNumber(0); } else if (lastDigit - firstDigit < 17) { let dIdx = firstDigit; significandLow = Long.fromNumber(digits[dIdx++]); significandHigh = new Long(0, 0);
for (; dIdx <= lastDigit; dIdx++) { significandLow = significandLow.multiply(Long.fromNumber(10)); significandLow = significandLow.add(Long.fromNumber(digits[dIdx])); } } else { let dIdx = firstDigit; significandHigh = Long.fromNumber(digits[dIdx++]);
for (; dIdx <= lastDigit - 17; dIdx++) { significandHigh = significandHigh.multiply(Long.fromNumber(10)); significandHigh = significandHigh.add(Long.fromNumber(digits[dIdx])); }
significandLow = Long.fromNumber(digits[dIdx++]);
for (; dIdx <= lastDigit; dIdx++) { significandLow = significandLow.multiply(Long.fromNumber(10)); significandLow = significandLow.add(Long.fromNumber(digits[dIdx])); } }
const significand = multiply64x2( significandHigh, Long.fromString("100000000000000000"), ); significand.low = significand.low.add(significandLow);
if (lessThan(significand.low, significandLow)) { significand.high = significand.high.add(Long.fromNumber(1)); }
biasedExponent = exponent + EXPONENT_BIAS; const dec = { low: Long.fromNumber(0), high: Long.fromNumber(0) };
if ( significand.high.shiftRightUnsigned(49).and(Long.fromNumber(1)).equals( Long.fromNumber(1), ) ) { dec.high = dec.high.or(Long.fromNumber(0x3).shiftLeft(61)); dec.high = dec.high.or( Long.fromNumber(biasedExponent).and( Long.fromNumber(0x3f_ff).shiftLeft(47), ), ); dec.high = dec.high.or( significand.high.and(Long.fromNumber(0x7f_ff_ff_ff_ff_ff)), ); } else { dec.high = dec.high.or( Long.fromNumber(biasedExponent & 0x3f_ff).shiftLeft(49), ); dec.high = dec.high.or( significand.high.and(Long.fromNumber(0x1_ff_ff_ff_ff_ff_ff)), ); }
dec.low = significand.low;
if (isNegative) { dec.high = dec.high.or(Long.fromString("9223372036854775808")); }
const buffer = new Uint8Array(16); index = 0;
buffer[index++] = dec.low.low & 0xff; buffer[index++] = (dec.low.low >> 8) & 0xff; buffer[index++] = (dec.low.low >> 16) & 0xff; buffer[index++] = (dec.low.low >> 24) & 0xff; buffer[index++] = dec.low.high & 0xff; buffer[index++] = (dec.low.high >> 8) & 0xff; buffer[index++] = (dec.low.high >> 16) & 0xff; buffer[index++] = (dec.low.high >> 24) & 0xff;
buffer[index++] = dec.high.low & 0xff; buffer[index++] = (dec.high.low >> 8) & 0xff; buffer[index++] = (dec.high.low >> 16) & 0xff; buffer[index++] = (dec.high.low >> 24) & 0xff; buffer[index++] = dec.high.high & 0xff; buffer[index++] = (dec.high.high >> 8) & 0xff; buffer[index++] = (dec.high.high >> 16) & 0xff; buffer[index++] = (dec.high.high >> 24) & 0xff;
return new Decimal128(buffer); }
toString(): string {
let biasedExponent; let significandDigits = 0; const significand = new Array<number>(36); for (let i = 0; i < significand.length; i++) significand[i] = 0; let index = 0;
let isZero = false;
let significandMsb; let significand128: { parts: [number, number, number, number] } = { parts: [0, 0, 0, 0], }; let j; let k;
const string: string[] = [];
index = 0;
const buffer = this.bytes;
const low = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); const midl = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);
const midh = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); const high = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24);
index = 0;
const dec = { low: new Long(low, midl), high: new Long(midh, high), };
if (dec.high.lessThan(Long.ZERO)) { string.push("-"); }
const combination = (high >> 26) & COMBINATION_MASK;
if (combination >> 3 === 3) { if (combination === COMBINATION_INFINITY) { return `${string.join("")}Infinity`; } if (combination === COMBINATION_NAN) { return "NaN"; } biasedExponent = (high >> 15) & EXPONENT_MASK; significandMsb = 0x08 + ((high >> 14) & 0x01); } else { significandMsb = (high >> 14) & 0x07; biasedExponent = (high >> 17) & EXPONENT_MASK; }
const exponent = biasedExponent - EXPONENT_BIAS;
significand128.parts[0] = (high & 0x3f_ff) + ((significandMsb & 0xf) << 14); significand128.parts[1] = midh; significand128.parts[2] = midl; significand128.parts[3] = low;
if ( significand128.parts[0] === 0 && significand128.parts[1] === 0 && significand128.parts[2] === 0 && significand128.parts[3] === 0 ) { isZero = true; } else { for (k = 3; k >= 0; k--) { let leastDigits = 0; const result = divideu128(significand128); significand128 = result.quotient; leastDigits = result.rem.low;
if (!leastDigits) continue;
for (j = 8; j >= 0; j--) { significand[k * 9 + j] = leastDigits % 10; leastDigits = Math.floor(leastDigits / 10); } } }
if (isZero) { significandDigits = 1; significand[index] = 0; } else { significandDigits = 36; while (!significand[index]) { significandDigits -= 1; index += 1; } }
const scientificExponent = significandDigits - 1 + exponent;
if ( scientificExponent >= 34 || scientificExponent <= -7 || exponent > 0 ) {
if (significandDigits > 34) { string.push(`${0}`); if (exponent > 0) string.push(`E+${exponent}`); else if (exponent < 0) string.push(`E${exponent}`); return string.join(""); }
string.push(`${significand[index++]}`); significandDigits -= 1;
if (significandDigits) { string.push("."); }
for (let i = 0; i < significandDigits; i++) { string.push(`${significand[index++]}`); }
string.push("E"); if (scientificExponent > 0) { string.push(`+${scientificExponent}`); } else { string.push(`${scientificExponent}`); } } else { if (exponent >= 0) { for (let i = 0; i < significandDigits; i++) { string.push(`${significand[index++]}`); } } else { let radixPosition = significandDigits + exponent;
if (radixPosition > 0) { for (let i = 0; i < radixPosition; i++) { string.push(`${significand[index++]}`); } } else { string.push("0"); }
string.push("."); while (radixPosition++ < 0) { string.push("0"); }
for ( let i = 0; i < significandDigits - Math.max(radixPosition - 1, 0); i++ ) { string.push(`${significand[index++]}`); } } }
return string.join(""); }
toExtendedJSON(): Decimal128Extended { return { $numberDecimal: this.toString() }; }
static fromExtendedJSON(doc: Decimal128Extended): Decimal128 { return Decimal128.fromString(doc.$numberDecimal); }
toJSON(): Decimal128Extended { return { $numberDecimal: this.toString() }; }
[Symbol.for("Deno.customInspect")](): string { return `new Decimal128("${this.toString()}")`; }}