From 773f9263dce8fd8e647253b18632b0045bbf2703 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 16:22:07 -0700 Subject: [PATCH] JS: Add BigInteger support to Amount. --- js/amount.js | 177 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 29 deletions(-) diff --git a/js/amount.js b/js/amount.js index ead5aa74d..07a578047 100644 --- a/js/amount.js +++ b/js/amount.js @@ -1,7 +1,10 @@ // Represent Newcoin amounts and currencies. +// - Numbers in hex are big-endian. +var utils = require('./utils.js'); +var jsbn = require('./jsbn.js'); -var utils = require("./utils.js"); +var BigInteger = jsbn.BigInteger; var UInt160 = function () { // Internal form: @@ -9,7 +12,13 @@ var UInt160 = function () { // XXX Should standardize on 'i' format or 20 format. }; -// Returns NaN on error. +UInt160.prototype.from_json = function (j) { + var u = new UInt160(); + + return u.parse_json(j); +}; + +// value === NaN on error. UInt160.prototype.parse_json = function (j) { // Canonicalize and validate @@ -49,7 +58,7 @@ UInt160.prototype.parse_json = function (j) { } } - return this.value; + return this; }; // Convert from internal form. @@ -109,19 +118,84 @@ var Amount = function () { // integer : XNS // { 'value' : ..., 'currency' : ..., 'issuer' : ...} - this.value = 0; - this.offset = 0; - this.is_native = false; - this.is_negative = false; + this.value = new BigInteger(); // NaN for bad value. Always positive for non-XNS. + this.offset = undefined; // For non-XNS. + this.is_native = true; // Default to XNS. Only valid if value is not NaN. + this.is_negative = undefined; // Undefined for XNS. this.currency = new Currency(); this.issuer = new UInt160(); }; -// Convert only value to JSON text. -Amount.prototype.to_text = function() { - // XXX Needs to work for native and non-native. - return this.is_negative ? -this.value : this.value; // XXX Use biginteger. +// YYY Might also check range. +Amount.prototype.is_valid = function() { + return NaN !== this.value; +} + +// Convert only value to JSON wire format. +Amount.prototype.to_text = function(allow_nan) { + if (NaN === this.value) { + // Never should happen. + return allow_nan ? NaN : "0"; + } + else if (this.is_native) { + if (this.value.compareTo(exports.consts.xns_max) > 0 || this.value.compareTo(exports.consts.xns_min) < 0) + { + // Never should happen. + return allow_nan ? NaN : "0"; + } + else + { + return this.value.toString(); + } + } + else if (this.value.equals(BigInteger.ZERO)) + { + return "0"; + } + else if (this.offset < -25 || mOffset > -5) + { + // Use e notation. + // XXX Clamp output. + + return (this.is_negative ? "-" : "") + this.value.toString() + "e" + this.offset; + } + else + { + var val = "000000000000000000000000000" + this.value.toString() + "00000000000000000000000"; + var pre = val.substring(0, this.offset + 43); + var post = val.substring(this.offset + 43); + var s_pre = val.match(/[1-9].*$/); // Everything but leading zeros. + var s_post = val.match(/0+$/); // Trailing zeros. + + + return (this.is_negative ? "-" : "") + + (null == s_pre ? "0" : s_pre[0]) + + "." + + post.substring(post.length - s_post.length); + } +}; + +Amount.prototype.canonicalize = function() { + if (NaN === this.value || !this.currency) { + // nothing + } + else if (this.value.equals(BigInteger.ZERO)) { + this.offset = -100; + this.is_negative = false; + } + else + { + while (this.value.compareTo(exports.consts.bi_man_min_value)) { + this.value.multiply(exports.consts.bi_10); + this.offset -= 1; + } + + while (this.value.compareTo(exports.consts.bi_man_max_value)) { + this.value.divide(exports.consts.bi_10); + this.offset += 1; + } + } }; Amount.prototype.to_json = function() { @@ -138,21 +212,35 @@ Amount.prototype.to_json = function() { } }; -// Parse a native value. +// Parse a XNS value from untrusted input. +// XXX Improvements: disallow leading zeros. Amount.prototype.parse_native = function(j) { - if ('integer' === typeof j) { - // XNS - this.value = j >= 0 ? j : -j; // XXX Use biginteger. - this.offset = 0; + var m; + + if ('string' === typeof j) + m = j.match(/^(\d+)(\.\d{1,6})?$/); + + if ('integer' === typeof j || null !== m) { + if ('integer' === typeof j || ("" === e[2])) { + this.value = new BigInteger(j); + } + else + { + // Decimal notation + var int_part = (new BigInteger(e[1])).multiply(exports.consts.xns_unit); + var fraction_part = (new BigInteger(e[2])).multiply(new BigInteger(Math.pow(10, exports.consts.xns_unit-e[2].length))); + + this.value = int_part.add(fraction_part); + } this.is_native = true; - this.is_negative = j < 0; + this.offset = undefined; + this.is_negative = undefined; + + if (this.value.compareTo(exports.consts.xns_max) > 0 || this.value.compareTo(exports.consts.xns_min) < 0) + { + this.value = NaN; + } } - else if ('string' === typeof j) { - this.value = j >= 0 ? j : -j; // XXX Use biginteger. - this.offset = 0; - this.is_native = true; - this.is_negative = j < 0; - } else { this.value = NaN; } @@ -161,16 +249,41 @@ Amount.prototype.parse_native = function(j) { // Parse a non-native value. Amount.prototype.parse_value = function(j) { if ('integer' === typeof j) { - this.value = j >= 0 ? j : -j; // XXX Use biginteger. + this.value = new BigInteger(j); this.offset = 0; this.is_native = false; this.is_negative = j < 0; + + this.canonicalize(); } else if ('string' === typeof j) { - this.value = j >= 0 ? j : -j; // XXX Use biginteger. - this.offset = 0; + var e = j.match(/^(-?\d+)e(\d+)/); + var d = j.match(/^(-?\d+)\.(\d+)/); + + if (null !== e) { + // e notation + + this.value = new BigInteger(e[1]); + this.offset = parseInt(e[2]); + } + else if (null !== d) { + // float notation + + this.value = (new BigInteger(e[1])).multiply((new BigInteger(exports.consts.bi_10)).pow(e[2].length)).add(new BigInteger(e[2])); + this.offset = -e[2].length; + } + else + { + // integer notation + + this.value = new BigInteger(j); + this.offset = 0; + } + this.is_native = false; - this.is_negative = j < 0; + this.is_negative = undefined; + + this.canonicalize(); } else { this.value = NaN; @@ -201,10 +314,16 @@ exports.consts = { 'address_one' : "iiiiiiiiiiiiiiiiiiiiBZbvjr", 'currency_xns' : 0, 'currency_one' : 1, - 'uint160_xns' : hexToString("0000000000000000000000000000000000000000"), - 'uint160_one' : hexToString("0000000000000000000000000000000000000001"), + 'uint160_xns' : utils.hexToString("0000000000000000000000000000000000000000"), + 'uint160_one' : utils.hexToString("0000000000000000000000000000000000000001"), 'hex_xns' : "0000000000000000000000000000000000000000", 'hex_one' : "0000000000000000000000000000000000000001", + 'xns_max' : new BigInteger("9000000000000000000"), // Json wire limit. + 'xns_min' : new BigInteger("-9000000000000000000"), // Json wire limit. + 'xns_unit' : new BigInteger('1000000'), + 'bi_man_min_value' : new BigInteger('1000000000000000'), + 'bi_man_max_value' : new BigInteger('9999999999999999'), + 'bi_10' : new BigInteger('10'), }; // vim:sw=2:sts=2:ts=8