关于JS小数运算不精确的探讨

各位老司机应该知道,很多时候做小数运算的时候会出现小数溢出。常见的场景是做价格运算,测试经常报bug。

于是针对这个奇特的问题,开始了寻根之旅。

举个例子:0.2+0.1=0.30000000000000004

为什么会这样?

其实很多其他计算机语言都有这个问题。计算机中的所有运算都会经过二进制转换再计算的。

0.2的二进制是  

0.2 * 2 = 0.4 取整数位  0  得 0.0

0.4 * 2 = 0.8 取整数位  0  得 0.00

0.8 * 2 = 1.6 取整数位  1  得 0.001

0.6 * 2 = 1.2 取整数位  1  得 0.0011

0.2 * 2 = 0.4 取整数位  0  得 0.00110

(n)….

太长了 于是直接在控制台上打印 

var num = 0.2;

num.toString(2)

// "0.001100110011001100110011001100110011001100110011001101"

同理算出 0.1的二进制为

// "0.0001100110011001100110011001100110011001100110011001101"

两个二进制相加得出    

// "0.0100110011001100110011001100110011001100110011001100111"

根据这个IEEE754的标准 就会把最后的 1 舍去变成

0.0100110011001100110011001100110011001100110011001101

然后转成十进制得出结果是 0.30000000000000004

再举个例子 0.1+0.3 = 0.4 

为什么这两个数不会产生溢出呢?

算出 0.1的二进制为

// "0.0001100110011001100110011001100110011001100110011001101"

算出 0.4的二进制为

// "0.01100110011001100110011001100110011001100110011001101"

两个二进制相加得出    

// "0.1000000000000000000000000000000000000000000000000000001"

根据这个IEEE754的标准 就会把最后的第52位进一舍零,得到

"0.1000000000000000000000000000000000000000000000000000"

剩下0.1 转换十进制为0.5

找了网上的一张图,其实还不是完全看懂指数位是什么。。。。

QQ图片20171226234411.png

怎么处理?

/**

 * 加法运算,避免数据相加小数点后产生多位数和计算精度损失。

 * 

 * @param a加数1 | b加数2

 */

function Add(a, b) {

    var n, n1, n2;

    try {

        n1 = a.toString().split(".")[1].length;

    } catch (e) {

        n1 = 0;

    }

    try {

        n2 = b.toString().split(".")[1].length;

    } catch (e) {

        n2 = 0;

    }

    n = Math.pow(10, Math.max(n1, n2));

    return (a * n + b * n) / n;

}

/**

 * 减法运算,避免数据相减小数点后产生多位数和计算精度损失。

 * 

 * @param a被减数  |  b减数

 */

function Sub(a, b) {

    var n, n1, n2;

    var precision; // 精度

    try {

        n1 = a.toString().split(".")[1].length;

    } catch (e) {

        n1 = 0;

    }

    try {

        n2 = b.toString().split(".")[1].length;

    } catch (e) {

        n2 = 0;

    }

    baseNum = Math.pow(10, Math.max(n1, n2));

    n = Math.pow(10, Math.max(n1, n2));

    return (a * n – b * n) / n;

}

/**

 * 乘法运算,避免数据相乘小数点后产生多位数和计算精度损失。

 * 

 * @param a被乘数 | b乘数

 */

function Mul(a, b) {

    var n1 = 0,

        n2 = 0;

    try {

        n1 += a.toString().split(".")[1].length;

    } catch (e) {}

    try {

        n2 += b.toString().split(".")[1].length;

    } catch (e) {}

    return (a * Math.pow(10, n1)) * (b * Math.pow(10, n2)) / Math.pow(10, n1 + n2);

}

/**

 * 除法运算,避免数据相除小数点后产生多位数和计算精度损失。

 * 

 * @param a被除数 | b除数

 */

function Div(a, b) {

    var n1 = 0,

        n2 = 0;

    var baseNum3, baseNum4;

    try {

        n1 += a.toString().split(".")[1].length;

    } catch (e) {}

    try {

        n2 += b.toString().split(".")[1].length;

    } catch (e) {}

    return (a * Math.pow(10, n1)) / (b * Math.pow(10, n2)) / Math.pow(10, n1 – n2);

}

// js小数相加,精确的结果应当是10090.701

var numAdd = Add(9856.556, 234.145);

// js小数相减,精确的结果应当是422.441

var numSub = Sub(656.55, 234.109);

// js小数相乘,精确的结果应当是51.285

var numMul = Mul(Mul(34.19, 0.03), 50);

// js小数相除,精确的结果应当是40.0936

var numDiv = Div(100.234, 2.5);

思想是计算出有几位小数(n),先转化成整数,保证用整数进行计算,再除以 10*n; 

发表评论