为什么0.1+0.2不等于0.3

首先来看一段诡异的代码:

package org.sang.test;

/**
 * @Author: chuxia0811
 * @Date: 2021/2/9 22:55
 * @Description :
 */
public class _01_02 {
    public static void main(String[] args) {
        float a = 0.1f;
        float b = 0.2f;
        System.out.println(a + b);
        System.out.println(a + b == 0.3);
    }
}

输出结果如下:

0.3
false

很奇怪,打印的a+b明明等于0.3,判断a+b是否等于0.3时返回的却是false。

要解释这个首先要了解:有些浮点数不能完全精确的表示出来,我们先来了解计算机内部是如何表示数的。

计算机内部如何表示数

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

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

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

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

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

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

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

3. 如何用二进制表示0.1

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

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

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

0.1 * 2 = 0.2 # 0
0.2 * 2 = 0.4 # 0
0.4 * 2 = 0.8 # 0
0.8 * 2 = 1.6 # 1
0.6 * 2 = 1.2 # 1
0.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.1
var b = 0.2

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

总结

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

为什么0.1 + 0.2 不等于0.3。因为计算机不能精确表示0.1, 0.2这样的浮点数,计算时使用的是带有舍入误差的数。

并不是所有的浮点数在计算机内部都存在舍入误差,比如0.5就没有舍入误差
具有舍入误差的运算结可能会符合我们的期望,原因可能是“负负得正”。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初夏0811

你的鼓励将是我创作最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值