Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day2 - 0.1 + 0.2 === 0.3 嘛?为什么?怎么解决? #2

Open
su37josephxia opened this issue Dec 21, 2021 · 18 comments
Open

Day2 - 0.1 + 0.2 === 0.3 嘛?为什么?怎么解决? #2

su37josephxia opened this issue Dec 21, 2021 · 18 comments

Comments

@su37josephxia
Copy link
Owner

su37josephxia commented Dec 21, 2021

@su37josephxia
Copy link
Owner Author

su37josephxia commented Dec 21, 2021

精度丢失可能出现在进制转换和对阶运算中

JavaScript 使用 Number 类型来表示数字(整数或浮点数),遵循 IEEE 754 标准,通过 64 位来表示一个数字(1 + 11 + 52)

1 符号位,0 表示正数,1 表示负数 s
11 指数位(e)
52 尾数,小数部分(即有效数字)

最大安全数字:Number.MAX_SAFE_INTEGER = Math.pow(2, 53) - 1,转换成整数就是 16 位,所以 0.1 === 0.1,是因为通过 toPrecision(16) 去有效位之后,两者是相等的。
在两数相加时,会先转换成二进制,0.1 和 0.2 转换成二进制的时候尾数会发生无限循环,然后进行对阶运算,JS 引擎对二进制进行截断,所以造成精度丢失。

高逼格ES6
浮点数的运算精度丢失问题。
可以引入ES6中的机器精度Number.EPSILON判定是计算误差还是数据不同。
Number.EPSILON为JavaScript可以表示的最小精度2^(-52)。

@liangle
Copy link

liangle commented Dec 22, 2021

浮点数的比较方法错了,正确的方法是比较绝对值是否在JS提供的最小精度范围内

console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON) //true

JS 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,根据浮点数的定义,非整数的 Number 类型无法用 ==(=== 也不行) 来比较。

console.log( 0.1 + 0.2 == 0.3) //false

浮点数运算的精度问题导致等式左右的结果并不是严格相等,而是相差了个微小的值。

@rachern
Copy link

rachern commented Dec 22, 2021

这是浮点数精度丢失的问题,经常出现在进制转换的时候

javascript 使用的是 IEEE-745 浮点数表示法,浮点数在计算机中是使用二进制进行存储,呈现给用户时使用十进制数,当 0.1 和 0.2 转换成二进制时,会出现无限循环,而双精度浮点数的小数部分最多支持 52 位,超过 52 位的部分或被截断,计算机就是使用被截断后的二进制数进行计算,然后在转换成十进制数返回给用户,这个过程就已经出现误差了,而误差的部分就是被计算机截断的部分导致的

解决方法有两种:
1.使用 ES6 的 Number.EPSILON 进行误差判断
2.获取加数中最多的小数位数 e,所有的加数同时放大 Math.pow(10, e) 倍,进行计算之后的结果再缩小 Math.pow(10, e) 倍

@bianzheCN
Copy link

bianzheCN commented Dec 22, 2021

不等于,这是由于 JS 存在精度丢失问题。

计算机存储小数的方法是将浮点数转化为对应的二进制存储,采用的标准是 IEEE 754。
JS 用 Number 类型,双精度浮点来表示所有的数字,用 64 bit 进行存储,

1 个符号位,表示正负号;
11 位表示指数,也就是次方
52 位用来表示小数位。

由于在 0.1, 0.2 转化成二进制之后是无限循环的数,所以会被截断,再转化为十进制之后,就会出现误差,导致二者相加不等于 0.3

解决方法:
使用 Number.EPSILON 作为误差判断,因为它是 1 和 比 1 大的最小浮点数的差值(2^-52),
只要符合:
Math.abs(0.2 - 0.3 + 0.1) < Number.EPSILON,则证明 0.1 + 0.2 === 0.3

@chunhuigao
Copy link

chunhuigao commented Dec 22, 2021

回答

不等于

原因

这是由于 JS 存在精度丢失问题。
JavaScript 存储数值是以双精度浮点数来存储的,采用的标准是 IEEE 754。
用双精度浮点数表示小数可能会得到一个无限循环的数,无限循环二进制转换为 10 进制会出现误差;
这里的 0.1+0.2 就是出现了这种误差

解决思路

解决本题

方法 1:可以将小数* $10^n$ 转换为整数,整数不存在精度丢失问题;
方法 2:使用 Number.EPSILON 作为误差判断

解决同类型题

方法:将数字转换为字符串,字符串逐位相加得到精确的结果;

最后

感谢 @liangle @rachern @bianzheCN 让我学到了 Number.EPSILON

@zcma11
Copy link

zcma11 commented Dec 22, 2021

这是浮点数的精度问题,JavaScript中的数字都是保存为双精度浮点数,在十进制转化成二进制的时候,不能被2除尽的数都无法精确表达,然后进行了截取。简单来说就是十进制转二进制计算这个过程是不完全准确的。计算时可以放大10的n次方转换成整数再进行计算。

@bianxuerui
Copy link

