为什么在大多数编程语言中 0.1 + 0.2 不等于 0.3?

简介: 为什么在大多数编程语言中 0.1 + 0.2 不等于 0.3?

前言


在文章开始之前先看下面“诡异”的一幕。

a, b = 0.1, 0.2
print(a + b == 0.3)
print(a + b)
out:
False
0.30000000000000004
复制代码

0.1 + 0.2 == 0.3 结果竟然为 False ?不知道大家第一次见到这个场景作何感想,反正我是有点怀疑人生,为什么会产生这样的结果呢,下面详细看一下。


浮点数的限制


浮点数在计算机硬件中表示为一个以 2 为基数(二进制)的小数。我们先看看如果用十进制和二进制来表示0.125(10)。

十进制

0.125(10)

等于

1\times10^{-1} + 2\times10^{-2} + 5\times10^{-3} = \cfrac{1}{8}

>**二进制**<br> >0.001(2)<br> >$$ 0\times2^{-1} + 0\times2^{-2} + 1\times2^{-3} = \cfrac{1}{8}

这两个小数均表示 0.125(10),唯一真正的区别是第一个是以 10 为基数的小数表示法,第二个则是 2 为基数。


不幸的是,大多数的十进制小数都不能精确地表示为二进制小数,但有些浮点数也能够用二进制精确的表述,条件是位数有限且分母能表示成 2^n 的小数。如 0.5, 0.125。这将导致在大多数情况下,你输入的十进制浮点数都只能近似地以二进制浮点数形式储存在计算机中。


正如上文中的 0.1 ,我们手动计算一下它的二进制结果。

注:十进制整数转二进制方法:除2取余;十进制小数转二进制方法:乘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.4 * 2 = 0.8 # 0
.....
复制代码


从上面结果可以看出,0.1 的二进制为:

0.0001100110011001100110011001100110011001100110011...
复制代码

这是一个二进制无限循环小数,但计算机内存有限,我们不能储存所有的小数位数。那如何解决呢?

答案就是从末尾某个位置截断,直接取近似值,因此,在目前大部分编程语言(支持处理器浮点运算)中,浮点数都只能近似地使用二进制小数表示。


很多人使用 Python 的时候都不会意识到这个差异的存在,因为 Python 只会输出计算机中存储的二进制值的十进制近似值。但我们要牢记,即使输出的结果看起来好像就是 0.1 的精确值,实际储存的值只是最接近 0.1 的计算机可表示的二进制值。


解决方式


1.decimal

decimal 模块可以进行十进制数学计算,我们将浮点数转成字符串进行运算。

from decimal import Decimal
a, b = Decimal('0.1'), Decimal('0.2')
a + b == Decimal('0.3')
out:True
复制代码


2.numpy.float32

numpy 模块中的32为浮点型保存数据。

import numpy as np
temp = np.array([0.1, 0.2, 0.3], dtype=np.float32)
temp[0] + temp[1] == temp[2]
复制代码


当然体高精度的同时,性能可能会降低,在实际应用中这些近似值造成的细微偏差可能不会造成什么影响。如果碰到了留个心眼就好!

说了这么多,总结出一句话就是:浮点数转二进制时丢失了精度,计算完再转回十进制时和理论结果不同。不知道大家get到了吗?



相关文章
|
9月前
|
Python
使用python统计字符串中字母个数的函数程序设计
要统计Python字符串中的字母,首先就应该要判断出这些字符为字母,那该如何判断呢?我们可以将该字符串通过Python内置的字符串方法upper()来全部转换为大写,然后通过for循环来遍历该字符串,每次迭代过程中都使用isupper()方法来判断该字符是否为大写。这样就可以避免将字符串中的中文统计在内。我们首先来通过一个实例来了解isupper()方法的用法,如下:
95 2
|
2月前
|
C语言
C 语言中所有可用的关系运算符
C 语言中所有可用的关系运算符。
137 82
|
5月前
|
大数据 Python
6-11|Python中保证两位小数的方法
6-11|Python中保证两位小数的方法
|
8月前
|
Python
Python中限制输入数范围的技巧与实践
Python中限制输入数范围的技巧与实践
145 6
|
8月前
|
存储 Dart 编译器
Dart编程语言中的数值类型与运算
Dart编程语言中的数值类型与运算
142 0
|
9月前
|
Java Go C++
Rust每日一练(Leetday0026) 最小覆盖子串、组合、子集
Rust每日一练(Leetday0026) 最小覆盖子串、组合、子集
74 0
Rust每日一练(Leetday0026) 最小覆盖子串、组合、子集
|
Rust Go
Rust 基础入门 —— 字符、布尔、单元 类型
布尔类型(bool) 说明一点,bool类型的应用场景 主要就是用在流程控制中,
146 2
|
存储 Rust 编译器
Rust 基础入门 ——数值类型
Rust 提供了一个非常简洁的方式,用来生成连续的数值,例如 1..5,生成从 1 到 4 的连续数字,不包含 5 ;1..=5,生成从 1 到 5 的连续数字,包含 5,它的用途很简单,常常用于循环中:
115 0
|
测试技术 Python
Python第2章 数值类型与数学运算(下)
Python第2章 数值类型与数学运算
1293 0
|
算法 Python Windows
Python第2章 数值类型与数学运算(上)
第2章 数值类型与数学运算
278 0

热门文章

最新文章