JS数字精度

2020-12-26 05:28

阅读:427

标签:php   知识点   数位   字符   str   不为   中间   rip   get   

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) + // 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数字精度

标签:php   知识点   数位   字符   str   不为   中间   rip   get   

原文地址:https://www.cnblogs.com/Qooo/p/13847904.html


评论


亲,登录后才可以留言!