UNPKG

ripple-binary-codec

Version:
158 lines 5.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SerializedNumber = void 0; const serialized_type_1 = require("./serialized-type"); /** * Limits of representation after normalization of mantissa and exponent */ const MIN_MANTISSA = BigInt('1000000000000000'); const MAX_MANTISSA = BigInt('9999999999999999'); const MIN_EXPONENT = -32768; const MAX_EXPONENT = 32768; const DEFAULT_VALUE_EXPONENT = -2147483648; /** * Helper: Write 64-bit big-endian integer to buffer. */ function add64(val) { const buf = new Uint8Array(8); for (let i = 0; i < 8; i++) { buf[7 - i] = Number((val >> BigInt(8 * i)) & BigInt(0xff)); } return buf; } /** * Helper: Write 32-bit big-endian integer to buffer. */ function add32(val) { const buf = new Uint8Array(4); for (let i = 0; i < 4; i++) { buf[3 - i] = (val >> (8 * i)) & 0xff; } return buf; } /** * Extract mantissa, exponent, and sign from string. */ function extractNumberPartsFromString(val) { // Regex: sign, integer part, optional .fraction, optional e/E exponent const regex = /^([-+]?)(0|[1-9][0-9]*)(?:\.([0-9]+))?(?:[eE]([+-]?[0-9]+))?$/; const match = regex.exec(val); if (!match) throw new Error(`Unable to parse number from string: ${val}`); const [, sign, intPart, fracPart, expPart] = match; let mantissaStr = intPart; let exponent = 0; if (fracPart) { mantissaStr += fracPart; exponent -= fracPart.length; } if (expPart) exponent += parseInt(expPart, 10); let mantissa = BigInt(mantissaStr); if (sign === '-') mantissa = -mantissa; const isNegative = mantissa < BigInt(0); return { mantissa, exponent, isNegative }; } /** * Normalize mantissa and exponent to XRP Number constraints. */ function normalize(mantissa, exponent) { let m = mantissa < BigInt(0) ? -mantissa : mantissa; const isNegative = mantissa < BigInt(0); while (m !== BigInt(0) && m < MIN_MANTISSA && exponent > MIN_EXPONENT) { exponent -= 1; m *= BigInt(10); } while (m > MAX_MANTISSA) { if (exponent >= MAX_EXPONENT) throw new Error('Mantissa and exponent are too large'); exponent += 1; m /= BigInt(10); } if (isNegative) m = -m; return { mantissa: m, exponent }; } /** * SerializedType for the XRPL Number type (12 bytes). */ class SerializedNumber extends serialized_type_1.SerializedType { constructor(bytes) { super(bytes); } /** * Creates a SerializedNumber from a string value. * Accepts only string input to avoid precision loss (as per XRPL and blockchain best practices). * Throws if a number or bigint is passed. * @param value string representing the number (decimal, integer, or scientific notation) * @returns SerializedNumber instance */ static from(value) { if (typeof value !== 'string') { throw new Error('SerializedNumber.from: value must be a string representing the number. ' + 'Numbers and bigints are not accepted to avoid precision loss. ' + 'Convert your value to a string first.'); } return SerializedNumber.fromValue(value); } static fromValue(val) { const { mantissa, exponent, isNegative } = extractNumberPartsFromString(val); let normalizedMantissa, normalizedExponent; if (mantissa === BigInt(0) && exponent === 0 && !isNegative) { normalizedMantissa = BigInt(0); normalizedExponent = DEFAULT_VALUE_EXPONENT; } else { ; ({ mantissa: normalizedMantissa, exponent: normalizedExponent } = normalize(mantissa, exponent)); } const mantissaBytes = add64(normalizedMantissa); const exponentBytes = add32(normalizedExponent); const bytes = new Uint8Array(12); bytes.set(mantissaBytes, 0); bytes.set(exponentBytes, 8); return new SerializedNumber(bytes); } static fromParser(parser) { return new SerializedNumber(parser.read(12)); } // eslint-disable-next-line complexity -- needed toJSON() { // Decompose mantissa (signed 64-bit) and exponent (signed 32-bit) const b = this.bytes; let mantissa = BigInt(0); for (let i = 0; i < 8; i++) { mantissa = (mantissa << BigInt(8)) | BigInt(b[i]); } // interpret as signed 64 if (b[0] & 0x80) mantissa -= BigInt('0x10000000000000000'); let exponent = (b[8] << 24) | (b[9] << 16) | (b[10] << 8) | b[11]; if (b[8] & 0x80) exponent = exponent - 0x100000000; // Special case: 0 if (mantissa === BigInt(0) && exponent === DEFAULT_VALUE_EXPONENT) { return '0'; } if (exponent === 0) return mantissa.toString(); // Use scientific notation for small/large exponents, decimal otherwise if (exponent < -25 || exponent > -5) { return `${mantissa}e${exponent}`; } // Else, output as decimal with a dot const isNegative = mantissa < BigInt(0); let mantissaAbs = mantissa < BigInt(0) ? -mantissa : mantissa; const padPrefix = 27, padSuffix = 23; let mantissaStr = mantissaAbs.toString(); let rawValue = '0'.repeat(padPrefix) + mantissaStr + '0'.repeat(padSuffix); const OFFSET = exponent + 43; let integerPart = rawValue.slice(0, OFFSET).replace(/^0+/, '') || '0'; let fractionPart = rawValue.slice(OFFSET).replace(/0+$/, ''); return `${isNegative ? '-' : ''}${integerPart}${fractionPart ? '.' + fractionPart : ''}`; } } exports.SerializedNumber = SerializedNumber; //# sourceMappingURL=number.js.map