博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
0.1+0.2结果却不等于0.3
阅读量:6681 次
发布时间:2019-06-25

本文共 2668 字,大约阅读时间需要 8 分钟。

hot3.png

先看几个简单但诡异的代码:

0.1 + 0.2 > 0.3 // true0.1 * 0.1 = 0.0100000000000000021000000000000000128 === 1000000000000000129

0.1加0.2为什么就不等于0.3昵?要回答这个问题,得先了解计算机内部是如何表示数的。

计算机内部如何表示数

我们都知道,计算机用位来储存及处理数据。每一个二进制数(二进制串)都一一对应一个十进制数。

1. 计算机内部如何表示整数

这里以十进制数13来展示“按位计数法”如何表示整数:

十进制值 进制 按位格式 描述
13 10 13 1x10^1 + 3x10^0 = 10 + 3
13 2 1101 1x2^3 + 1x2^2 + 0x2^1 + 1x2^0 = 8 + 4 + 0 + 1

2. 计算机内部如何表示小数

再看小数怎么用按位计数法表示,以十进制数0.625为例:

十进制值 进制 按位格式 描述
0.625 10 0.625 6x10^-1 + 2x10^-2 + 5x10^-3 = 0.6 + 0.02 + 0.005
0.625 2 0.101 1x2^-1 + 0 x2^-2 + 1x2^-3 = 1/2 + 0 + 1/8

3. 如何用二进制表示0.1

关于十进制与二进制间如何转换,这里不细说,直接给出结论:

十进制整数转二进制方法:除2取余;十进制小数转二进制方法:乘2除整

十进制0.1转换成二进制,乘2取整过程:

0.1 * 2 = 0.2 # 00.2 * 2 = 0.4 # 00.4 * 2 = 0.8 # 00.8 * 2 = 1.6 # 10.6 * 2 = 1.2 # 10.2 * 2 = 0.4 # 0.....

从上面可以看出,0.1的二进制格式是:0.0001100011....。这是一个二进制无限循环小数,但计算机内存有限,我们不能用储存所有的小数位数。那么在精度与内存间如何取舍呢?

答案是:在某个精度点直接舍弃。当然,代价就是,0.1在计算机内部根本就不是精确的0.1,而是一个有舍入误差的0.1。当代码被编译或解释后,0.1已经被四舍五入成一个与之很接近的计算机内部数字,以至于计算还没开始,一个很小的舍入错误就已经产生了。这也就是 0.1 + 0.2 不等于0.3 的原因。

有误差的两个数,其计算的结果,当然就很可能与我们期望的不一样了。注意前面的这句话中的“很可能”这三个字?为啥是很可能昵?

0.1 + 0.1 为什么等于0.2

答案是:两个有舍入误差的值在求和时,相互抵消了,但这种“负负得正,相互抵消”不一定是可靠的,当这两个数字是用不同长度数位来表示的浮点数时,舍入误差可能不会相互抵消。

又如,对于 0.1 + 0.3 ,结果其实并不是0.4,但0.4是最接近真实结果的数,比其它任何浮点数都更接近。许多语言也就直接显示结果为0.4了,而不展示一个浮点数的真实结果了。

另外要注意,二进制能精确地表示位数有限且分母是2的倍数的小数,比如0.5,0.5在计算机内部就没有舍入误差。所以0.5 + 0.5 === 1

计算机这样胡乱舍入,能满足所有的计算需求吗

我们看两个现实的场景:

  • 对于一个修建铁路的工程师而言,10米宽,还是10.0001米宽并没有什么不同。铁路工程师就不需要这么高0.x这样的精度
  • 对于芯片设计师,0.0001米就会是一个巨大不同,他也永远不用处理超过0.1米距离

不同行业,要求的精度不是线性的,我们允许(对结果无关紧要的)误差存在。10.0001与10.001在铁路工程师看来都是合格的。

虽然允许误差存在,但程序员在使用浮点数进行计算或逻辑处理时,不注意,就可能出问题。记住,永远不要直接比较两个浮点的大小

var a = 0.1var b = 0.2if (a + b === 0.3) {  // doSomething}

JS中如何进入浮点数运算

将浮点运算转换成整数计算

整数是完全精度的,不存在舍入误差。例如,一些关于人民币的运算,都会以分为基本单位,计算采用分,展示再转换成元。当然,这样也有一些问题,会带来额外的工作量,如果那天人民币新增了一个货币单位,对系统的扩展性也会有考验。

使用进行运算

bignumber.js会在一定精度内,让浮点数计算结果符合我们的期望。

{  let x = new BigNumber(0.1);  let y = new BigNumber(0.2)  let z = new BigNumber(0.3)  console.log(z.equals(x.add(y))) // 0.3 === 0.1 + 0.2, true  console.log(z.minus(x).equals(y)) // true  console.log(z.minus(y).equals(x)) // true}
{  let x = 0.2  console.log(x * x === 0.04) // false  let y = new BigNumber(0.2)  let r = y.mul(y) // 0.04  console.log(r.equals(new BigNumber(0.04))) // true}

更多例子,可以看bignumber.js官方示例。

在浮点数运算中产生误差值的示例中,最出名应该是0.1 + 0.2 === 0.30000000000000004了,到底有多有名?看看这个网站就知道了。也就是说不仅是JavaScript会产生这种问题,只要是采用IEEE 754 Floating-point的浮点数编码方式来表示浮点数时,则会产生这类问题

小结

本文主要介绍了浮点数计算问题,简单回答了为什么以及怎么办两个问题:

  • 为什么0.1 + 0.2 不等于0.3。因为计算机不能精确表示0.1, 0.2这样的浮点数,计算时使用的是带有舍入误差的数
  • 并不是所有的浮点数在计算机内部都存在舍入误差,比如0.5就没有舍入误差
  • 具有舍入误差的运算结可能会符合我们的期望,原因可能是“负负得正”
  • 怎么办?1个办法是使用整型代替浮点数计算;2是不要直接比较两个浮点数,而应该使用bignumber.js这样的浮点数运算库

转载于:https://my.oschina.net/zhangyafei/blog/1941432

你可能感兴趣的文章
梯度下降(Gradient Descent)小结
查看>>
一起谈.NET技术,使用User Control做HTML生成
查看>>
一起谈.NET技术,ASP.NET调用.sql文件(二)
查看>>
谷歌启动搜索引擎新功能 网页Flash内容即时预览
查看>>
专访梭子鱼:以“立体交付”保障Web应用安全
查看>>
微软SQL Server 2012新特性Silverlight报表客户端 - Power View
查看>>
记一次网站收录数和排名的实现
查看>>
pthread_cond_wait()用法分析
查看>>
poj-3368 Frequent values ***
查看>>
Install IIS 7.5 PHP & FastCGI for PHP on Windows 7
查看>>
C#连接Excel示例代码和驱动
查看>>
彻底弄明白之java多线程中的volatile
查看>>
RPi 2B IPC webcam server
查看>>
(转)一文学会用 Tensorflow 搭建神经网络
查看>>
30幅非常漂亮的微距摄影作品欣赏
查看>>
6、关于ctemplate的一个例子
查看>>
Sql Server 锁资源模式详解
查看>>
标准电声系统测试
查看>>
SQLite移植手记
查看>>
继续聊WPF——用Blend自定义Listview控件的列表头
查看>>