详谈javascript精度问题与调整


Posted in Javascript onJuly 08, 2017

一个经典的问题:

0.1+0.2==0.3

答案是:false

因为:0.1+0.2=0.30000000000000004

第一次看到这个结果就是无比惊讶,下巴碰到地上,得深入了解下问题出在哪里,该怎么去调整。

产生问题的原因

在JS中数值类型就只有number类型,没有int,float,double之分,number类型实际上存储的就是IEEE754标准的浮点数,计算规则也是。

在表达式计算前,先要按照标准将两个数转成浮点数。

IEEE 754规定:

1.32位的浮点数(单精度),最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。

浮点数的表现形式:

x=(-1)^S*m*2^(e+127)

m=1.M

E=e+127

2.64位的浮点数(双精度),最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

浮点数的表现形式:

x=(-1)^S*m*2^(e+1023)

m=1.M

E=e+1023

我们就按照双精度浮点数的标准转一下看看。

首先按照规则将0.1转成二进制的浮点数。

0.1*2=0.2 //0
0.2*2=0.4 //00
0.4*2=0.8 //000
0.8*2=1.6 //0001
0.6*2=1.2 //00011
0.2*2=0.4 //000110
0.4*2=0.8 //0001100
0.8*2=1.6 //00011001
0.6*2=1.2 //000110011
0.2*2=0.4 //0001100110
0.4*2=0.8 //00011001100
0.8*2=1.6 //000110011001
0.6*2=1.2 //0001100110011
0.2*2=0.4 //00011001100110
0.4*2=0.8 //000110011001100
0.8*2=1.6 //0001100110011001
0.6*2=1.2 //00011001100110011
//省略

在转换中,会发现小数位的二进制值在不停的重复,转换没完没了了,因为乘不尽啊,不是10的倍数。

转换也不可能一直重复下去,按照标准规格化的要求凑满。

转换结果:

0.00011001100110011001100110011001100110011001100110011001

精度问题产生的第一个原因就在这里诞生了,按照标准算出来的二进制浮点数并不能都精确的表示一个小数,只是无限近似,0.5可以,因为5是10的倍数,转出来的小数位二进制不会重复。

我们看看再转回小数会怎么样,按照公式写成:

0*2^-1 + 0*2^-2 + 0*2^-3 + 1*2^-4 + 1*2^-5 + 0*2^-6 + 0*2^-7 + 1*2^-8 + 1*2^-9 + 0*2^-10 + 0*2^-11 + 1*2^-12 + 1*2^-13 + 0*2^-14 + 0*2^-15 + 1*2^-16 + 1*2^-17 + 0*2^-18 + 0*2^-19 + 1*2^-20 + 1*2^-21 + 0*2^-22 + 0*2^-23 + 1*2^-24 + 1*2^-25 + 0*2^-26 + 0*2^-27 + 1*2^-28 + 1*2^-29 + 0*2^-30 + 0*2^-31 + 1*2^-32 + 1*2^-33 + 0*2^-34 + 0*2^-35 + 1*2^-36 + 1*2^-37 + 0*2^-38 + 0*2^-39 + 1*2^-40 + 1*2^-41 + 0*2^-42 + 0*2^-43 + 1*2^-44 + 1*2^-45 + 0*2^-46 + 0*2^-47 + 1*2^-48 + 1*2^-49 + 0*2^-50 + 0*2^-51 + 1*2^-52 + 1*2^-53 + 0*2^-54 + 0*2^-55 + 1*2^-56

计算结果:

0.09999999999999999167332731531133

精度就在这里丢了一次。就是转换成小数位的二进制的时候。

按照表现形式的要求,要写成x=(-1)^s*m*2^(e+1023),m=1.M的格式,按照要求尾数m的左边最高位总是1,所以要上面小数二进制结果的小数点进行移动

移动前:

0.00011001100110011001100110011001100110011001100110011001

移动后:

1.1001100110011001100110011001100110011001100110011001*2^-4

小数点右边选取要求的52位,上面的结果因为是提前算好,所以就省略了截取工作。

因为小数点最左侧的最高位总是1,所以它是不用存储的,那么虽然存储的是52位,但实际上可以表示53位的浮点数。

S=0,E=-4+1023=1019,m=1.M=1.1001100110011001100110011001100110011001100110011001,M=1001100110011001100110011001100110011001100110011001

浮点数表示:

x=-1^0*1.1001100110011001100110011001100110011001100110011001*2^1019

浮点数存储值(最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M):

0 ‭001111111011‬ 1001100110011001100110011001100110011001100110011001

同理0.2的IEEE754的转换后的结果:

浮点数表示:

-1^0*1.1001100110011001100110011001100110011001100110011001*2^1020

浮点数存储值(最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M):

0 ‭001111111100‬ 1001100110011001100110011001100110011001100110011001

接下来,按照IEEE754的加法规则,运算过程为:

1.0操作数的检查。

2.比较阶码大小并对阶。

3.尾数进行加法运算。

4.结果规格化。

5.舍入处理。

6.溢出处理。

按照计算过程,结果规格化、舍入处理、溢出处理都会遭成精度问题。

总结来看,造成精度问题的环节:

1.小数向二进制转换。

2.运算过程中的规格化,舍入、溢出处理。

精度调整

两种方法可以进行调整。

1.使用toFixed函数对小数位进行四舍五入。

但是其返回值是字符串,其参数是0 ~ 20之间的值,需要注意。

(0.1+0.2).toFixed(1) // '0.3'

2.无小数运算,运算结果附上小数点

使用该方法,要注意因为要变成整数再计算,对于一个小数点后位数很多的数来运算的时候,要注意溢出。

