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
相关推荐
程序员小奕13 分钟前
Springboot 高校报修与互助平台小程序
spring boot·后端·小程序
有梦想的攻城狮1 小时前
spring中的ImportSelector接口详解
java·后端·spring·接口·importselector
晨曦~~1 小时前
SpringCloudAlibaba和SpringBoot版本问题
java·spring boot·后端
bobz9651 小时前
go doc 使用
后端
小华同学ai2 小时前
真香,Cursor懂的都懂(学习用哈),22.5k一键重置Cursor试用限制!被全网疯狂收藏!
前端·后端·github
Android洋芋2 小时前
Steam++开发逻辑详解:从零到一的实战指南
后端·github
AI转型之路2 小时前
手把手带你实现Dify集成Ollama(Windows环境本地部署)
后端
树獭叔叔2 小时前
从零开始Node之旅——Nest.js 模块系统(Modules)
后端·node.js
RunsenLIu2 小时前
基于Flask前后端分离智慧安防小区系统
后端·python·flask