js 计算加减乘除导致精度丢失

发布时间 2023-08-03 15:22:51作者: 河畔的风
(function() {
    var ROOT = this;
    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(Math.abs(this.as_int.value) * Math.abs(target.as_int.value));
            var _symbol = this.as_int.value * target.as_int.value
            var exp = this.as_int.exp + target.as_int.exp;

            return Decimal(format(result, exp, _symbol));
        };

        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() {
            return this.internal;
        };

        this.toNumber = function() {
            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, symbol = 1) {
        position = Math.abs(position);
        const _symbol = symbol > 0 ? '' : '-' 
        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 _symbol + head + sep + tail;
    };

    var pos_exp = function(str, exp, symbol = 1) {
        var zeros = zero(exp);
        const _symbol = symbol > 0 ? '' : '-' 
        return _symbol + String(str + zeros);
    };

    var format = function(num, exp, symbol = 1) {
        num = String(num);
        var func = exp >= 0 ? pos_exp : neg_exp;
        return func(num, exp, symbol);
    };

    var zero = function(exp) {
        return new Array(exp + 1).join('0');
    };

    // Generics
    var methods = ['add', 'mul', 'sub', 'div'];
    for(var i = 0; i < methods.length; i++) {
        (function(method) {
            Decimal[method] = function(a, b) {
                return new Decimal(a)[method](b);
            }
        })(methods[i]);
    }

    if(typeof module != 'undefined' && module.exports) {
        module.exports = Decimal;
    } else {
        ROOT.Decimal = Decimal;
    }

    
})();
Decimal(.1).add(.2).toNumber()
0.3