Python 的 float 本质是:
IEEE 754 双精度浮点数(64 位)
它不是“真实小数”,而是:
符号位 + 指数位 + 尾数位
因此很多十进制数字根本无法被精确表示。
大多数人知道:
0.1 + 0.2 != 0.3
但真正危险的,其实是一些“边缘场景”的奇异行为。
这些问题:
不容易复现
不一定报错
看起来像 Python Bug
实际是浮点底层设计导致
1. 0.1 + 0.2 只是最基础的问题
经典例子:
0.1+0.2
=0.3
print(0.1 + 0.2)
输出:
0.30000000000000004
因为:
0.1
0.2
0.3
都无法被二进制有限表示。
真正存储的是:
0.10000000000000000555...
之类的近似值。
2. 相同公式,不同顺序,结果不同
浮点加法:
不满足严格结合律。
例如:
a = 1e16
b = -1e16
c = 1.0
print((a + b) + c)
print(a + (b + c))
输出:
1.0
0.0
原因:
b + c
中的 c 太小。
在:
-10000000000000000 + 1
时被“吞掉”。
这叫:
灾难性消除(Catastrophic Cancellation)
3. 大数吃小数
例如:
print(1e20 + 1)
输出:
1e20
那个 1 消失了。
因为:
双精度浮点只有:
53 位有效精度
当数值太大时:
小增量无法表示
直接被舍弃
4. 无限接近但永远不等于
例如:
x = 0.0
while x != 1.0:
x += 0.1
可能永远无法结束。
因为:
x
实际变成:
0.9999999999999999
1.0999999999999999
之类。
正确做法
使用:
import math
math.isclose(x, 1.0)
5. round() 的银行家舍入
很多人认为:
round(2.5)
应该:
3
但结果:
2
因为 Python 使用:
Banker's Rounding
即:
“四舍六入五成双”
例如:
print(round(1.5)) # 2
print(round(2.5)) # 2
print(round(3.5)) # 4
这是为了:
降低统计偏差
避免长期累计误差
6. round() 看起来错误
例如:
print(round(2.675, 2))
结果:
2.67
很多人以为 Python Bug。
实际上:
2.675
真实存储接近:
2.674999999999...
因此被舍入成:
2.67
7. NaN 最诡异
IEEE754 规定:
nan != nan
例如:
x = float('nan')
print(x == x)
输出:
False
甚至:
x < 1
x > 1
x == 1
全部:
False
正确检测
不要:
x == float('nan')
而要:
math.isnan(x)
8. inf - inf
例如:
float('inf') - float('inf')
结果:
nan
因为:
数学上:
∞ - ∞
未定义。
9. 浮点排序异常
例如:
arr = [1.0, float('nan'), 2.0]
print(sorted(arr))
结果可能不稳定。
因为:
nan
无法比较。
某些排序算法:
不报错
但顺序怪异
10. hash 行为边缘问题
Python 保证:
1 == 1.0
因此:
hash(1) == hash(1.0)
结果:
True
所以:
d = {1: "a"}
d[1.0]
会得到:
"a"
因为它们被视为同一个 key。
11. -0.0 的存在
IEEE754 有:
+0.0
-0.0
例如:
print(-0.0 == 0.0)
结果:
True
但:
import math
print(math.copysign(1, -0.0))
输出:
-1.0
说明:
Python 仍保留符号位。
12. Decimal 与 float 混合
例如:
from decimal import Decimal
print(Decimal("0.1") + 0.2)
直接报错:
TypeError
因为:
Python 不允许:
Decimal + float
混算。
这是故意设计的。
避免隐式精度污染。
13. JSON 精度丢失
例如:
import json
x = 0.1234567890123456789
print(json.dumps(x))
可能变成:
0.12345678901234568
因为:
float 精度已经损失。
在:
金融
区块链
高精度计算
特别危险。
14. 浮点累积误差
例如:
x = 0
for _ in range(1000000):
x += 0.1
理论:
100000
实际可能:
100000.00000133288
因为:
每一步都有微小误差。
更稳定方案
Python 提供:
math.fsum()
例如:
import math
arr = [0.1] * 1000000
print(sum(arr))
print(math.fsum(arr))
fsum 精度更高。
15. 浮点在不同 CPU 上可能不同
同样代码:
0.1 + 0.2
在:
x86
ARM
NumPy SIMD
GPU
结果最后几位可能不同。
因为:
FPU 实现
扩展精度
SIMD 优化
编译器策略
不完全一致。
金融场景为什么禁用 float
银行系统通常:
不允许 float 存钱
不允许 IEEE754 运算
因为:
0.01
都无法精确表示。
通常使用:
Decimal
整数分单位
定点数
最安全实践
金额
使用:
from decimal import Decimal
不要:
float
浮点比较
使用:
math.isclose(a, b)
不要:
a == b
大量求和
使用:
math.fsum()
序列化
不要直接信任:
json.dumps(float)
本质原因
浮点问题并不是 Python 独有。
而是:
“二进制无法精确表达大多数十进制小数。”
例如:
0.1
10
=0.00011001100110011…
2
这是无限循环二进制。
因此:
精度损失
舍入误差
比较异常
NaN 行为
顺序依赖
都会出现。