详谈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 相关文章推荐
CLASS_CONFUSION JS混淆 全源码
Dec 12 Javascript
前淘宝前端开发工程师阿当的PPT中有JS技术理念问题
Jan 15 Javascript
动感效果的TAB选项卡jquery 插件
Jul 09 Javascript
Extjs4.0设置Ext.data.Store传参的请求方式(默认为GET)
Apr 02 Javascript
jquery文本框中的事件应用以输入邮箱为例
May 06 Javascript
javascript event在FF和IE的兼容传参心得(绝对好用)
Jul 10 Javascript
Bootstrap布局组件教程之Bootstrap下拉菜单
Jun 12 Javascript
canvas实现绘制吃豆鱼效果
Jan 12 Javascript
js实现网页定位导航功能
Mar 07 Javascript
React-Native中禁用Navigator手势返回的示例代码
Sep 09 Javascript
深入理解js A*寻路算法原理与具体实现过程
Dec 13 Javascript
react中useState使用:如何实现在当前表格直接更改数据
Aug 05 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
PHP与SQL注入攻击[一]
2007/04/17 PHP
php缓冲 output_buffering和ob_start使用介绍
2014/01/30 PHP
将php数组输出html表格的方法
2014/02/24 PHP
php+ajax实现文章自动保存的方法
2014/12/30 PHP
php基于jquery的ajax技术传递json数据简单实例
2016/04/15 PHP
php实现数字补零的方法总结
2018/09/12 PHP
PHP Web表单生成器案例分析
2020/06/02 PHP
javascript 获取元素位置的快速方法 getBoundingClientRect()
2009/11/26 Javascript
html文件中jquery与velocity变量中的$冲突的解决方法
2013/11/01 Javascript
jQuery 隐藏和显示 input 默认值示例
2014/06/03 Javascript
yepnope.js使用详解及示例分享
2014/06/23 Javascript
基于JQuery制作可编辑的表格特效
2014/12/23 Javascript
jquery获取多个checkbox的值异步提交给php
2015/07/07 Javascript
Bootstrap 模态框(Modal)带参数传值实例
2017/08/20 Javascript
探索webpack模块及webpack3新特性
2017/09/18 Javascript
webpack实用小功能介绍
2018/01/02 Javascript
详解Vue-cli webpack移动端自动化构建rem问题
2018/04/07 Javascript
vue.js+element-ui动态配置菜单的实例
2018/09/07 Javascript
Puppeteer环境搭建的详细步骤
2018/09/21 Javascript
Net微信网页开发 使用微信JS-SDK获取当前地理位置过程详解
2019/08/26 Javascript
Vue form表单动态添加组件实战案例
2019/09/02 Javascript
JavaScript装箱及拆箱boxing及unBoxing用法解析
2020/06/15 Javascript
vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
2021/03/01 Vue.js
Python获取apk文件URL地址实例
2013/11/01 Python
Python学习入门之区块链详解
2017/07/25 Python
pycharm 取消默认的右击运行unittest的方法
2018/11/29 Python
Python实现ATM系统
2020/02/17 Python
python SOCKET编程基础入门
2021/02/27 Python
全球速卖通:AliExpress(国际版淘宝)
2017/09/20 全球购物
物流经理自我评价
2013/09/23 职场文书
小学数学课题方案
2014/06/15 职场文书
初中信息技术教学计划
2015/01/22 职场文书
幼师中班个人总结
2015/02/12 职场文书
大学生学年个人总结
2015/02/15 职场文书
赵氏孤儿观后感
2015/06/09 职场文书
《学会看病》教学反思
2016/02/17 职场文书