ripple-binary-codec
Version:
XRP Ledger binary codec
158 lines • 5.85 kB
JavaScript
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
;