0.1 + 0.2 是不等于 0.3的,这是浮点数的精度的问题;
为了实现计算,极小数和极大数通常用科学计数法表示;
因为js始终遵循国际IEEE754标准,将数字存储为双精度浮点数;
其中数字存储在位 0 到 51 中,指数存储在位 52 到 62 中,符号存储在位 63 中
我们按 IEEE754 标准,将十进制的 0.1 转换为二进制的 0.1;
十进制小数转换成二进制小数采用"乘2取整,顺序排列"法;
具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零,在这里0.1和0.2是无法让积中的小数部分为零,所以是一个无限循环的数。
再根据双精度标准,我们将把其四舍五入到 52 位时会丢失精度,计算完再转回十进制时和理论结果不相同。

@yaoqq632319345
Copy link

  • 不等于
  • 0.1+0.2 在计算中会先转换为二进制,在转换过程中0.1和0.2都会转换为无限循环的小数,相加之后再转换回十进制,之后就会产生误差
  • 解决办法我的思路是转换成整数相加,加完再转回小数

@rhythm022
Copy link

在js中,0.1 + 0.2不等于0.3。
这个语言现象,其实不止在js里面会有,Java也会有同样的问题。具体原因:是因为语言的底层实现里面,他遵照的是IEEE 754这个标准,用浮点数去表示数字,像0.1/0.2这种数字其实是没有办法精确的用浮点数来表示出来的。
那么在做0.1 + 0.2的加法运算时,其实是做最接近于他们的浮点数表示的加法运算,那么加出来肯定不是0.3。
解决方式:先把被加数转化成整数,做好相加之后再转回小数,这样保留他们的精度。

@xyz-fish
Copy link

xyz-fish commented Dec 22, 2021

  1. 不等于

  2. 为什么

    js的浮点数精度丢失

    解析

    js 里的 Number 类型 遵循IEEE 754的标准:是一个 64 位的固定长度的浮点数, 第一个位置代表 正负,后52位是尾数 代表二进制的有效的数字 中间的11位用于指数

    因为有效数字是52位的,所以当数值转化成二进制的时候如果尾数大于52为的时候,会出现溢出, 就会按照IEE754的规则进行舍入(最近偶数)

    回到0.1 + 0.2 的问题,计算过程是先转二进制 进行相加 结果再转成我们看到的十进制
    0.1转化成二进制 通过 不断的取小数部分 乘 2,可以看出会无限循环下去,会出现溢出 大于52位 会按照规则舍入, 0.1 ~ 0.9 中只有0.5不会出现精度丢失的问题,其他的都会出现精度丢失的问题

    为什么 0.3 === 0.3 是true : 小数值会去16位精度 0.3 实际上可能是 0.300000000000000005, 也可能是0.3000000000000000051 取16 + 省略的一位位精度会相同

0.3 === 0.3 0.2 + 0.2 === 0.4 这个咋理解

  1. 怎么解决 精度丢失的问题
    1. 项目中没有过多计算的时候,会自己通过 * 1000 转成整数 除以 1000 配置 toFixed()展示
    2. 有较多数值计算的时候,采用现有处理好的库 mathjs 之类的

@jiafei-cat
Copy link

jiafei-cat commented Dec 22, 2021

肯定是不等于的

  • 在计算的时候会先转换为二进制,小数采用乘二取整方法获得二进制,象0.1就会出现无限小数
  • 又因为js number是64位双精度浮点类型,超过最大安全数时会被截取,导致二进制进位
  • 最终计算完转为10进制的结果不为0.3

解决方法

  • 转为整数计算完再转浮点数
  • BigInt
  • 第三方库(Math.js,Sinful.js,BigDecimal.js等) - 同学总结
  • 判断等式两边相减是否小于Number.EPSILON -同学总结
  • 转字符串运算(x.toPrecision() + y.toPrecision()) - 同学总结

@JanusJiang1
Copy link

JanusJiang1 commented Dec 22, 2021

结论:

0.1 + 0.2 !=0.3

原因:

JavaScript中的数字,是以双精浮点数在计算机中进行储存的,为二进制,占用64bit,既用64位数来表示一个数值。
而实际呈现出的数字,是以十进制的方式呈现的,这中间就涉及十进制到二进制的转化。

而0.1,0.2转化为二进制时,会出现无限循环的问题,所以由于位数限制会进行截断,而用截断过后的二进制数转换回十进制表示出来,就会自然出现精度缺失。

解决办法:

  • es6:Number.EPSILON属性表示浮点数运算的最小精度差,比较浮点数时,结果的差值少于这个值,即可认为判断相等

  • 通用:若不只是判断,是要保证计算结果精度的话,计算加法时,判断出两数最大小数点后位数,并依此共同转化为整数进行计算即可,在整数小于Math.pow(2,53)计算时,无精度缺失问题。

@zhenyuWang
Copy link

zhenyuWang commented Dec 23, 2021

不等,是因为 JavaScript 中使用基于 IEEE 754 数值的浮点计算,所以会产生舍入误差。

IEEE 754

