JS precision floating-point problem

This article is reproduced in: Ape 2048 Website ➥ https://www.mk2048.com/blog/blog.php?id=ib1icibj2j

  Recently in doing the project, related to the calculation of commodity prices, it would often calculated accuracy problems. Beginning hastily, directly toFixed to solve the problem, and there is no good to think about this problem. Then slowly, more and more problems, even toFixed also appeared (Yun sad), and later through various online blog and forum search, sorting summed up.

Problems found

  It summed up a total of two question

Precision floating-point operations problems after

  In calculating the arithmetic commodity prices, occasionally accuracy problems, some common examples are as follows:

// 加法 =====================
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001

// 减法 =====================
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
 
// 乘法 =====================
19.9 * 100 = 1989.9999999999998
0.8 * 3 = 2.4000000000000004
35.41 * 100 = 3540.9999999999995

// 除法 =====================
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999

toFixed wonderful problem

  When precision floating-point operations after encountering a problem, and I just started using toFixed (2) to solve, because W3school and rookie tutorials (they both said that this pot is not back) clearly written definition: toFixed () number rounded to the methods specified number of decimal digits.

  But in chrome test results are less than satisfactory:

1.35.toFixed(1) // 1.4 正确
1.335.toFixed(2) // 1.33  错误
1.3335.toFixed(3) // 1.333 错误
1.33335.toFixed(4) // 1.3334 正确
1.333335.toFixed(5)  // 1.33333 错误
1.3333335.toFixed(6) // 1.333333 错误

  IE IETester used in the following test result is correct.

Why would

  Let's look at why the 0.1 + 0.2 will equal .30000000000000004, rather than 0.3. First of all, I want to know why such a problem, let us return to college to learn complex (ku) miscellaneous computer (zao) the composition principle. Although teachers have all returned to the university, but it does not matter, we still have Baidu thing.

Floating-point storage

  And various other languages ​​such as Java and Python, JavaScript, all figures include both integer and fractional only one type - Number. Its implementation follows the IEEE 754 standard, 64-bit fixed-length expressed, i.e. double the standard double precision floating point (also associated float 32-bit single precision).

  Such advantage is that storage structure normalization and decimal integer, to save storage space.

  64 bits can be divided into three parts:

  • Sign bit S: the first one is a negative sign bit (sign), 0 represents a positive number of 1 stands for negative

  • Exponent bits E: 11 of the intermediate storage index (exponent), used to represent the number of power

  • Mantissa bits M: The final mantissa is 52 (mantissa), the excess part of a round zero auto

Storage

Floating-point arithmetic

  So in the end what happened then JavaScript 0.1 and 0.2 when calculating +?

  First, 0.1 and 0.2 in decimal are converted into binary, but due to floating point binary representation is infinite:

0.1 -> 0.0001 1001 1001 1001...(1100循环)
0.2 -> 0.0011 0011 0011 0011...(0011循环)

  Standard 64-bit IEEE 754 double-precision floating-point fractional part supports a maximum of 53 bits, so that after the addition of the two binary obtain:

0.0100110011001100110011001100110011001100110011001100 

  Due to limitations of floating-point decimal places and truncated binary digits, and then converted to decimal, it becomes .30000000000000004. Therefore, an error is caused when performing arithmetic calculations.

Solution

  For the above two issues, Internet search, wave solutions, basically similar, each look.

Solve toFixed

  For compatibility issues toFixed, we can rewrite the toFix to address, as follows:

// toFixed兼容方法
Number.prototype.toFixed = function(len){
    if(len>20 || len<0){
        throw new RangeError('toFixed() digits argument must be between 0 and 20');
    }
    // .123转为0.123
    var number = Number(this);
    if (isNaN(number) || number >= Math.pow(10, 21)) {
        return number.toString();
    }
    if (typeof (len) == 'undefined' || len == 0) {
        return (Math.round(number)).toString();
    }
    var result = number.toString(),
        numberArr = result.split('.');

    if(numberArr.length<2){
        //整数的情况
        return padNum(result);
    }
    var intNum = numberArr[0], //整数部分
        deciNum = numberArr[1],//小数部分
        lastNum = deciNum.substr(len, 1);//最后一个数字
    
    if(deciNum.length == len){
        //需要截取的长度等于当前长度
        return result;
    }
    if(deciNum.length < len){
        //需要截取的长度大于当前长度 1.3.toFixed(2)
        return padNum(result)
    }
    //需要截取的长度小于当前长度,需要判断最后一位数字
    result = intNum + '.' + deciNum.substr(0, len);
    if(parseInt(lastNum, 10)>=5){
        //最后一位数字大于5,要进位
        var times = Math.pow(10, len); //需要放大的倍数
        var changedInt = Number(result.replace('.',''));//截取后转为整数
        changedInt++;//整数进位
        changedInt /= times;//整数转为小数,注:有可能还是整数
        result = padNum(changedInt+'');
    }
    return result;
    //对数字末尾加0
    function padNum(num){
        var dotPos = num.indexOf('.');
        if(dotPos === -1){
            //整数的情况
            num += '.';
            for(var i = 0;i<len;i++){
                num += '0';
            }
            return num;
        } else {
            //小数的情况
            var need = len - (num.length - dotPos - 1);
            for(var j = 0;j<need;j++){
                num += '0';
            }
            return num;
        }
    }
}

  We judge the last one is greater than or equal to 5 to decide or need to carry, if you need to first carry decimal integer multiplied by the multiplier becomes, after adding 1, divided by the multiple changes to decimal, so do not bit by bit judge.

