217 lines
4.2 KiB
JavaScript
217 lines
4.2 KiB
JavaScript
/*!
|
|
* fixed.js - fixed number parsing
|
|
* Copyright (c) 2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'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);
|
|
|
|
let lo = num % mult;
|
|
let hi = (num - lo) / mult;
|
|
|
|
lo = lo.toString(10);
|
|
hi = hi.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.');
|
|
|
|
hi = parseInt(hi, 10);
|
|
lo = parseInt(lo, 10);
|
|
|
|
const mult = pow10(exp);
|
|
const maxLo = modSafe(mult);
|
|
const maxHi = divSafe(mult);
|
|
|
|
assert(hi < maxHi || (hi === maxHi && lo <= maxLo),
|
|
'Fixed number string exceeds 2^53-1.');
|
|
|
|
return sign * (hi * mult + lo);
|
|
};
|
|
|
|
/**
|
|
* 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.');
|
|
}
|