徹底解決Javascript數字計算不精確的問題__Java

來源:互聯網
上載者:User
問題重現 Javascript的數字類型只有一個number,沒有短型、整型和浮點型、雙浮點型等類型。由於Javascript在實現數字運算的時候,所採取的的浮點數類型實現方式,其會精確到小數點後16位。見下面的樣本:
7*0.8 = 5.6000000000000005 
0.1+0.2 = 0.3000000000000001

解決思路 要解決這個問題一般有兩種方案,一種是用將數字轉化為字串來進行計算,另一種是將所有小數轉化為整數進行計算後再將計算結果轉化為對應的小數。我們主要採取第一種解決方案。
實現字串相加、相乘的運算 實現字串的相加相乘,有時候在某訊的面試題中會出現。下面我會完整實現,有可最佳化的地方可指出。
console.log(bigMut("567", "1234")); // 699678function bigMut(big, common) {big += "";common += "";if (big.length < common.length) {big = [common, common = big][0];}big = big.split("").reverse();var oneMutManyRes = [];var i = 0,len = big.length;for (; i < len; i++) {oneMutManyRes[oneMutManyRes.length] = oneMutMany(big[i], common) + getLenZero(i);}var result = oneMutManyRes[0];for (i = 1, len = oneMutManyRes.length; i < len; i++) {result = bigNumAdd(result, oneMutManyRes[i]);}return result;}function getLenZero(len) {len += 1;var ary = [];ary.length = len;return ary.join("0");}function oneMutMany(one, many) {one += "";many += "";if (one.length != 1) {one = [many, many = one][0];}one = parseInt(one, 10);var i = 0,len = many.length,resAry = [],addTo = 0,curItem,curRes,toSave;many = many.split("").reverse();for (; i <= len; i++) {curItem = parseInt(many[i] || 0, 10);curRes = curItem * one + addTo;toSave = curRes % 10;addTo = (curRes - curRes % 10) / 10;resAry.unshift(toSave);}if (resAry[0] == 0) {resAry.splice(0, 1);}return resAry.join("");}function bigNumAdd(big, common) {big += "";common += "";var maxLen = Math.max(big.length, common.length),bAry = big.split("").reverse(),cAry = common.split("").reverse(),i = 0,addToNext = 0,resAry = [],fn,sn,sum;for (; i <= maxLen; i++) {fn = parseInt(bAry[i] || 0);sn = parseInt(cAry[i] || 0);sum = fn + sn + addToNext;addToNext = (sum - sum % 10) / 10;resAry.unshift(sum % 10);}if (resAry[0] == 0) {resAry.splice(0, 1);}return resAry.join("");}