Precision floating-point arithmetic to solve

  Since we discovered the problem of floating-point numbers, you can not directly make two floating-point operations, how to handle it?

  We can calculate the number needed to upgrade (n multiplied by the power of 10) to the computer can accurately identify the integers, and the like after completion of the calculation downgrade (10 divided by the power of n), which is most of the language processing into accuracy problems commonly used method. E.g:

0.1 + 0.2 == 0.3 //false
(0.1*10 + 0.2*10)/10 == 0.3 //true

  But this can be the perfect solution? Observant readers may have found the problem in the example above:

35.41 * 100 = 3540.9999999999995

  It seems to digitally upgrade is not entirely reliable ah (Yun sad).

  But the Magic goes Road ridge, so that it can be stumped us, we can float toString after indexOf ( '.'), The record about the length of decimal places, and then erase the decimal point, complete code is as follows:

 /*** method **
 *  add / subtract / multiply /divide
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatObj = function() {

    /*
     * 判断obj是否为一个整数
     */
    function isInteger(obj) {
        return Math.floor(obj) === obj
    }

    /*
     * 将一个浮点数转成整数,返回整数和倍数。如 3.14 >> 314,倍数是 100
     * @param floatNum {number} 小数
     * @return {object}
     *   {times:100, num: 314}
     */
    function toInteger(floatNum) {
        var ret = {times: 1, num: 0}
        if (isInteger(floatNum)) {
            ret.num = floatNum
            return ret
        }
        var strfi  = floatNum + ''
        var dotPos = strfi.indexOf('.')
        var len    = strfi.substr(dotPos+1).length
        var times  = Math.pow(10, len)
        var intNum = Number(floatNum.toString().replace('.',''))
        ret.times  = times
        ret.num    = intNum
        return ret
    }

    /*
     * 核心方法,实现加减乘除运算,确保不丢失精度
     * 思路:把小数放大为整数(乘),进行算术运算,再缩小为小数(除)
     *
     * @param a {number} 运算数1
     * @param b {number} 运算数2
     * @param digits {number} 精度,保留的小数点数,比如 2, 即保留为两位小数
     * @param op {string} 运算类型,有加减乘除(add/subtract/multiply/divide)
     *
     */
    function operation(a, b, digits, op) {
        var o1 = toInteger(a)
        var o2 = toInteger(b)
        var n1 = o1.num
        var n2 = o2.num
        var t1 = o1.times
        var t2 = o2.times
        var max = t1 > t2 ? t1 : t2
        var result = null
        switch (op) {
            case 'add':
                if (t1 === t2) { // 两个小数位数相同
                    result = n1 + n2
                } else if (t1 > t2) { // o1 小数位 大于 o2
                    result = n1 + n2 * (t1 / t2)
                } else { // o1 小数位 小于 o2
                    result = n1 * (t2 / t1) + n2
                }
                return result / max
            case 'subtract':
                if (t1 === t2) {
                    result = n1 - n2
                } else if (t1 > t2) {
                    result = n1 - n2 * (t1 / t2)
                } else {
                    result = n1 * (t2 / t1) - n2
                }
                return result / max
            case 'multiply':
                result = (n1 * n2) / (t1 * t2)
                return result
            case 'divide':
                result = (n1 / n2) * (t2 / t1)
                return result
        }
    }

    // 加减乘除的四个接口
    function add(a, b, digits) {
        return operation(a, b, digits, 'add')
    }
    function subtract(a, b, digits) {
        return operation(a, b, digits, 'subtract')
    }
    function multiply(a, b, digits) {
        return operation(a, b, digits, 'multiply')
    }
    function divide(a, b, digits) {
        return operation(a, b, digits, 'divide')
    }

    // exports
    return {
        add: add,
        subtract: subtract,
        multiply: multiply,
        divide: divide
    }
}();

  If you think floatObj call trouble, we can add the corresponding calculation method on Number.prototype.

If you thought that it was pretty good, please pay attention to my Denver home page . For more articles please visit Xie Xiaofei's blog

Guess you like

Origin www.cnblogs.com/htmlandcss/p/11788978.html