diff --git a/lib/btc/amount.js b/lib/btc/amount.js index 0dbdcd4d..f5f6c20a 100644 --- a/lib/btc/amount.js +++ b/lib/btc/amount.js @@ -77,7 +77,7 @@ Amount.prototype.toSatoshis = function toSatoshis(num) { */ Amount.prototype.toBits = function toBits(num) { - return Amount.serialize(this.value, 2, num); + return Amount.encode(this.value, 2, num); }; /** @@ -87,7 +87,7 @@ Amount.prototype.toBits = function toBits(num) { */ Amount.prototype.toMBTC = function toMBTC(num) { - return Amount.serialize(this.value, 5, num); + return Amount.encode(this.value, 5, num); }; /** @@ -97,7 +97,7 @@ Amount.prototype.toMBTC = function toMBTC(num) { */ Amount.prototype.toBTC = function toBTC(num) { - return Amount.serialize(this.value, 8, num); + return Amount.encode(this.value, 8, num); }; /** @@ -154,7 +154,7 @@ Amount.prototype.fromValue = function fromValue(value) { */ Amount.prototype.fromSatoshis = function fromSatoshis(value, num) { - this.value = Amount.parse(value, 0, num); + this.value = Amount.decode(value, 0, num); return this; }; @@ -167,7 +167,7 @@ Amount.prototype.fromSatoshis = function fromSatoshis(value, num) { */ Amount.prototype.fromBits = function fromBits(value, num) { - this.value = Amount.parse(value, 2, num); + this.value = Amount.decode(value, 2, num); return this; }; @@ -180,7 +180,7 @@ Amount.prototype.fromBits = function fromBits(value, num) { */ Amount.prototype.fromMBTC = function fromMBTC(value, num) { - this.value = Amount.parse(value, 5, num); + this.value = Amount.decode(value, 5, num); return this; }; @@ -193,7 +193,7 @@ Amount.prototype.fromMBTC = function fromMBTC(value, num) { */ Amount.prototype.fromBTC = function fromBTC(value, num) { - this.value = Amount.parse(value, 8, num); + this.value = Amount.decode(value, 8, num); return this; }; @@ -318,10 +318,24 @@ Amount.prototype.inspect = function inspect() { */ Amount.btc = function btc(value, num) { - if (util.isFloat(value)) + if (typeof value === 'string') return value; - return Amount.serialize(value, 8, num); + return Amount.encode(value, 8, num); +}; + +/** + * Safely convert a BTC string to satoshis. + * @param {String} str - BTC + * @returns {Amount} Satoshis. + * @throws on parse error + */ + +Amount.value = function value(str, num) { + if (typeof str === 'number') + return str; + + return Amount.decode(str, 8, num); }; /** @@ -334,80 +348,13 @@ Amount.btc = function btc(value, num) { * @returns {String} */ -Amount.serialize = function serialize(value, exp, num) { - assert(util.isInt(value), 'Non-satoshi value for conversion.'); - - let negative = false; - - if (value < 0) { - value = -value; - negative = true; - } - - value = value.toString(10); - - assert(value.length <= 16, 'Number exceeds 2^53-1.'); - - while (value.length < exp + 1) - value = '0' + value; - - const hi = value.slice(0, -exp); - let lo = value.slice(-exp); - - lo = lo.replace(/0+$/, ''); - - if (lo.length === 0) - lo += '0'; - - let result = `${hi}.${lo}`; - - if (negative) - result = '-' + result; +Amount.encode = function encode(value, exp, num) { + const str = util.toFixed(value, exp); if (num) - return Number(result); + return Number(str); - return result; -}; - -/** - * Unsafely convert satoshis to a BTC string. - * @param {Amount} value - * @param {Number} exp - Exponent. - * @param {Boolean} num - Return a number. - * @returns {String} - */ - -Amount.serializeUnsafe = function serializeUnsafe(value, exp, num) { - assert(util.isInt(value), 'Non-satoshi value for conversion.'); - - value /= pow10(exp); - value = value.toFixed(exp); - - if (num) - return Number(value); - - if (exp !== 0) { - value = value.replace(/0+$/, ''); - if (value[value.length - 1] === '.') - value += '0'; - } - - return value; -}; - -/** - * Safely convert a BTC string to satoshis. - * @param {String} value - BTC - * @returns {Amount} Satoshis. - * @throws on parse error - */ - -Amount.value = function _value(value, num) { - if (util.isInt(value)) - return value; - - return Amount.parse(value, 8, num); + return str; }; /** @@ -416,165 +363,22 @@ Amount.value = function _value(value, num) { * floating point arithmetic. It also does * extra validation to ensure the resulting * Number will be 53 bits or less. - * @param {String} value - BTC + * @param {String} str - BTC * @param {Number} exp - Exponent. * @param {Boolean} num - Allow numbers. * @returns {Amount} Satoshis. * @throws on parse error */ -Amount.parse = function parse(value, exp, num) { - if (num && typeof value === 'number') { - assert(util.isNumber(value), 'Non-BTC value for conversion.'); - value = value.toString(10); +Amount.decode = function decode(str, exp, num) { + if (num && typeof str === 'number') { + assert(util.isNumber(str), 'Non-BTC value for conversion.'); + str = str.toString(10); } - assert(util.isFloat(value), 'Non-BTC value for conversion.'); - - const mult = pow10(exp); - const maxLo = modSafe(mult); - const maxHi = divSafe(mult); - let negative = false; - - if (value[0] === '-') { - negative = true; - value = value.substring(1); - } - - const parts = value.split('.'); - - assert(parts.length <= 2, 'Bad decimal point.'); - - let hi = parts[0] || '0'; - let lo = parts[1] || '0'; - - hi = hi.replace(/^0+/, ''); - lo = lo.replace(/0+$/, ''); - - assert(hi.length <= 16 - exp, 'Number exceeds 2^53-1.'); - assert(lo.length <= exp, 'Too many decimal places.'); - - if (hi.length === 0) - hi = '0'; - - while (lo.length < exp) - lo += '0'; - - hi = parseInt(hi, 10); - lo = parseInt(lo, 10); - - assert(hi < maxHi || (hi === maxHi && lo <= maxLo), - 'Number exceeds 2^53-1.'); - - let result = hi * mult + lo; - - if (negative) - result = -result; - - return result; + return util.fromFixed(str, exp); }; -/** - * Unsafely convert a BTC string to satoshis. - * @param {String} value - BTC - * @param {Number} exp - Exponent. - * @param {Boolean} num - Allow numbers. - * @returns {Amount} Satoshis. - * @throws on parse error - */ - -Amount.parseUnsafe = function parseUnsafe(value, exp, num) { - if (typeof value === 'string') { - assert(util.isFloat(value), 'Non-BTC value for conversion.'); - value = parseFloat(value); - } else { - assert(util.isNumber(value), 'Non-BTC value for conversion.'); - assert(num, 'Cannot parse number.'); - } - - value *= pow10(exp); - - assert(value % 1 === 0, 'Too many decimal places.'); - - return value; -}; - -/* - * 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.'); -} - /* * Expose */ diff --git a/lib/node/config.js b/lib/node/config.js index 228811b5..cc115d2f 100644 --- a/lib/node/config.js +++ b/lib/node/config.js @@ -10,6 +10,7 @@ const assert = require('assert'); const path = require('path'); const os = require('os'); const fs = require('../utils/fs'); +const util = require('../utils/util'); const HOME = os.homedir ? os.homedir() : '/'; /** @@ -248,7 +249,7 @@ Config.prototype.str = function str(key, fallback) { return fallback; if (typeof value !== 'string') - throw new Error(`${key} must be a string.`); + throw new Error(`${fmt(key)} must be a string.`); return value; }; @@ -271,17 +272,17 @@ Config.prototype.num = function num(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(`${key} must be a positive integer.`); + throw new Error(`${fmt(key)} must be a positive integer.`); return value; } if (!/^\d+$/.test(value)) - throw new Error(`${key} must be a positive integer.`); + throw new Error(`${fmt(key)} must be a positive integer.`); value = parseInt(value, 10); if (!isFinite(value)) - throw new Error(`${key} must be a positive integer.`); + throw new Error(`${fmt(key)} must be a positive integer.`); return value; }; @@ -304,17 +305,17 @@ Config.prototype.flt = function flt(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(`${key} must be a float.`); + throw new Error(`${fmt(key)} must be a float.`); return value; } if (!/^\d*(?:\.\d*)?$/.test(value)) - throw new Error(`${key} must be a float.`); + throw new Error(`${fmt(key)} must be a float.`); value = parseFloat(value); if (!isFinite(value)) - throw new Error(`${key} must be a float.`); + throw new Error(`${fmt(key)} must be a float.`); return value; }; @@ -327,7 +328,7 @@ Config.prototype.flt = function flt(key, fallback) { */ Config.prototype.amt = function amt(key, fallback) { - let value = this.get(key); + const value = this.get(key); if (fallback === undefined) fallback = null; @@ -337,24 +338,17 @@ Config.prototype.amt = function amt(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') - throw new Error(`${key} must be an amount.`); + throw new Error(`${fmt(key)} must be an amount.`); + if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) + throw new Error(`${fmt(key)} must be an amount (u64).`); return value; } - if (!/^\d+(\.\d{0,8})?$/.test(value)) - throw new Error(`${key} must be an amount.`); - - value = parseFloat(value); - - if (!isFinite(value)) - throw new Error(`${key} must be an amount.`); - - value *= 1e8; - - if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new Error(`${key} must be an amount (uint64).`); - - return value; + try { + return util.fromFixed(value, 8); + } catch (e) { + throw new Error(`${fmt(key)} must be an amount (parse).`); + } }; /** @@ -375,7 +369,7 @@ Config.prototype.bool = function bool(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'boolean') - throw new Error(`${key} must be a boolean.`); + throw new Error(`${fmt(key)} must be a boolean.`); return value; } @@ -385,7 +379,7 @@ Config.prototype.bool = function bool(key, fallback) { if (value === 'false' || value === '0') return false; - throw new Error(`${key} must be a boolean.`); + throw new Error(`${fmt(key)} must be a boolean.`); }; /** @@ -406,14 +400,14 @@ Config.prototype.buf = function buf(key, fallback) { if (typeof value !== 'string') { if (!Buffer.isBuffer(value)) - throw new Error(`${key} must be a buffer.`); + throw new Error(`${fmt(key)} must be a buffer.`); return value; } const data = Buffer.from(value, 'hex'); if (data.length !== value.length / 2) - throw new Error(`${key} must be a hex string.`); + throw new Error(`${fmt(key)} must be a hex string.`); return data; }; @@ -436,7 +430,7 @@ Config.prototype.array = function array(key, fallback) { if (typeof value !== 'string') { if (!Array.isArray(value)) - throw new Error(`${key} must be an array.`); + throw new Error(`${fmt(key)} must be an array.`); return value; } @@ -470,7 +464,7 @@ Config.prototype.obj = function obj(key, fallback) { return fallback; if (!value || typeof value !== 'object') - throw new Error(`${key} must be an object.`); + throw new Error(`${fmt(key)} must be an object.`); return value; }; @@ -492,7 +486,7 @@ Config.prototype.func = function func(key, fallback) { return fallback; if (!value || typeof value !== 'function') - throw new Error(`${key} must be a function.`); + throw new Error(`${fmt(key)} must be a function.`); return value; }; @@ -933,6 +927,16 @@ Config.prototype.parseForm = function parseForm(query, map) { * Helpers */ +function fmt(key) { + if (Array.isArray(key)) + key = key[0]; + + if (typeof key === 'number') + return `Argument #${key}`; + + return key; +} + function unescape(str) { try { str = decodeURIComponent(str); @@ -945,13 +949,7 @@ function unescape(str) { } function isAlpha(str) { - if (typeof str !== 'string') - return false; - - if (!/^[a-z0-9]+$/.test(str)) - return false; - - return true; + return /^[a-z0-9]+$/.test(str); } /* diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 45dd7741..39b5af2d 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -2066,7 +2066,7 @@ TX.prototype.format = function format(view, entry, index) { rate = this.getRate(view); // Rate can exceed 53 bits in testing. - if (!util.isSafeInteger(rate)) + if (!Number.isSafeInteger(rate)) rate = 0; } @@ -2135,7 +2135,7 @@ TX.prototype.getJSON = function getJSON(network, view, entry, index) { rate = this.getRate(view); // Rate can exceed 53 bits in testing. - if (!util.isSafeInteger(rate)) + if (!Number.isSafeInteger(rate)) rate = 0; } diff --git a/lib/utils/protowriter.js b/lib/utils/protowriter.js index b7894921..aaa8ad60 100644 --- a/lib/utils/protowriter.js +++ b/lib/utils/protowriter.js @@ -98,7 +98,7 @@ ProtoWriter.prototype.writeFieldVarint = function writeFieldVarint(tag, value) { }; ProtoWriter.prototype.writeFieldU64 = function writeFieldU64(tag, value) { - assert(util.isSafeInteger(value)); + assert(Number.isSafeInteger(value)); this.writeFieldVarint(tag, value); }; @@ -125,7 +125,7 @@ ProtoWriter.prototype.writeFieldString = function writeFieldString(tag, data, en */ function writeVarint(data, num, off) { - assert(util.isSafeInteger(num), 'Number exceeds 2^53-1.'); + assert(Number.isSafeInteger(num), 'Number exceeds 2^53-1.'); do { assert(off < data.length); @@ -142,7 +142,7 @@ function writeVarint(data, num, off) { }; function slipVarint(num) { - assert(util.isSafeInteger(num), 'Number exceeds 2^53-1.'); + assert(Number.isSafeInteger(num), 'Number exceeds 2^53-1.'); let data = 0; let size = 0; @@ -163,7 +163,7 @@ function slipVarint(num) { } function sizeVarint(num) { - assert(util.isSafeInteger(num), 'Number exceeds 2^53-1.'); + assert(Number.isSafeInteger(num), 'Number exceeds 2^53-1.'); let size = 0; diff --git a/lib/utils/util.js b/lib/utils/util.js index d4c00864..97217940 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -105,16 +105,6 @@ util.revHex = function revHex(data) { return out; }; -/** - * Test whether a number is below MAX_SAFE_INTEGER. - * @param {Number} value - * @returns {Boolean} - */ - -util.isSafeInteger = function isSafeInteger(value) { - return Number.isSafeInteger(value); -}; - /** * Test whether the result of a positive * addition would be below MAX_SAFE_INTEGER. @@ -168,9 +158,7 @@ util.isSafeAddition = function isSafeAddition(a, b) { */ util.isNumber = function isNumber(value) { - return typeof value === 'number' - && isFinite(value) - && util.isSafeInteger(value); + return typeof value === 'number' && Number.isSafeInteger(value); }; /** @@ -263,24 +251,6 @@ util.isHex256 = function isHex256(hash) { return util.isHex(hash) && hash.length === 64; }; -/** - * Test whether a string qualifies as a float. - * - * This is stricter than checking if the result of parseFloat() is NaN - * as, e.g. parseFloat successfully parses the string '1.2.3' as 1.2, and - * we also check that the value is a string. - * - * @param {String?} value - * @returns {Boolean} - */ - -util.isFloat = function isFloat(value) { - return typeof value === 'string' - && /^-?(\d+)?(?:\.\d*)?$/.test(value) - && value.length !== 0 - && value !== '-'; -}; - /** * util.inspect() with 20 levels of depth. * @param {Object|String} obj @@ -809,3 +779,180 @@ util.memoryUsage = function memoryUsage() { external: util.mb(mem.external) }; }; + +/** + * 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} + */ + +util.toFixed = function toFixed(num, exp) { + assert(typeof num === 'number'); + assert(Number.isSafeInteger(num) && num % 1 === 0, 'Invalid integer value.'); + + let sign = ''; + + if (num < 0) { + num = -num; + sign = '-'; + } + + const mult = pow10(exp); + let lo = num % mult; + const hi = (num - lo) / mult; + + lo = lo.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} + */ + +util.fromFixed = function fromFixed(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'; + + 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); +}; + +/* + * 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.'); +} diff --git a/lib/utils/validator.js b/lib/utils/validator.js index 4407c249..2f15ca9b 100644 --- a/lib/utils/validator.js +++ b/lib/utils/validator.js @@ -7,6 +7,7 @@ 'use strict'; const assert = require('assert'); +const util = require('../utils/util'); /** * Validator @@ -280,7 +281,7 @@ Validator.prototype.i64 = function i64(key, fallback) { */ Validator.prototype.amt = function amt(key, fallback) { - let value = this.get(key); + const value = this.get(key); if (fallback === undefined) fallback = null; @@ -291,23 +292,16 @@ Validator.prototype.amt = function amt(key, fallback) { if (typeof value !== 'string') { if (typeof value !== 'number') throw new ValidationError(key, 'amount'); + if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) + throw new ValidationError(key, 'amount'); return value; } - if (!/^\d+(\.\d{0,8})?$/.test(value)) + try { + return util.fromFixed(value, 8); + } catch (e) { throw new ValidationError(key, 'amount'); - - value = parseFloat(value); - - if (!isFinite(value)) - throw new ValidationError(key, 'amount'); - - value *= 1e8; - - if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) - throw new ValidationError(key, 'amount (uint64)'); - - return value; + } }; /** @@ -318,7 +312,7 @@ Validator.prototype.amt = function amt(key, fallback) { */ Validator.prototype.btc = function btc(key, fallback) { - let value = this.num(key); + const value = this.flt(key); if (fallback === undefined) fallback = null; @@ -326,12 +320,14 @@ Validator.prototype.btc = function btc(key, fallback) { if (value === null) return fallback; - value *= 1e8; - - if (value % 1 !== 0 || value < 0 || value > 0x1fffffffffffff) + if (value < 0 || value > 0x1fffffffffffff) throw new ValidationError(key, 'btc float (uint64)'); - return value; + try { + return util.fromFixed(value.toString(10), 8); + } catch (e) { + throw new ValidationError(key, 'btc float'); + } }; /** @@ -581,18 +577,13 @@ Validator.prototype.func = function func(key, fallback) { */ function fmt(key) { + if (Array.isArray(key)) + key = key[0]; + if (typeof key === 'number') return `Param #${key}`; - return key; -} -function inherits(child, parent) { - child.super_ = parent; - Object.setPrototypeOf(child.prototype, parent.prototype); - Object.defineProperty(child.prototype, 'constructor', { - value: child, - enumerable: false - }); + return key; } function ValidationError(key, type) { @@ -608,14 +599,10 @@ function ValidationError(key, type) { Error.captureStackTrace(this, ValidationError); } -inherits(ValidationError, Error); +util.inherits(ValidationError, Error); /* * Expose */ -exports = Validator; -exports.Validator = Validator; -exports.Error = ValidationError; - -module.exports = exports; +module.exports = Validator; diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index a487ed1c..28902bfc 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -2963,7 +2963,7 @@ Details.prototype.toJSON = function toJSON() { let rate = this.getRate(fee); // Rate can exceed 53 bits in testing. - if (!util.isSafeInteger(rate)) + if (!Number.isSafeInteger(rate)) rate = 0; return { diff --git a/test/utils-test.js b/test/utils-test.js index 45871d76..9aa4cafa 100644 --- a/test/utils-test.js +++ b/test/utils-test.js @@ -10,6 +10,7 @@ const encoding = require('../lib/utils/encoding'); const Amount = require('../lib/btc/amount'); const consensus = require('../lib/protocol/consensus'); const Validator = require('../lib/utils/validator'); +const util = require('../lib/utils/util'); const base58Tests = [ ['', ''], @@ -78,24 +79,34 @@ describe('Utils', function() { assert(btc === 5460 * 10000000); btc = Amount.value('546.0000'); assert(btc === 5460 * 10000000); + assert.doesNotThrow(() => { Amount.value('546.00000000000000000'); }); + assert.throws(() => { Amount.value('546.00000000000000001'); }); + assert.doesNotThrow(() => { Amount.value('90071992.54740991'); }); + assert.doesNotThrow(() => { Amount.value('090071992.547409910'); }); + assert.throws(() => { Amount.value('90071992.54740992'); }); + assert.throws(() => { Amount.value('190071992.54740991'); }); + + assert.strictEqual(parseFloat('0.15645647') * 1e8, 15645646.999999998); + assert.strictEqual(util.fromFixed('0.15645647', 8), 15645647); + assert.strictEqual(util.toFixed(15645647, 8), '0.15645647'); }); it('should write/read new varints', () => {