js number最大长度_解决JS浮点数运算结果不精确的Bug

263e67e0da79194e22b428235cdb5c2f.png前言 263e67e0da79194e22b428235cdb5c2f.png

最近在做项目的时候,涉及到产品价格的计算,经常会出现JS浮点数精度问题,这个问题,对于财务管理系统的开发者来说,是个非常严重的问题(涉及到钱相关的问题都是严重的问题),这里把相关的原因和问题的解决方案整理一下,也希望给各位提供一些参考。

01常见例子
// 加法0.1 + 0.2 = 0.300000000000000040.1 + 0.7 = 0.79999999999999990.2 + 0.4 = 0.6000000000000001// 减法0.3 - 0.2 = 0.099999999999999981.5 - 1.2 = 0.30000000000000004// 乘法0.8 * 3 = 2.400000000000000419.9 * 100 = 1989.9999999999998// 除法0.3 / 0.1 = 2.99999999999999960.69 / 10 = 0.06899999999999999// 比较0.1 + 0.2 === 0.3 // false   (0.3 - 0.2) === (0.2 - 0.1) // false
0 2导致原因

JavaScript 内部只有一种数字类型Number,也就是说,JavaScript 语言的底层根本没有整数,所有数字都是以IEEE-754标准格式64位浮点数形式储存,11.0是相同的。因为有些小数以二进制表示位数是无穷的。JavaScript会把超出53位之后的二进制舍弃,所以涉及小数的比较和运算要特别小心。

0 3 IEEE二进制浮点数算术标准(IEEE 754)IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。0 4 浮点数的存储

JS的浮点数实现也是遵循IEEE 754标准,采用双精度存储(double precision),使用64位固定长度来表示,其中1位用来表示符号位,11位用来表示指数,52位表示尾数。如下图:

49d94c36c5c24f3cbd3f55a746ea9af1.png

  • 符号位(sign):第1位是正负数符号位,0代表正数,1代表负数

  • 指数位(Exponent):中间11位存储指数,用来表示次方数

  • 尾数位(mantissa):最后的52位是尾数,超出部分自动进一舍零

0 5浮点数的计算步骤(0.1+0.2)

【1】首先,十进制的0.1和0.2会转换成二进制的,但是由于浮点数用二进制表示是无穷的

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

【2】IEEE754标准的64位双精度浮点数的小数部分最多支持53位二进制,多余的二进制数字被截断,所以两者相加之后的二进制之和是

 0.0100110011001100110011001100110011001100110011001101

【3】将截断之后的二进制数字再转换为十进制,就成了0.30000000000000004,所以在计算时产生了误差

0 6解决办法

【1】引用类库

  1. Math.js 

  2.  decimal.js   

  3.  big.js

【2】思路一:在知道小数位个数的前提下,可以考虑通过将浮点数放大倍数到整型(最后再除以相应倍数),再进行运算操作,这样就能得到正确的结果了

0.1 + 0.2 ——> (0.1 * 10 + 0.2 * 10) / 10 // 0.3  0.8 * 3 ——> ( 0.85 * 100 * 3) / 100         //2.4

【3】自定义一个转换和处理函数

// f代表需要计算的表达式,digit代表小数位数Math.formatFloat = function (f, digit) {// Math.pow(指数,幂指数)var m = Math.pow(10, digit);// Math.round() 四舍五入return Math.round(f * m, 10) / m;    }console.log(Math.formatFloat(0.3 * 8, 1));  // 2.4    console.log(Math.formatFloat(0.35 * 8, 2));  // 2.8

【4】加法函数

/**     ** 加法函数,用来得到精确的加法结果     ** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。     ** 调用:accAdd(arg1,arg2)     ** 返回值:arg1加上arg2的精确结果     **/function accAdd(arg1, arg2) {var r1, r2, m, c;try {        r1 = arg1.toString().split(".")[1].length;      } catch (e) {        r1 = 0;      }try {        r2 = arg2.toString().split(".")[1].length;      } catch (e) {        r2 = 0;      }      c = Math.abs(r1 - r2);      m = Math.pow(10, Math.max(r1, r2));if (c > 0) {var cm = Math.pow(10, c);if (r1 > r2) {          arg1 = Number(arg1.toString().replace(".", ""));          arg2 = Number(arg2.toString().replace(".", "")) * cm;        } else {          arg1 = Number(arg1.toString().replace(".", "")) * cm;          arg2 = Number(arg2.toString().replace(".", ""));        }      } else {        arg1 = Number(arg1.toString().replace(".", ""));        arg2 = Number(arg2.toString().replace(".", ""));      }return (arg1 + arg2) / m;    }//给Number类型增加一个add方法,调用起来更加方便。Number.prototype.add = function (arg) {return accAdd(arg, this);    };

【5】减法函数

/**     ** 减法函数,用来得到精确的减法结果     ** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。     ** 调用:accSub(arg1,arg2)     ** 返回值:arg1加上arg2的精确结果     **/function accSub(arg1, arg2) {var r1, r2, m, n;try {        r1 = arg1.toString().split(".")[1].length;      } catch (e) {        r1 = 0;      }try {        r2 = arg2.toString().split(".")[1].length;      } catch (e) {        r2 = 0;      }      m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度      n = (r1 >= r2) ? r1 : r2;return ((arg1 * m - arg2 * m) / m).toFixed(n);    }// 给Number类型增加一个mul方法,调用起来更加方便。Number.prototype.sub = function (arg) {return accMul(arg, this);    };

【6】乘法函数

/**     ** 乘法函数,用来得到精确的乘法结果     ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。     ** 调用:accMul(arg1,arg2)     ** 返回值:arg1乘以 arg2的精确结果     **/function accMul(arg1, arg2) {var m = 0,        s1 = arg1.toString(),        s2 = arg2.toString();try {        m += s1.split(".")[1].length;      } catch (e) {}try {        m += s2.split(".")[1].length;      } catch (e) {}return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);    }// 给Number类型增加一个mul方法,调用起来更加方便。Number.prototype.mul = function (arg) {return accMul(arg, this);    };

【7】除法函数

/**     ** 除法函数,用来得到精确的除法结果     ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。     ** 调用:accDiv(arg1,arg2)     ** 返回值:arg1除以arg2的精确结果     **/function accDiv(arg1, arg2) {var t1 = 0,        t2 = 0,        r1, r2;try {        t1 = arg1.toString().split(".")[1].length;      } catch (e) {}try {        t2 = arg2.toString().split(".")[1].length;      } catch (e) {}with(Math) {        r1 = Number(arg1.toString().replace(".", ""));        r2 = Number(arg2.toString().replace(".", ""));return (r1 / r2) * pow(10, t2 - t1);      }    }//给Number类型增加一个div方法,调用起来更加方便。Number.prototype.div = function (arg) {return accDiv(this, arg);    };
扫码关注更多精彩 946fc169ade577417941a155f37d9743.png 101e7e82a93cf970e29ee48c15dc3b91.png a4d1370126d2ac4b0b7dff4eaec72057.png点个 在看,2020暴富
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值