JS浮点数运算结果不精确的Bug解决


Posted in Javascript onAugust 01, 2019

前言

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

一. 常见例子  

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

 // 减法
 0.3 - 0.2 = 0.09999999999999998
 1.5 - 1.2 = 0.30000000000000004

 // 乘法
 0.8 * 3 = 2.4000000000000004
 19.9 * 100 = 1989.9999999999998

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

 // 比较
 0.1 + 0.2 === 0.3 // false
 (0.3 - 0.2) === (0.2 - 0.1) // false

二. 导致原因

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

三. IEEE二进制浮点数算术标准(IEEE 754)

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

四. 浮点数的存储

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

JS浮点数运算结果不精确的Bug解决

  • ​符号位(sign):第1位是正负数符号位,0代表正数,1代表负数
  • 指数位(Exponent):中间11位存储指数,用来表示次方数
  • 尾数位(mantissa):最后的52位是尾数,超出部分自动进一舍零

五. 浮点数的计算步骤(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,所以在计算时产生了误差

六. 解决办法

【1】引用类库

  • Math.js 
  • decimal.js   
  • big.js

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

0.1 + 0.2 ——> (0.1 * 10 + 0.2 * 10) / 10 // 0.3
0.8 * 3 ——> ( 0.8 * 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);
  };

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript form 验证函数 弹出对话框形式
Jun 23 Javascript
IE iframe的onload方法分析小结
Jan 07 Javascript
Javascript 去除数组的重复元素
May 04 Javascript
基于jquery的一个拖拽到指定区域内的效果
Sep 21 Javascript
jquery如何判断表格同一列不同行input数据是否重复
May 14 Javascript
javascript自动生成包含数字与字符的随机字符串
Feb 09 Javascript
js判断checkbox是否选中个数的方法(超简单)
Aug 19 Javascript
详解XMLHttpRequest(二)响应属性、二进制数据、监测上传下载进度
Sep 14 Javascript
vue2.0多条件搜索组件使用详解
Mar 26 Javascript
js如何获取网页所有图片
May 12 Javascript
ECMAScript6变量的解构赋值实例详解
Sep 19 Javascript
微信小程序实现Session功能及无法获取session问题的解决方法
May 07 Javascript
微信小程序动画组件使用解析,类似vue,且更强大
Aug 01 #Javascript
vue项目中全局引入1个.scss文件的问题解决
Aug 01 #Javascript
jQuery中DOM操作原则实例分析
Aug 01 #jQuery
详解package.json版本号规则
Aug 01 #Javascript
ES6 新增的创建数组的方法(小结)
Aug 01 #Javascript
详解基于Wepy开发小程序插件(推荐)
Aug 01 #Javascript
深入浅析Vue中mixin和extend的区别和使用场景
Aug 01 #Javascript
You might like
PHP面向对象程序设计模拟一般面向对象语言中的方法重载(overload)示例
2019/06/13 PHP
javascript检查表单数据是否改变的方法
2013/07/30 Javascript
ExtJS4中使用mixins实现多继承示例
2013/12/03 Javascript
动态加载jquery库的方法
2014/02/12 Javascript
Javascript保存网页为图片借助于html2canvas库实现
2014/09/05 Javascript
jquery常用函数与方法汇总
2015/09/01 Javascript
JavaScript的函数式编程基础指南
2016/03/19 Javascript
jquery动态遍历Json对象的属性和值的方法
2016/07/27 Javascript
jQuery动态创建元素以及追加节点的实现方法
2016/10/20 Javascript
Javascript继承机制详解
2017/05/30 Javascript
JS实现点击Radio动态更新table数据
2017/07/18 Javascript
微信小程序日期选择器实例代码
2018/07/18 Javascript
js代码规范之Eslint安装与配置详解
2018/09/08 Javascript
jquery简单实现纵向的无缝滚动代码实例
2019/04/01 jQuery
详解VUE前端按钮权限控制
2019/04/26 Javascript
使用jquery-easyui的布局layout写后台管理页面的代码详解
2019/06/19 jQuery
vue自定义表单生成器form-create使用详解
2019/07/19 Javascript
Node.js API详解之 querystring用法实例分析
2020/04/29 Javascript
typescript编写微信小程序创建项目的方法
2021/01/29 Javascript
Python base64编码解码实例
2015/06/21 Python
分享python数据统计的一些小技巧
2016/07/21 Python
Python基础学习之常见的内建函数整理
2017/09/06 Python
在python中利用最小二乘拟合二次抛物线函数的方法
2018/12/29 Python
python实现小球弹跳效果
2019/05/10 Python
Python Web框架之Django框架文件上传功能详解
2019/08/16 Python
python 使用opencv 把视频分割成图片示例
2019/12/12 Python
Coccinelle官网:意大利的著名皮具品牌
2019/05/15 全球购物
《我不是最弱小的》教学反思
2014/02/23 职场文书
应用心理学专业求职信
2014/08/04 职场文书
快递员岗位职责
2014/09/12 职场文书
2014年财务人员工作总结
2014/11/11 职场文书
2015企业年终工作总结范文
2015/05/27 职场文书
思想工作总结范文
2015/08/12 职场文书
庆祝教师节主题班会
2015/08/17 职场文书
新学期开学寄语2016
2015/12/04 职场文书
创业计划书之养殖业
2019/10/11 职场文书