UNPKG

ripple-binary-codec

Version:
208 lines 8.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.STNumber = void 0; const serialized_type_1 = require("./serialized-type"); const utils_1 = require("../utils"); /** * Constants for mantissa and exponent normalization per XRPL Number spec. * These define allowed magnitude for mantissa and exponent after normalization. */ const MIN_MANTISSA = BigInt('1000000000000000'); const MAX_MANTISSA = BigInt('9999999999999999'); const MIN_EXPONENT = -32768; const MAX_EXPONENT = 32768; const DEFAULT_VALUE_EXPONENT = -2147483648; /** * Extract mantissa, exponent, and sign from a number string. * * @param val - The string representing the number (may be integer, decimal, or scientific notation). * @returns Object containing mantissa (BigInt), exponent (number), and isNegative (boolean). * @throws Error if the string cannot be parsed as a valid number. * * Examples: * '123' -> { mantissa: 123n, exponent: 0, isNegative: false } * '-00123.45' -> { mantissa: -12345n, exponent: -2, isNegative: true } * '+7.1e2' -> { mantissa: 71n, exponent: -1 + 2 = 1, isNegative: false } */ function extractNumberPartsFromString(val) { /** * Regex for parsing decimal/float/scientific number strings with optional sign, integer, decimal, and exponent parts. * * Pattern: /^([-+]?)([0-9]+)(?:\.([0-9]+))?(?:[eE]([+-]?[0-9]+))?$/ * * Breakdown: * 1. ([-+]?) - Optional '+' or '-' sign at the start. * 2. ([0-9]+) - Integer part: one or more digits (leading zeros allowed). * 3. (?:\.([0-9]+))? - Optional decimal point followed by one or more digits. * 4. (?:[eE]([+-]?[0-9]+))? - Optional exponent, starting with 'e' or 'E', optional sign, and digits. * * Notes: * - Leading zeros are accepted and normalized by code after parsing. * - Empty decimal ('123.') and missing integer ('.456') are NOT matched—must be fully specified. */ const regex = /^([-+]?)([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; // Remove leading zeros (unless the entire intPart is zeros) const cleanIntPart = intPart.replace(/^0+(?=\d)/, '') || '0'; let mantissaStr = cleanIntPart; 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 the mantissa and exponent to XRPL constraints. * * Ensures that after normalization, the mantissa is between MIN_MANTISSA and MAX_MANTISSA (unless zero). * Adjusts the exponent as needed by shifting the mantissa left/right (multiplying/dividing by 10). * * @param mantissa - The unnormalized mantissa (BigInt). * @param exponent - The unnormalized exponent (number). * @returns An object with normalized mantissa and exponent. * @throws Error if the number cannot be normalized within allowed exponent range. */ 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 }; } /** * STNumber: Encodes XRPL's "Number" type. * * - Always encoded as 12 bytes: 8-byte signed mantissa, 4-byte signed exponent, both big-endian. * - Can only be constructed from a valid number string or another STNumber instance. * * Usage: * STNumber.from("1.2345e5") * STNumber.from("-123") * STNumber.fromParser(parser) */ class STNumber extends serialized_type_1.SerializedType { /** * Construct a STNumber from 12 bytes (8 for mantissa, 4 for exponent). * @param bytes - 12-byte Uint8Array * @throws Error if input is not a Uint8Array of length 12. */ constructor(bytes) { const used = bytes !== null && bytes !== void 0 ? bytes : STNumber.defaultBytes; if (!(used instanceof Uint8Array) || used.length !== 12) { throw new Error(`STNumber must be constructed from a 12-byte Uint8Array, got ${used === null || used === void 0 ? void 0 : used.length}`); } super(used); } /** * Construct from a number string (or another STNumber). * * @param value - A string, or STNumber instance. * @returns STNumber instance. * @throws Error if not a string or STNumber. */ static from(value) { if (value instanceof STNumber) { return value; } if (typeof value === 'string') { return STNumber.fromValue(value); } throw new Error('STNumber.from: Only string or STNumber instance is supported'); } /** * Construct from a number string (integer, decimal, or scientific notation). * Handles normalization to XRPL Number constraints. * * @param val - The number as a string (e.g. '1.23', '-123e5'). * @returns STNumber instance * @throws Error if val is not a valid number string. */ static fromValue(val) { const { mantissa, exponent, isNegative } = extractNumberPartsFromString(val); let normalizedMantissa; let normalizedExponent; if (mantissa === BigInt(0) && exponent === 0 && !isNegative) { normalizedMantissa = BigInt(0); normalizedExponent = DEFAULT_VALUE_EXPONENT; } else { ; ({ mantissa: normalizedMantissa, exponent: normalizedExponent } = normalize(mantissa, exponent)); } const bytes = new Uint8Array(12); (0, utils_1.writeInt64BE)(bytes, normalizedMantissa, 0); (0, utils_1.writeInt32BE)(bytes, normalizedExponent, 8); return new STNumber(bytes); } /** * Read a STNumber from a BinaryParser stream (12 bytes). * @param parser - BinaryParser positioned at the start of a number * @returns STNumber instance */ static fromParser(parser) { return new STNumber(parser.read(12)); } /** * Convert this STNumber to a normalized string representation. * The output is decimal or scientific notation, depending on exponent range. * Follows XRPL convention: zero is "0", other values are normalized to a canonical string. * * @returns String representation of the value */ // eslint-disable-next-line complexity -- required toJSON() { const b = this.bytes; if (!b || b.length !== 12) throw new Error('STNumber internal bytes not set or wrong length'); // Signed 64-bit mantissa const mantissa = (0, utils_1.readInt64BE)(b, 0); // Signed 32-bit exponent const exponent = (0, utils_1.readInt32BE)(b, 8); // Special zero: XRPL encodes canonical zero as mantissa=0, exponent=DEFAULT_VALUE_EXPONENT. 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}`; } // Decimal rendering for -25 <= exp <= -5 const isNegative = mantissa < BigInt(0); const mantissaAbs = mantissa < BigInt(0) ? -mantissa : mantissa; const padPrefix = 27; const padSuffix = 23; const mantissaStr = mantissaAbs.toString(); const rawValue = '0'.repeat(padPrefix) + mantissaStr + '0'.repeat(padSuffix); const OFFSET = exponent + 43; const integerPart = rawValue.slice(0, OFFSET).replace(/^0+/, '') || '0'; const fractionPart = rawValue.slice(OFFSET).replace(/0+$/, ''); return `${isNegative ? '-' : ''}${integerPart}${fractionPart ? '.' + fractionPart : ''}`; } } exports.STNumber = STNumber; /** 12 zero bytes, represents canonical zero. */ STNumber.defaultBytes = new Uint8Array(12); //# sourceMappingURL=st-number.js.map