UNPKG

@drift-labs/sdk-browser

Version:
501 lines (500 loc) 21.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BigNum = void 0; const anchor_1 = require("@coral-xyz/anchor"); const assert_1 = require("../assert/assert"); const numericConstants_1 = require("./../constants/numericConstants"); class BigNum { static setLocale(locale) { BigNum.delim = (1.1).toLocaleString(locale).slice(1, 2) || '.'; BigNum.spacer = (1000).toLocaleString(locale).slice(1, 2) || ','; } constructor(val, precisionVal = new anchor_1.BN(0)) { this.toString = (base, length) => this.val.toString(base, length); this.val = new anchor_1.BN(val); this.precision = new anchor_1.BN(precisionVal); } bigNumFromParam(bn) { return anchor_1.BN.isBN(bn) ? BigNum.from(bn) : bn; } add(bn) { (0, assert_1.assert)(bn.precision.eq(this.precision), 'Adding unequal precisions'); return BigNum.from(this.val.add(bn.val), this.precision); } sub(bn) { (0, assert_1.assert)(bn.precision.eq(this.precision), 'Subtracting unequal precisions'); return BigNum.from(this.val.sub(bn.val), this.precision); } mul(bn) { const mulVal = this.bigNumFromParam(bn); return BigNum.from(this.val.mul(mulVal.val), this.precision.add(mulVal.precision)); } /** * Multiplies by another big number then scales the result down by the big number's precision so that we're in the same precision space * @param bn * @returns */ scalarMul(bn) { if (anchor_1.BN.isBN(bn)) return BigNum.from(this.val.mul(bn), this.precision); return BigNum.from(this.val.mul(bn.val), this.precision.add(bn.precision)).shift(bn.precision.neg()); } div(bn) { if (anchor_1.BN.isBN(bn)) return BigNum.from(this.val.div(bn), this.precision); return BigNum.from(this.val.div(bn.val), this.precision.sub(bn.precision)); } /** * Shift precision up or down * @param exponent * @param skipAdjustingPrecision * @returns */ shift(exponent, skipAdjustingPrecision = false) { const shiftVal = typeof exponent === 'number' ? new anchor_1.BN(exponent) : exponent; return BigNum.from(shiftVal.isNeg() ? this.val.div(new anchor_1.BN(10).pow(shiftVal)) : this.val.mul(new anchor_1.BN(10).pow(shiftVal)), skipAdjustingPrecision ? this.precision : this.precision.add(shiftVal)); } /** * Shift to a target precision * @param targetPrecision * @returns */ shiftTo(targetPrecision) { return this.shift(targetPrecision.sub(this.precision)); } /** * Scale the number by a fraction * @param numerator * @param denominator * @returns */ scale(numerator, denominator) { return this.mul(BigNum.from(new anchor_1.BN(numerator))).div(new anchor_1.BN(denominator)); } toPercentage(denominator, precision) { return this.shift(precision) .shift(2, true) .div(denominator) .toPrecision(precision); } gt(bn, ignorePrecision) { const comparisonVal = this.bigNumFromParam(bn); if (!ignorePrecision && !comparisonVal.eq(numericConstants_1.ZERO)) { (0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter'); } return this.val.gt(comparisonVal.val); } lt(bn, ignorePrecision) { const comparisonVal = this.bigNumFromParam(bn); if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) { (0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter'); } return this.val.lt(comparisonVal.val); } gte(bn, ignorePrecision) { const comparisonVal = this.bigNumFromParam(bn); if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) { (0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter'); } return this.val.gte(comparisonVal.val); } lte(bn, ignorePrecision) { const comparisonVal = this.bigNumFromParam(bn); if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) { (0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter'); } return this.val.lte(comparisonVal.val); } eq(bn, ignorePrecision) { const comparisonVal = this.bigNumFromParam(bn); if (!ignorePrecision && !comparisonVal.val.eq(numericConstants_1.ZERO)) { (0, assert_1.assert)(comparisonVal.precision.eq(this.precision), 'Trying to compare numbers with different precision. Yo can opt to ignore precision using the ignorePrecision parameter'); } return this.val.eq(comparisonVal.val); } eqZero() { return this.val.eq(numericConstants_1.ZERO); } gtZero() { return this.val.gt(numericConstants_1.ZERO); } ltZero() { return this.val.lt(numericConstants_1.ZERO); } gteZero() { return this.val.gte(numericConstants_1.ZERO); } lteZero() { return this.val.lte(numericConstants_1.ZERO); } abs() { return new BigNum(this.val.abs(), this.precision); } neg() { return new BigNum(this.val.neg(), this.precision); } /** * Pretty print the underlying value in human-readable form. Depends on precision being correct for the output string to be correct * @returns */ print() { (0, assert_1.assert)(this.precision.gte(numericConstants_1.ZERO), 'Tried to print a BN with precision lower than zero'); const isNeg = this.isNeg(); const plainString = this.abs().toString(); const precisionNum = this.precision.toNumber(); // make a string with at least the precisionNum number of zeroes let printString = [ ...Array(this.precision.toNumber()).fill(0), ...plainString.split(''), ].join(''); // inject decimal printString = printString.substring(0, printString.length - precisionNum) + BigNum.delim + printString.substring(printString.length - precisionNum); // remove leading zeroes printString = printString.replace(/^0+/, ''); // add zero if leading delim if (printString[0] === BigNum.delim) printString = `0${printString}`; // Add minus if negative if (isNeg) printString = `-${printString}`; // remove trailing delim if (printString[printString.length - 1] === BigNum.delim) printString = printString.slice(0, printString.length - 1); return printString; } prettyPrint(useTradePrecision, precisionOverride, decimalOverride) { const [leftSide, rightSide] = this.printShort(useTradePrecision, precisionOverride).split(BigNum.delim); let formattedLeftSide = leftSide; let formattedRightSide = rightSide; // Apply decimal override if specified if (decimalOverride !== undefined) { if (decimalOverride === 0) { formattedRightSide = undefined; } else { // If no decimal part exists, create one with zeros const currentRightSide = rightSide || ''; // Pad with zeros if needed or truncate if too long formattedRightSide = currentRightSide .padEnd(decimalOverride, '0') .substring(0, decimalOverride); } } const isNeg = formattedLeftSide.includes('-'); if (isNeg) { formattedLeftSide = formattedLeftSide.replace('-', ''); } let index = formattedLeftSide.length - 3; while (index >= 1) { const formattedLeftSideArray = formattedLeftSide.split(''); formattedLeftSideArray.splice(index, 0, BigNum.spacer); formattedLeftSide = formattedLeftSideArray.join(''); index -= 3; } return `${isNeg ? '-' : ''}${formattedLeftSide}${formattedRightSide ? `${BigNum.delim}${formattedRightSide}` : ''}`; } /** * Print and remove unnecessary trailing zeroes * @returns */ printShort(useTradePrecision, precisionOverride) { const printVal = precisionOverride ? this.toPrecision(precisionOverride) : useTradePrecision ? this.toTradePrecision() : this.print(); if (!printVal.includes(BigNum.delim)) return printVal; return printVal.replace(/0+$/g, '').replace(/\.$/, '').replace(/,$/, ''); } debug() { console.log(`${this.toString()} | ${this.print()} | ${this.precision.toString()}`); } /** * Pretty print with the specified number of decimal places * @param fixedPrecision * @returns */ toFixed(fixedPrecision, rounded = false) { if (rounded) { return this.toRounded(fixedPrecision).toFixed(fixedPrecision); } const printString = this.print(); const [leftSide, rightSide] = printString.split(BigNum.delim); const filledRightSide = [ ...(rightSide !== null && rightSide !== void 0 ? rightSide : '').slice(0, fixedPrecision), ...Array(fixedPrecision).fill('0'), ] .slice(0, fixedPrecision) .join(''); return `${leftSide}${BigNum.delim}${filledRightSide}`; } getZeroes(count) { return new Array(Math.max(count, 0)).fill('0').join(''); } toRounded(roundingPrecision) { const printString = this.toString(); let shouldRoundUp = false; const roundingDigitChar = printString[roundingPrecision]; if (roundingDigitChar) { const roundingDigitVal = Number(roundingDigitChar); if (roundingDigitVal >= 5) shouldRoundUp = true; } if (shouldRoundUp) { const valueWithRoundedPrecisionAdded = this.add(BigNum.from(new anchor_1.BN(10).pow(new anchor_1.BN(printString.length - roundingPrecision)), this.precision)); const roundedUpPrintString = valueWithRoundedPrecisionAdded.toString().slice(0, roundingPrecision) + this.getZeroes(printString.length - roundingPrecision); return BigNum.from(roundedUpPrintString, this.precision); } else { const roundedDownPrintString = printString.slice(0, roundingPrecision) + this.getZeroes(printString.length - roundingPrecision); return BigNum.from(roundedDownPrintString, this.precision); } } /** * Pretty print to the specified number of significant figures * @param fixedPrecision * @returns */ toPrecision(fixedPrecision, trailingZeroes = false, rounded = false) { if (rounded) { return this.toRounded(fixedPrecision).toPrecision(fixedPrecision, trailingZeroes); } const isNeg = this.isNeg(); const printString = this.abs().print(); const thisString = this.abs().toString(); // Handle small numbers (those with leading zeros after decimal) if (printString.includes(BigNum.delim)) { const [leftSide, rightSide] = printString.split(BigNum.delim); if (leftSide === '0' && rightSide) { // Count leading zeros let leadingZeros = 0; for (let i = 0; i < rightSide.length; i++) { if (rightSide[i] === '0') { leadingZeros++; } else { break; } } // Get significant digits starting after leading zeros const significantPart = rightSide.slice(leadingZeros); let significantDigits = significantPart.slice(0, fixedPrecision); // Remove trailing zeros if not requested if (!trailingZeroes) { significantDigits = significantDigits.replace(/0+$/, ''); } // Only return result if we have significant digits if (significantDigits.length > 0) { const result = `${isNeg ? '-' : ''}0${BigNum.delim}${rightSide.slice(0, leadingZeros)}${significantDigits}`; return result; } } } let precisionPrintString = printString.slice(0, fixedPrecision + 1); if (!printString.includes(BigNum.delim) && thisString.length < fixedPrecision) { const precisionMismatch = fixedPrecision - thisString.length; return BigNum.from((isNeg ? '-' : '') + thisString + this.getZeroes(precisionMismatch), precisionMismatch).toPrecision(fixedPrecision, trailingZeroes); } if (!precisionPrintString.includes(BigNum.delim) || precisionPrintString[precisionPrintString.length - 1] === BigNum.delim) { precisionPrintString = printString.slice(0, fixedPrecision); } const pointsOfPrecision = precisionPrintString.replace(BigNum.delim, '').length; if (pointsOfPrecision < fixedPrecision) { precisionPrintString = [ ...precisionPrintString.split(''), ...Array(fixedPrecision - pointsOfPrecision).fill('0'), ].join(''); } if (!precisionPrintString.includes(BigNum.delim)) { const delimFullStringLocation = printString.indexOf(BigNum.delim); let skipExponent = false; if (delimFullStringLocation === -1) { // no decimal, not missing any precision skipExponent = true; } if (precisionPrintString[precisionPrintString.length - 1] === BigNum.delim) { // decimal is at end of string, not missing any precision, do nothing skipExponent = true; } if (printString.indexOf(BigNum.delim) === fixedPrecision) { // decimal is at end of string, not missing any precision, do nothing skipExponent = true; } if (!skipExponent) { const exponent = delimFullStringLocation - fixedPrecision; if (trailingZeroes) { precisionPrintString = `${precisionPrintString}${Array(exponent) .fill('0') .join('')}`; } else { precisionPrintString = `${precisionPrintString}e${exponent}`; } } } return `${isNeg ? '-' : ''}${precisionPrintString}`; } toTradePrecision(rounded = false) { return this.toPrecision(6, true, rounded); } /** * Print dollar formatted value. Defaults to fixed decimals two unless a given precision is given. * @param useTradePrecision * @param precisionOverride * @returns */ toNotional(useTradePrecision, precisionOverride, decimalOverride) { var _a; const prefix = `${this.lt(BigNum.zero()) ? `-` : ``}$`; const usingCustomPrecision = true && (useTradePrecision || precisionOverride || decimalOverride); let val = usingCustomPrecision ? this.prettyPrint(useTradePrecision, precisionOverride, decimalOverride) : BigNum.fromPrint(this.toFixed(2), new anchor_1.BN(2)).prettyPrint(); // Append trailing zeroes out to 2 decimal places if not using custom precision if (!usingCustomPrecision) { const [_, rightSide] = val.split(BigNum.delim); const trailingLength = (_a = rightSide === null || rightSide === void 0 ? void 0 : rightSide.length) !== null && _a !== void 0 ? _a : 0; if (trailingLength === 0) { val = `${val}${BigNum.delim}00`; } else if (trailingLength === 1) { val = `${val}0`; } } return `${prefix}${val.replace('-', '')}`; } toMillified(precision = 3, rounded = false, type = 'financial') { if (rounded) { return this.toRounded(precision).toMillified(precision); } const isNeg = this.isNeg(); const stringVal = this.abs().print(); const [leftSide] = stringVal.split(BigNum.delim); if (!leftSide) { return this.shift(new anchor_1.BN(precision)).toPrecision(precision, true); } if (leftSide.length <= precision) { return this.toPrecision(precision); } if (leftSide.length <= 3) { return this.shift(new anchor_1.BN(precision)).toPrecision(precision, true); } const unitTicks = type === 'financial' ? ['', 'K', 'M', 'B', 'T', 'Q'] : ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // TODO -- handle nubers which are larger than the max unit tick const unitNumber = Math.floor((leftSide.length - 1) / 3); const unit = unitTicks[unitNumber]; let leadDigits = leftSide.slice(0, precision); if (leadDigits.length < precision) { leadDigits = [ ...leadDigits.split(''), ...Array(precision - leadDigits.length).fill('0'), ].join(''); } const decimalLocation = leftSide.length - 3 * unitNumber; let leadString = ''; if (decimalLocation >= precision) { leadString = `${leadDigits}`; } else { leadString = `${leadDigits.slice(0, decimalLocation)}${BigNum.delim}${leadDigits.slice(decimalLocation)}`; } return `${isNeg ? '-' : ''}${leadString}${unit}`; } toJSON() { return { val: this.val.toString(), precision: this.precision.toString(), }; } isNeg() { return this.lt(numericConstants_1.ZERO, true); } isPos() { return !this.isNeg(); } /** * Get the numerical value of the BigNum. This can break if the BigNum is too large. * @returns */ toNum() { let printedValue = this.print(); // Must convert any non-US delimiters and spacers to US format before using parseFloat if (BigNum.delim !== '.' || BigNum.spacer !== ',') { printedValue = printedValue .split('') .map((char) => { if (char === BigNum.delim) return '.'; if (char === BigNum.spacer) return ','; return char; }) .join(''); } return parseFloat(printedValue); } static fromJSON(json) { return BigNum.from(new anchor_1.BN(json.val), new anchor_1.BN(json.precision)); } /** * Create a BigNum instance * @param val * @param precision * @returns */ static from(val = numericConstants_1.ZERO, precision) { (0, assert_1.assert)(new anchor_1.BN(precision).lt(new anchor_1.BN(100)), 'Tried to create a bignum with precision higher than 10^100'); return new BigNum(val, precision); } /** * Create a BigNum instance from a printed BigNum * @param val * @param precisionOverride * @returns */ static fromPrint(val, precisionShift) { var _a, _b; // Handle empty number edge cases if (!val) return BigNum.from(numericConstants_1.ZERO, precisionShift); if (!val.replace(BigNum.delim, '')) { return BigNum.from(numericConstants_1.ZERO, precisionShift); } if (val.includes('e')) val = (+val).toFixed((_a = precisionShift === null || precisionShift === void 0 ? void 0 : precisionShift.toNumber()) !== null && _a !== void 0 ? _a : 9); // prevent small numbers e.g. 3.1e-8, use assume max precision 9 as default const sides = val.split(BigNum.delim); const rightSide = sides[1]; const leftSide = sides[0].replace(/\s/g, ''); const bnInput = `${leftSide !== null && leftSide !== void 0 ? leftSide : ''}${rightSide !== null && rightSide !== void 0 ? rightSide : ''}`; const rawBn = new anchor_1.BN(bnInput); const rightSideLength = (_b = rightSide === null || rightSide === void 0 ? void 0 : rightSide.length) !== null && _b !== void 0 ? _b : 0; const totalShift = precisionShift ? precisionShift.sub(new anchor_1.BN(rightSideLength)) : numericConstants_1.ZERO; return BigNum.from(rawBn, precisionShift).shift(totalShift, true); } static max(a, b) { return a.gt(b) ? a : b; } static min(a, b) { return a.lt(b) ? a : b; } static zero(precision) { return BigNum.from(0, precision); } } exports.BigNum = BigNum; BigNum.delim = '.'; BigNum.spacer = ',';