其實,如果我們將整套的解決方案(包括加、減、乘、除)封裝為一個庫,然後供每個人方便的調用,那將是最好的。實現如下。 實現庫 整個的庫的實現代碼以及使用API如下:
/* * 小數計算 * @example: * 0.1+0.2     //0.30000000000000004 * var a=Decimal('0.1');var b=Decimal('0.2'); * a.add(b).toNumber()    //0.3 * * 四捨五入,保留一位小數 * a.add(b).add(0.14).toNumber(1)  //0.4 * * Decimal.add(0.1,0.2,0.3).toNumber()  //0.6 * Decimal.add([0.1,0.2,0.3]).toNumber()  //0.6 * * (0.1+0.2+0.3)*2/0.5      //2.4000000000000004 * Decimal.add([0.1,0.2,0.3]).mul(2).div(0.5).toNumber() //2.4 * */(function (ROOT, factory) {    if (typeof exports === 'object') {        // Node.        module.exports = factory();    } else if (typeof define === 'function' && define.amd) {        // AMD. Register as an anonymous module.        define(factory);    } else {        // Browser globals (root is window)        ROOT.Decimal = factory();    }}((0,eval)(this), function () {    var DECIMAL_SEPARATOR = '.';    // Decimal    var Decimal = function (num) {        if (this.constructor != Decimal) {            return new Decimal(num);        }        if (num instanceof Decimal) {            return num;        }        this.internal = String(num);        this.as_int = as_integer(this.internal);        this.add = function (target) {            var operands = [this, new Decimal(target)];            operands.sort(function (x, y) {                return x.as_int.exp - y.as_int.exp            });            var smallest = operands[0].as_int.exp;            var biggest = operands[1].as_int.exp;            var x = Number(format(operands[1].as_int.value, biggest - smallest));            var y = Number(operands[0].as_int.value);            var result = String(x + y);            return Decimal(format(result, smallest));        };        this.sub = function (target) {            return Decimal(this.add(target * -1));        };        this.mul = function (target) {            target = new Decimal(target);            var result = String(this.as_int.value * target.as_int.value);            var exp = this.as_int.exp + target.as_int.exp;            return Decimal(format(result, exp));        };        this.div = function (target) {            target = new Decimal(target);            var smallest = Math.min(this.as_int.exp, target.as_int.exp);            var x = Decimal.mul(Math.pow(10, Math.abs(smallest)), this);            var y = Decimal.mul(Math.pow(10, Math.abs(smallest)), target);            return Decimal(x / y);        };        this.toString = function (precision) {            if (isNumber(precision)) {                return ''+toFixed(Number(this.internal), precision);            }            return this.internal;        };        this.toNumber = function (precision) {            if (isNumber(precision)) {                return toFixed(Number(this.internal), precision);            }            return Number(this.internal);        }    };    var as_integer = function (number) {        number = String(number);        var value,            exp,            tokens = number.split(DECIMAL_SEPARATOR),            integer = tokens[0],            fractional = tokens[1];        if (!fractional) {            var trailing_zeros = integer.match(/0+$/);            if (trailing_zeros) {                var length = trailing_zeros[0].length;                value = integer.substr(0, integer.length - length);                exp = length;            } else {                value = integer;                exp = 0;            }        } else {            value = parseInt(number.split(DECIMAL_SEPARATOR).join(''), 10);            exp = fractional.length * -1;        }        return {            'value': value,            'exp': exp        };    };    // Helpers    var neg_exp = function (str, position) {        position = Math.abs(position);        var offset = position - str.length;        var sep = DECIMAL_SEPARATOR;        if (offset >= 0) {            str = zero(offset) + str;            sep = '0.';        }        var length = str.length;        var head = str.substr(0, length - position);        var tail = str.substring(length - position, length);        return head + sep + tail;    };    var pos_exp = function (str, exp) {        var zeros = zero(exp);        return String(str + zeros);    };    var format = function (num, exp) {        num = String(num);        var func = exp >= 0 ? pos_exp : neg_exp;        return func(num, exp);    };    var zero = function (exp) {        return new Array(exp + 1).join('0');    };    var methods = ['add', 'mul', 'sub', 'div'];    for (var i = 0; i < methods.length; i++) {        (function (method) {            Decimal[method] = function () {                var args = [].slice.call(arguments);                if (isArray(args[0])) {                    args = args[0];                }                if (args.length == 1) {                    return new Decimal(args[0]);                }                var option = args[args.length - 1];                var sum = new Decimal(args[0]),                    index = 1;                while (index < args.length) {                    sum = sum[method](args[index]);                    index++;                }                return sum;            };        })(methods[i]);    }    var toFixed = function (number, precision) {        var multiplier = Math.pow(10, precision + 1),            wholeNumber = Math.floor(number * multiplier);        return Math.round(wholeNumber / 10) * 10 / multiplier;    };    var isNumber = function (o) {        return Object.prototype.toString.call(o).slice(8, -1) === 'Number';    };    var isArray = function (o) {        return Object.prototype.toString.call(o).slice(8, -1) === 'Array';    };    var isObject = function (o) {        return Object.prototype.toString.call(o).slice(8, -1) === 'Object';    };    return Decimal;}));

這種簡單的封裝有兩個比較好的地方需要特別指明一下。
間接調用eval 間接調用eval顧名思義就是不是直接調用eval。間接調用eval和直接調用eval的一個(僅有的一個)區別就是:間接調用eval所執行的範圍始終是在全域,它不會以某個函數或對象為執行範圍來對要執行的字串進行求值。樣本如下: var a = {b:function(){console.error(eval('this'));}}; a.b(); Object { b :  function } var a = {b:function(){console.error((0,eval)('this'));}}; a.b(); Window { top :  Window ,  window :  Window ,  location :  Location ,  external :  Object ,  chrome :  Object … }
這樣做的優點有兩個:速度快,更安全。 所以,你就能明白為什麼很多的架構喜歡用(0,eval)('this')來傳遞window對象的引用到命名空間閉包內。 架構封裝對外暴漏的介面 現在的Javascript代碼已經今非昔比,他可以運行在瀏覽器端,也可以運行在服務端,你可以模組化的方式進行開發,也可以以命名空間暴漏全域變數的方式進行開發。我們在封裝一個簡單的架構的時候都需要考慮到這所有的情況。於是,就出現了如下的常見的架構封裝方式:
(function (ROOT, factory) {    if (typeof exports === 'object') {        // Node.        module.exports = factory();    } else if (typeof define === 'function' && define.amd) {        // AMD. Register as an anonymous module.        define(factory);    } else {        // Browser globals (root is window)        ROOT.Decimal = factory();    }}((0,eval)(this), function () {});

基本滿足所有的需求。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.