//加
function add(arg1,arg2){ 
 var digits1,digits2,maxDigits; 
 try{digits1=arg1.toString().split(".")[1].length}catch(e){digits1=0} 
 try{digits2=arg2.toString().split(".")[1].length}catch(e){digits2=0} 
 maxDigits=Math.pow(10,Math.max(digits1,digits2)) 
 return (arg1*maxDigits+arg2*maxDigits)/maxDigits 
} 
 
//减
function sub(arg1,arg2){ 
 var digits1,digits2,maxDigits; 
 try{digits1=arg1.toString().split(".")[1].length}catch(e){digits1=0} 
 try{digits2=arg2.toString().split(".")[1].length}catch(e){digits2=0} 
 maxDigits=Math.pow(10,Math.max(digits1,digits2)); 
 return (arg1*maxDigits-arg2*maxDigits)/maxDigits; 
} 

//乘
function mul(arg1,arg2) { 
 var digits=0,s1=arg1.toString(),s2=arg2.toString(); 
 try{digits+=s1.split(".")[1].length}catch(e){} 
 try{digits+=s2.split(".")[1].length}catch(e){}
 return Number(s1.replace(".",""))*Number(s2.replace(".",""))/Math.pow(10,digits); 
}

//除
function div(arg1,arg2){ 
 var int1=0,int2=0,digits1,digits2; 
 try{digits1=arg1.toString().split(".")[1].length}catch(e){digits1=0} 
 try{digits2=arg2.toString().split(".")[1].length}catch(e){digits2=0} 
 
 int1=Number(arg1.toString().replace(".","")) 
 int2=Number(arg2.toString().replace(".","")) 
 return (int1/int2)*Math.pow(10,digits2-digits1); 

}

以上这篇详谈javascript精度问题与调整就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 获取图片颜色
Apr 05 Javascript
基于Jquery实现键盘按键监听
May 11 Javascript
jQuery实现简单滚动动画效果
Apr 07 Javascript
jQuery Ajax和getJSON获取后台普通json数据和层级json数据用法分析
Jun 08 Javascript
Angular.js中上传指令ng-upload的基本使用教程
Jul 30 Javascript
使用vuex的state状态对象的5种方式
Apr 19 Javascript
vue input输入框模糊查询的示例代码
May 22 Javascript
使用imba.io框架得到比 vue 快50倍的性能基准
Jun 17 Javascript
如何利用node.js开发一个生成逐帧动画的小工具
Dec 01 Javascript
JavaScript的console命令使用实例
Dec 03 Javascript
Vue组件基础用法详解
Feb 05 Javascript
JavaScript语句错误throw、try及catch实例解析
Aug 18 Javascript
javascript定时器取消定时器及优化方法
Jul 08 #Javascript
Javascript 一些需要注意的细节(必看篇)
Jul 08 #Javascript
JQuery 获取Dom元素的实例讲解
Jul 08 #jQuery
深入理解jquery的$.extend()、$.fn和$.fn.extend()
Jul 08 #jQuery
浅谈jQuery框架Ajax常用选项
Jul 08 #jQuery
js中变量的连续赋值(实例讲解)
Jul 08 #Javascript
理解 javascript 中的函数表达式与函数声明
Jul 07 #Javascript
You might like
解析dedeCMS验证码的实现代码
2013/06/07 PHP
PHP的反射类ReflectionClass、ReflectionMethod使用实例
2014/08/05 PHP
PHP也能干大事之PHP中的编码解码详解
2015/04/20 PHP
微信小程序 消息推送php服务器验证实例详解
2017/03/30 PHP
用XMLDOM和ADODB.Stream实现base64编码解码实现代码
2010/11/28 Javascript
Javascript中查找不以XX字符结尾的单词示例代码
2013/10/15 Javascript
ZeroClipboard插件实现多浏览器复制功能(支持firefox、chrome、ie6)
2014/08/30 Javascript
javascript实现博客园页面右下角返回顶部按钮
2015/02/22 Javascript
javascript的正则匹配方法学习
2016/02/24 Javascript
微信小程序 Toast自定义实例详解
2017/01/20 Javascript
详解Nodejs之静态资源处理
2017/06/05 NodeJs
详解node child_process模块学习笔记
2018/01/24 Javascript
angular4自定义组件非input元素实现ngModel双向数据绑定的方法
2018/12/28 Javascript
详解小程序如何避免多次点击,重复触发事件
2019/04/08 Javascript
vscode 调试 node.js的方法步骤
2020/09/15 Javascript
解决Can't find variable: SockJS vue项目的问题
2020/09/22 Javascript
Python计算三角函数之asin()方法的使用
2015/05/15 Python
实例介绍Python中整型
2019/02/11 Python
Pycharm运行加载文本出现错误的解决方法
2019/06/27 Python
django settings.py 配置文件及介绍
2019/07/15 Python
深入解析神经网络从原理到实现
2019/07/26 Python
详解用selenium来下载小姐姐图片并保存
2021/01/26 Python
日常奢侈品,轻松购物:Verishop
2019/08/20 全球购物
美体小铺法国官方网站:The Body Shop法国
2020/06/04 全球购物
中科方德软件测试面试题
2016/04/21 面试题
EJB与JAVA BEAN的区别
2016/08/29 面试题
工商技校毕业生自荐信
2013/11/15 职场文书
自我评价正确写法范文
2013/12/10 职场文书
合作意向书格式及范文
2014/03/31 职场文书
4S店售后客服自我评价
2014/04/09 职场文书
廉洁自律个人总结
2015/02/14 职场文书
罗马假日观后感
2015/06/08 职场文书
2015年教学副校长工作总结
2015/07/22 职场文书
小学记事作文之200字
2019/08/06 职场文书
golang elasticsearch Client的使用详解
2021/05/05 Golang
Win11怎样将锁屏账户头像图片改成动画视频
2021/11/21 数码科技