JS数字精度
Posted 酷儿q
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS数字精度相关的知识,希望对你有一定的参考价值。
0.前言
最近在看计算机组成原理的浮点数部分,突然想起之前看过的一道快手面试题
为什么js中0.1+0.2不等于0.3,应该如何解决?
这里我们可以借这道题来说一下js的精度问题
1.JS数的储存
二进制和浮点数和定点数
首先计算机里面的数据肯定以二进制形式存储
对于同一段二进制码,不同的解读方式肯定有不同的意义
对于小数,我们有定点数和浮点数两种表示方法
目前计算机大多用浮点数,精度高,表示范围大
一个数以浮点数二进制码形式储存,我们从二进制浮点数码中能算出表达的二进制,然后二进制又可以得到相应的十进制,这就是他们的转化关系
复习浮点数
我们复习一下计组中对浮点数的介绍 这里以32位为例
如上图,32位二进制码中有三个部分,符号位,指数位,尾数位
浮点数计算公式:
从左往右看有三个部分,符号位指数位,尾数位
(1)(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
(2)2^E表示指数位。
(3)M表示有效数字,大于等于1,小于2。
这里有两个注意点,
1.M由于恒定为1.xxx,所以默认省略1
2.E不全为0或不全为1。这时E=E-127或者E=E-1023(64位)
我们按上面的规则算一下为什么图中0011111000100...00表示的0.15625
首先指数位置01111100表示的是124 则E=124-127=-3
然后我们看尾数,尾数位为1.01(加上了隐藏的1)
所以v=1.01*2的-3次方=0.00101
注意这个是二进制结果,转化为十进制的就是2(-3次方)+2(-5次方)=0.125+0.125*0.25=0.15625
JS中数值
javascript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,1与1.0是相同的,是同一个数。
我们看一下64位的JS数字是怎么储存的
JS中1和0.1的表示方法
在http://www.binaryconvert.com/这个网站上我们可以找到一个数的二进制浮点数表示
我们手动按上面的方法算一下,并且验证看对不对
1的表示方法
1对应的二进制1.00000
那么1对应的浮点数应该长成这样
1.0* 2的0次方
指数为0 尾数为1.0 所以指数实际是1023 尾数是0000000000000...00
看来我们算的是对的
0.1的表示方法
0.1对应的二进制 0.000110011001100...(循环)
那么1对应的浮点数应该长成这样
1.10011001100....*2的-4次方
所以尾数位应该是10011001100....
指数应该是1019也就是01111111011
看来我们是对的
2.精度产生的原因
为什么精度会产生呢
首先0.1这种转化为二进制码是有误差的,尾数是一个不断循环的数,明显会有误差
其次,在浮点数加法运算里面,有对阶操作。対阶会损失一部分尾数,如果尾数后面都是0,没影响。但是如果是0.1转换成的二进制浮点数码的尾数,対阶的时候舍弃部分尾数明显也会造成误差。
如果一个大数和一个小数相加时,会产生很大的误差,因为対阶的时候尾数得截掉好多位
(1+0.1).toPrecision(20) //"1.1000000000000000888"
(100000000000+0.1).toPrecision(20)
"100000000000.10000610"
很明显误差大了
东莞vi设计https://www.houdianzi.com/dgvi/ 豌豆资源网站大全https://55wd.com
3.关于精度的额外知识点
0.1并不是0.1
上面的内容你如果理解了,你再看到0.1,你就会清楚,0.1并不是0.1,0.1的浮点数二进制码是有误差的,不可能算出0.1
我们看到的是浏览器帮我们做了处理的
不信,你可以试试
0.1.toPrecision(17)
// "0.10000000000000001"
JS安全数
面试喜欢考这个问题,啥叫安全数,就是在这个范围数值都有一正一反,一一对应
尾数一共52位,加上一个隐藏位,共53位
也就是JS能表示的最大整数是2的53次方,这个数是16位
但是
Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
实际上2的53次方都不安全了,所以是2的53次方-1
在es6中 是Number.MAX_SAFE_INTEGER
toPrecision vs toFixed
数据处理时,这两个函数很容易混淆。它们的共同点是把数字转成字符串供展示使用。注意在计算的中间过程不要使用,只用于最终结果。
不同点就需要注意一下:
- toPrecision是处理精度,精度是从左至右第一个不为0的数开始数起。
- toFixed是小数点后指定位数取整,从小数点开始数起。
两者都能对多余数字做凑整处理,也有些人用toFixed来做四舍五入,但一定要知道它是有 Bug 的。
如:1.005.toFixed(2)返回的是1.00而不是1.01。
原因:1.005实际对应的数字是1.00499999999999989,在四舍五入时全部被舍去!
怎么解决这个问题呢,引入mathjs用它的round方法也可以,自己写一套字符串逻辑去处理也可以
4.解决方案
误差主要产生在进制转化和浮点数运算的対阶操作中
整数由于尾数后面全是0,同时转化为二进制数没有误差,所以
我们第一种方案就是全部转化为整数,计算完再转化为小数类似这种
/**
* 精确加法
*/
function add(num1, num2) {
const num1Digits = (num1.toString().split(‘.‘)[1] || ‘‘).length;
const num2Digits = (num2.toString().split(‘.‘)[1] || ‘‘).length;
const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
return (num1 * baseNum + num2 * baseNum) / baseNum;
}
我们第二种方案就是用现成的库 mathjs之类的,原理就是不走浮点数那一套,转化成字符串,自己实现运算逻辑,从性能上说肯定比原生慢一点
当然如果仅仅是展示类型的,3.0000000001保留两位小数的这种还是tofixed(2)最快哦
以上是关于JS数字精度的主要内容,如果未能解决你的问题,请参考以下文章