Python中0.1 + 0.2 != 0.3?

程序中常用到浮点类型,而在对浮点类型中使用到相等比较式会出现如标题0.1 + 0.2 != 0.3的困惑。

我们在Python交互式编码环境,得出0.1 + 0.2结果如下:

python 复制代码
>>> 0.1 + 0.2
0.30000000000000004

What the hell is that?

随着我们深入探究,变可知为何输出如上结果。

IEEE 754

Python中浮点类型遵循 IEEE754标准, 它是最广泛使用的浮点数运算标准,被许多CPU和浮点运算器采用。

IEEE754提供了四种浮点数值的方式:单精度(32bit),双精度(64bit),扩展单精度(43bit以上)与扩展双进度(79bit以上,通常以80bit实现)

Python实际实现的是双进度(64bit)类型。

浮点数表示

符号位(S) 阶码(E) 尾数(M)
sign exponent mantissa

浮点数在内存中二进制表示分三个不分:符号位、阶码(经过换算的指数),以及尾数。它的值就等于:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> ( − 1 ) s ∗ 1. M ∗ 2 E − o f f s e t (-1)^s * 1.M * 2^{E-offset} </math>(−1)s∗1.M∗2E−offset

浮点值得符号由符号位决定:1为负值,0为正值。offset为阶码偏移值。

二进制构成

我们来看看float32和float64在阶码和尾数上的不同。

浮点数类型 符号位(bit位数) 阶码(bit位数) 阶码偏移值 尾数(bit位数)
单精度(float32) 1 8 127 23
双精度(float64) 1 11 1023 52

这里float64符号位长度和float32一致,其余两个部分的长度远大于float32。

阶码偏移值,我们来看看十进制形式浮点值139.8125如何转换为IEEE754规定中的单精度二进制表示。

1、将整数部分和小数部分分别转换为二进制形式

  • 整数部分:139d => 10001011b
  • 小数部分:0.8125d => 0.1101b

十进制小数转换为二进制可采用乘2取整的竖式计算,过程如下:

0.8125 * 2 = 1.625 (1) 0.625 * 2 = 1.25 (1) 0.25 * 2 = 0.5 (0) 0.5 * 2 = 1.0 (1)

即1101b

2、移动小数点,直到整数部分仅有一个1,

10001011.1101b => 1.00010111101b,为了让整数仅保留一个1,小数点向左移动了7位,这样指数就为7,尾数为00010111101b。

3、计算阶码

IEEE754规定不能将小数点得到的指数,直接填到阶码部分,指数到阶码还需要一个转换过程,阶码= 指数 + 偏移值,偏移值 = 2**(e -1) -1,e为阶码部分的bit位数,float32为8,float6为11,这样以float32为例, 偏移值 为 127,阶码= 7+ 127 = 134d = 10000110b。

4、将符号位、阶码和尾数填到各自位置,就得到了最终浮点数的二进制表示,尾数不足23时后面补0:

符号位 阶码 尾数
0 1000110 00010111101(000000000000)

最终我们得到139.8125b的二进制表示为 0b_0_10000110_00010111101_000000000000

如果float64中各自位置为:

符号位 阶码 尾数
0 10000000110 00010111101(00000000000000000000000000000000000000000)

实际在Python浮点数二进制形式为: 0b_0_10000000110_0001011110100000000000000000000000000000000000000000

实际使用

因为浮点数的特殊性,咱们就不能直接用==来对浮点数进行匹配 ​

使用容忍误差比较

python 复制代码
def is_close(a, b, rel_tol=1e-9, abs_tol=0.0):
    return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

is_close(0.1 + 0.2, 0.3)  # True

decimal 模块(高精度十进制计算)

适用于金融等需要精确十进制的场景:

python 复制代码
from decimal import Decimal, getcontext

# 使用字符串初始化避免初始误差
a = Decimal('0.1')
b = Decimal('0.2')
print(a + b)  # 精确输出0.3

# 调整全局精度
getcontext().prec = 128  # 设置128位精度

​​ math.isclose / numpy.isclose

Python 3.5+ 内置或 NumPy 的近似比较:

python 复制代码
import math
math.isclose(0.1 + 0.2, 0.3)  # True

import numpy as np
np.isclose(0.1 + 0.2, 0.3)    # True
相关推荐
努力的小雨34 分钟前
从“Agent 元年”到 AI IDE 元年——2025 我与 Vibe Coding 的那些事儿
后端·程序员
源码获取_wx:Fegn08951 小时前
基于springboot + vue小区人脸识别门禁系统
java·开发语言·vue.js·spring boot·后端·spring
wuxuanok1 小时前
Go——Swagger API文档访问500
开发语言·后端·golang
用户21411832636022 小时前
白嫖Google Antigravity!Claude Opus 4.5免费用,告别token焦虑
后端
爬山算法2 小时前
Hibernate(15)Hibernate中如何定义一个实体的主键?
java·后端·hibernate
用户26851612107563 小时前
常见的 Git 分支命名策略和实践
后端
程序员小假3 小时前
我们来说一下 MySQL 的慢查询日志
java·后端
南囝coding3 小时前
《独立开发者精选工具》第 025 期
前端·后端
To Be Clean Coder4 小时前
【Spring源码】从源码倒看Spring用法(二)
java·后端·spring