IEEE 754 中双精度浮点数使用64 bit来进行存储:
第一位存储符号表示正负号 0 正 1 负
2-12位存储指数表示次方数
13-64位存储尾数表示精确度

小数在计算机中的存储方式:
小数(浮点数)会被转成对应的二进制数,并用科学计数法表示,
然后把这个数值数值通过 IEEE 754 标准表示成真正会在计算机中存储的值。

具体原因

0.1和0.2转换成二进制后是一个无限循环的二进制数,而尾数位只能存储52位有效数字,所以这个时候无法被存储的后续部分就进行了取舍,取舍的规则是在 IEEE 754中定义的。

为什么无限循环

转换二进制的过程:

  1. 整数部分模2取余法
    3 => 3%2 = 1 余 1
    1 => 1%2 = 0 余 1
    3(十进制)= 11(二进制)

4 => 4%2 = 2 余 0
2 => 2%2 = 1 余 0
1 => 1%2 = 0 余 1
4(十进制)= 100(二进制)

  1. 小数部分模2取整法

0.5 => 0.5*2 = 1 取整 1
0.5(十进制)= .1(二进制)

0.1 => 0.12 = 0.2 取整 0
0.2 => 0.2
2 = 0.4 取整 0
0.4 => 0.42 = 0.8 取整 0
0.8 => 0.8
2 = 1.6 取整 1
0.6 => 0.62 = 1.2 取整 1
0.2 => 0.2
2 = 0.4 取整 0
0.4 => 0.42 = 0.8 取整 0
0.8 => 0.8
2 = 1.6 取整 1
0.6 => 0.6*2 = 1.2 取整 1
...发生循环

浮点数的舍入

任何有效数上的运算结果,通常都存放在较长的寄存器中,当结果被放回浮点格式时,必须将多出来的比特丢弃。有多种方法可以用来执行舍入作业,实际上IEEE标准列出4种不同的方法:

  • 舍入到最接近:舍入到最接近,在一样接近的情况下偶数优先(Ties To Even,这是默认的舍入方式):会将结果舍入为最接近且可以表示的值,但是当存在两个数一样接近的时候,则取其中的偶数(在二进制中是以0结尾的)。
  • 朝+∞方向舍入:会将结果朝正无限大的方向舍入。
  • 朝-∞方向舍入:会将结果朝负无限大的方向舍入。
  • 朝0方向舍入:会将结果朝0的方向舍入。

00011001100110011001100110011... (0011)循环

0(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011) = 0(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)(0011)

所以 0.1 和 0.2 存储后就发生了精度丢失的问题,从而相加之后的结果不严格等于 0.3。

解决方案

  1. 使用 JavaScript 提供的最小精度值比较是否相等 => Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON
  2. 保留几位小数 比如金额,只需要精确到分即可
  3. 使用别人的轮子,例如:math.js
  4. 转成字符串相加(效率较低)

@MMmaXingXing
Copy link

十进制转二进制方法是"乘以2取整,顺序输出”;

计算机存储浮点数,会被转为二进制,0.1和0.2会被转为无限不循环小数,直到存满规范的浮点数存储空间,相加之后,尾部大小溢出,取和,最终变成0.30000000000000004

解决办法,使用第三方库,math.js 或者big.js,自己来计算的话也可以先将浮点类型转换为整形,最终再对应处理相加之后转回浮点型。

昨天写错地方了,挪过来

@Theseus-Xin
Copy link

图片

@BambooSword
Copy link

JS中的所有数字均用浮点数值表示,其采用IEEE 754标准定义64位浮点格式表示数字。

其表示方式为1位符号位,11位指数位,52位小数位。

js的表示的数字范围是由这些64位组成的。由于0.1和0.2用二进制表示是无限循环小数,而JS的浮点数计数标准中浮点数小数位只有52位,所以会有精度的丢失。我们知道在10进制计算过程中我们是四舍五入,而在二进制中只有0和1,所以是1进0舍,所以0.1+0.2的结果的偏差会照成实际结果要比0.3略大。
如何避免呢,原则上是无法避免的,这和某种语言无关,这个是和我们的储存方式相关。所以一般我们采用的解决方案是使用别人写好的库,比如'math.js'等

@yeqiuchan
Copy link

不等于
js 计算精度丢失问题
0.1+0.2 是转换给二进制进行存储的
0.1转换给二进制的结果是由0和1组成的无限小数
0.2也是超出计算精度,结果保留十六位小数
0.1在计算机内部不是精确的0.1
0.2在计算机内部不是精确的0.2
所以出现了0.1+0.2 不等0.3的情况

@zhe-he
Copy link

zhe-he commented Jan 11, 2022

console.log(
    (424325.2).toString(2), (42432225.2).toString(2),
    (424325.2).toString(2).length, (42432225.2).toString(2).length
)

image

为什么他们的长度不一样?按照52尾数来算,打印结果不应该都是54吗?

@su37josephxia su37josephxia changed the title 0.1 + 0.2 === 0.3 嘛?为什么?怎么解决? Day2 - 0.1 + 0.2 === 0.3 嘛?为什么?怎么解决? Jan 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests