UNPKG

hsd

Version:
217 lines (175 loc) 4.37 kB
/*! * fixed.js - fixed number parsing * Copyright (c) 2017, Christopher Jeffrey (MIT License). * https://github.com/handshake-org/hsd */ 'use strict'; const assert = require('bsert'); /** * Convert int to fixed number string and reduce by a * power of ten (uses no floating point arithmetic). * @param {Number} num * @param {Number} exp - Number of decimal places. * @returns {String} Fixed number string. */ exports.encode = function encode(num, exp) { assert(Number.isSafeInteger(num), 'Invalid integer value.'); let sign = ''; if (num < 0) { num = -num; sign = '-'; } const mult = pow10(exp); const nlo = num % mult; const nhi = (num - nlo) / mult; let lo = nlo.toString(10); const hi = nhi.toString(10); while (lo.length < exp) lo = '0' + lo; lo = lo.replace(/0+$/, ''); assert(lo.length <= exp, 'Invalid integer value.'); if (lo.length === 0) lo = '0'; if (exp === 0) return `${sign}${hi}`; return `${sign}${hi}.${lo}`; }; /** * Parse a fixed number string and multiply by a * power of ten (uses no floating point arithmetic). * @param {String} str * @param {Number} exp - Number of decimal places. * @returns {Number} Integer. */ exports.decode = function decode(str, exp) { assert(typeof str === 'string'); assert(str.length <= 32, 'Fixed number string too large.'); let sign = 1; if (str.length > 0 && str[0] === '-') { str = str.substring(1); sign = -1; } let hi = str; let lo = '0'; const index = str.indexOf('.'); if (index !== -1) { hi = str.substring(0, index); lo = str.substring(index + 1); } hi = hi.replace(/^0+/, ''); lo = lo.replace(/0+$/, ''); assert(hi.length <= 16 - exp, 'Fixed number string exceeds 2^53-1.'); assert(lo.length <= exp, 'Too many decimal places in fixed number string.'); if (hi.length === 0) hi = '0'; while (lo.length < exp) lo += '0'; if (lo.length === 0) lo = '0'; assert(/^\d+$/.test(hi) && /^\d+$/.test(lo), 'Non-numeric characters in fixed number string.'); const nhi = parseInt(hi, 10); const nlo = parseInt(lo, 10); const mult = pow10(exp); const maxLo = modSafe(mult); const maxHi = divSafe(mult); assert(nhi < maxHi || (nhi === maxHi && nlo <= maxLo), 'Fixed number string exceeds 2^53-1.'); return sign * (nhi * mult + nlo); }; /** * Convert int to float and reduce by a power * of ten (uses no floating point arithmetic). * @param {Number} num * @param {Number} exp - Number of decimal places. * @returns {Number} Double float. */ exports.toFloat = function toFloat(num, exp) { return parseFloat(exports.encode(num, exp)); }; /** * Parse a double float number and multiply by a * power of ten (uses no floating point arithmetic). * @param {Number} num * @param {Number} exp - Number of decimal places. * @returns {Number} Integer. */ exports.fromFloat = function fromFloat(num, exp) { assert(typeof num === 'number' && isFinite(num)); assert(Number.isSafeInteger(exp)); return exports.decode(num.toFixed(exp), exp); }; /* * Helpers */ function pow10(exp) { switch (exp) { case 0: return 1; case 1: return 10; case 2: return 100; case 3: return 1000; case 4: return 10000; case 5: return 100000; case 6: return 1000000; case 7: return 10000000; case 8: return 100000000; } throw new Error('Exponent is too large.'); } function modSafe(mod) { switch (mod) { case 1: return 0; case 10: return 1; case 100: return 91; case 1000: return 991; case 10000: return 991; case 100000: return 40991; case 1000000: return 740991; case 10000000: return 4740991; case 100000000: return 54740991; } throw new Error('Exponent is too large.'); } function divSafe(div) { switch (div) { case 1: return 9007199254740991; case 10: return 900719925474099; case 100: return 90071992547409; case 1000: return 9007199254740; case 10000: return 900719925474; case 100000: return 90071992547; case 1000000: return 9007199254; case 10000000: return 900719925; case 100000000: return 90071992; } throw new Error('Exponent is too large.'); }