Python基础指南之浮点数float精度问题及解决方案
一、开篇:一个令人困惑的计算
在上一篇文章中,我们学习了Python的整数int——精确、无限、完美。今天要讲的浮点数float,就没那么"完美"了。
先看一个经典的例子:
>>> 0.1 + 0.2 0.30000000000000004
等待——0.1加0.2不是应该等于0.3吗?那个尾部的4是怎么回事?是Python算错了吗?
答案是:不是Python的错,这是所有计算机的通病。这篇文章,我就带你搞清楚浮点数的来龙去脉,包括为什么会出现精度问题、如何在开发中正确处理浮点数。
二、浮点数的基本表示
2.1 小数表示
# 基本的浮点数写法 a = 3.14 b = -0.5 c = 2.0 d = .5 # 0.5的简写(可以省略小数点前的0) e = 5. # 5.0的简写 # 科学计数法 f = 1.5e6 # 1.5 × 10⁶ = 1500000.0 g = 2.5e-3 # 2.5 × 10⁻³ = 0.0025 h = 6.02e23 # 阿伏伽德罗常数 # 下划线分隔(Python 3.6+) pi = 3.141_592_653_589_793 budget = 1_000_000.50
2.2 类型确认
x = 3.14 print(type(x)) # <class 'float'> # 结果是浮点数的运算 print(type(1 / 2)) # <class 'float'> print(type(3.0 + 2)) # <class 'float'> print(type(2 ** 0.5))# <class 'float'>
Python中,任何包含小数点的数字字面量都是float类型。除法运算 / 的结果也始终是float,即使结果恰好是整数(如 4 / 2 结果是 2.0 而不是 2)。
2.3 浮点数的范围
import sys
# Python浮点数的范围(通常基于IEEE 754双精度)
print(f'浮点数最大值:{sys.float_info.max:.10e}') # ~1.8e308
print(f'浮点数最小值(正数):{sys.float_info.min:.10e}') # ~2.2e-308
print(f'浮点数精度(小数位数):{sys.float_info.dig}') # 15位十进制精度
Python的float基于IEEE 754双精度浮点数标准(64位),提供大约15-17位有效十进制数字的精度。
三、浮点数精度问题的源头
3.1 二进制不能精确表示某些十进制小数
问题的根源在于:计算机使用二进制存储数字,而很多十进制小数在二进制中是无限循环小数。
想一想:在十进制中,1/3 = 0.3333…是一个无限循环小数,我们只能取近似值。同样的道理,在二进制中,0.1(十进制)是一个无限循环小数:
十进制的 0.1 = 二进制的 0.0001100110011001100110011...
(循环节 0011 无限重复)
计算机只能存储64位(大约相当于二进制53位有效数字),所以它必须截断。截断后的值并不精确等于0.1,只是非常非常接近0.1。
# 验证0.1在内存中的实际值 import decimal # 用高精度decimal显示0.1的实际值 print(decimal.Decimal(0.1)) # 输出:0.1000000000000000055511151231257827021181583404541015625 # 这个值不是0.1!但非常接近
3.2 更多让人惊讶的例子
>>> 0.1 + 0.2 0.30000000000000004 >>> 0.1 + 0.1 + 0.1 0.30000000000000004 >>> 0.1 * 3 0.30000000000000004 >>> 0.3 == 0.1 + 0.2 False # 这让人最惊讶! >>> 1.0 / 3.0 0.3333333333333333 >>> 0.1 * 0.1 0.010000000000000002
这些不是Python独有的问题。JavaScript、Java、C++、Go等所有使用IEEE 754浮点数的语言,都会出现完全相同的情况。
3.3 哪些小数可以精确表示
能被精确表示的二进制小数,它的分母必须是2的幂次方:
# 能精确表示的 0.5 = 1/2 ✅ 0.25 = 1/4 ✅ 0.125 = 1/8 ✅ 0.75 = 3/4 ✅ 0.375 = 3/8 ✅ # 不能精确表示的 0.1 ❌(分母有因子5) 0.2 ❌(分母有因子5) 0.3 ❌(分母有因子5) 0.01 ❌
# 验证 print(0.5 + 0.25 == 0.75) # True print(0.1 + 0.2 == 0.3) # False!
四、浮点数的运算
4.1 基本运算
a, b = 10.5, 3.0 print(a + b) # 13.5 print(a - b) # 7.5 print(a * b) # 31.5 print(a / b) # 3.5 print(a // b) # 3.0(浮点数的整除结果也是浮点数) print(a % b) # 1.5 print(a ** b) # 1157.625(10.5的3次方)
4.2 浮点数的整除和取余
# 浮点数的整除也是向下取整 print(7.8 // 2.5) # 3.0(因为2.5*3=7.5,2.5*4=10.0 > 7.8) print(7.8 % 2.5) # 0.3000000000000007(7.8 - 2.5*3) print(-7.8 // 2.5) # -4.0(向下取整) print(-7.8 % 2.5) # 2.2(-7.8 - 2.5*(-4) = -7.8 + 10.0 = 2.2)
4.3 浮点数比较
# ❌ 直接比较相等——可能得到错误结果
if 0.1 + 0.2 == 0.3:
print('相等')
else:
print('不相等') # 会输出这个!
# ✅ 使用容差比较(epsilon比较)
def is_close(a, b, rel_tol=1e-9, abs_tol=1e-12):
"""判断两个浮点数是否"接近"(即视为相等)"""
return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
print(is_close(0.1 + 0.2, 0.3)) # True
# ✅ 使用math.isclose(Python 3.5+)
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
print(math.isclose(0.1 + 0.2, 0.3000000001)) # False
五、解决浮点数精度问题的四种方案
5.1 方案一:使用math.isclose()
import math
# 判断两个浮点数是否"几乎相等"
a = 0.1 + 0.2
b = 0.3
if math.isclose(a, b, rel_tol=1e-9):
print('a和b在误差范围内相等')
# 参数说明
# rel_tol: 相对容差(relative tolerance),默认1e-9
# abs_tol: 绝对容差(absolute tolerance),默认0.0
5.2 方案二:使用Decimal(精确小数)
from decimal import Decimal
# 注意:要从字符串创建Decimal,而不是从浮点数!
# ❌ 错误做法
print(Decimal(0.1))
# 0.1000000000000000055511151231257827021181583404541015625
# ✅ 正确做法
print(Decimal('0.1'))
# 0.1
# Decimal支持精确运算
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b
print(c) # 0.3
print(c == Decimal('0.3')) # True
# 设置精度
from decimal import getcontext
getcontext().prec = 50 # 设置50位有效数字
print(Decimal('1') / Decimal('7'))
# 0.14285714285714285714285714285714285714285714285714
Decimal适合需要精确计算的场景:金融、会计、科学计算等。但它的缺点是运算速度比float慢很多。
5.3 方案三:使用分数(Fraction)
from fractions import Fraction
# 创建分数
a = Fraction(1, 3) # 1/3
b = Fraction(2, 6) # 2/6,自动约分为1/3
c = Fraction('0.25') # 从字符串创建:1/4
d = Fraction('3.14') # 3.14 = 157/50
# 分数运算——始终精确
print(a + b) # 2/3
print(a * 3) # 1
print(a == b) # True
# 混合运算
print(Fraction('0.1') + Fraction('0.2')) # 3/10
print(float(Fraction('0.1') + Fraction('0.2'))) # 0.3
# 分数转小数
x = Fraction(1, 7)
print(float(x)) # 0.14285714285714285
Fraction适合有理数运算,但在需要大量运算时性能较差。
5.4 方案四:用整数代替小数
# 处理金额时,用"分"作为单位而不是"元"
# 0.1元 = 10分,0.2元 = 20分,0.3元 = 30分
# ❌ 用浮点数(可能出错)
price = 0.1
tax = 0.2
total = price + tax
print(total) # 0.30000000000000004
# ✅ 用整数(精确)
price_cents = 10 # 10分 = 0.1元
tax_cents = 20 # 20分 = 0.2元
total_cents = price_cents + tax_cents # 30分 = 0.3元
print(f'{total_cents / 100:.2f}元') # 0.30元
金融系统中普遍使用这个方案——所有金额用最小单位(分、厘)的整数存储,只在最终展示时转换为元。
六、浮点数的特殊值
6.1 无限大和NaN
# 正无穷大
pos_inf = float('inf')
print(pos_inf) # inf
print(pos_inf > 10**100) # True
print(math.isinf(pos_inf)) # True
# 负无穷大
neg_inf = float('-inf')
print(neg_inf) # -inf
# NaN(Not a Number)
nan = float('nan')
print(nan) # nan
print(math.isnan(nan)) # True
# NaN的"奇怪"行为
print(nan == nan) # False!(NaN不等于任何东西,包括它自己)
print(nan > 0) # False
print(nan < 0) # False
6.2 产生特殊值的运算
# 产生无穷大
print(1.0 / 0.0) # ZeroDivisionError(Python会报错)
# print(float('inf')) # 创建无穷大的正确方式
# 产生NaN
import math
print(math.sqrt(-1.0)) # ValueError
# 需要使用cmath进行复数运算
import cmath
print(cmath.sqrt(-1)) # 1j
# 无穷大的运算
inf = float('inf')
print(inf + 1) # inf
print(inf - inf) # nan
print(inf * 0) # nan
print(1.0 / inf) # 0.0
6.3 处理特殊值的工具函数
import math
def describe_float(x):
"""描述一个浮点数的状态"""
if math.isnan(x):
return 'NaN(非数字)'
elif math.isinf(x):
if x > 0:
return '正无穷大'
else:
return '负无穷大'
elif x == 0.0:
return '零'
else:
return f'普通浮点数:{x}'
print(describe_float(3.14)) # 普通浮点数:3.14
print(describe_float(float('nan')))# NaN(非数字)
print(describe_float(float('inf')))# 正无穷大
print(describe_float(0.0)) # 零
七、浮点数格式化输出
7.1 控制小数位数
pi = 3.141592653589793
# f-string格式化
print(f'{pi:.2f}') # 3.14(保留2位小数)
print(f'{pi:.4f}') # 3.1416(保留4位小数,自动四舍五入)
print(f'{pi:.0f}') # 3(取整)
print(f'{pi:10.3f}') # ' 3.142'(总宽度10,右对齐)
print(f'{pi:010.3f}') # '000003.142'(总宽度10,前面补零)
# format()函数
print('{:.2f}'.format(pi)) # 3.14
print('{:+.2f}'.format(pi)) # +3.14(显示正号)
print('{:+.2f}'.format(-pi)) # -3.14
# 百分比
rate = 0.8567
print(f'{rate:.1%}') # 85.7%
print(f'{rate:.2%}') # 85.67%
# 科学计数法
num = 123456789.0
print(f'{num:.2e}') # 1.23e+08
print(f'{num:.4E}') # 1.2346E+08
# 千分位分隔
print(f'{num:,.2f}') # 123,456,789.00
7.2 常用的格式化速查
value = 12345.6789
# 各种格式化方式
print(f'默认:{value}')
print(f'2位小数:{value:.2f}')
print(f'科学计数:{value:.2e}')
print(f'百分比:{value:.2%}') # 注意:会乘以100
print(f'千分位:{value:,.2f}')
print(f'右对齐10宽:{value:>10.1f}')
print(f'左对齐10宽:{value:<10.1f}')
print(f'居中对齐10宽:{value:^10.1f}')
八、实际开发中的最佳实践
8.1 金融计算用Decimal
from decimal import Decimal, ROUND_HALF_UP
def calculate_order_total(unit_price, quantity, tax_rate):
"""计算订单总价(精确到分)"""
unit_price = Decimal(str(unit_price))
quantity = Decimal(str(quantity))
tax_rate = Decimal(str(tax_rate))
subtotal = unit_price * quantity
tax = (subtotal * tax_rate).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
total = (subtotal + tax).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
return float(subtotal), float(tax), float(total)
subtotal, tax, total = calculate_order_total(9.99, 3, 0.06)
print(f'小计: ¥{subtotal:.2f}')
print(f'税费: ¥{tax:.2f}')
print(f'总计: ¥{total:.2f}')
8.2 科学计算使用float但注意容差
import math
def safe_float_equals(a, b):
"""安全的浮点数相等判断"""
return math.isclose(a, b, rel_tol=1e-9, abs_tol=1e-12)
# 不应该这样
# assert my_calculation() == 0.3
# 应该这样
assert math.isclose(my_calculation(), 0.3)
8.3 大数据量的金额用整数
class Money:
"""用整数表示金额(分为单位)"""
def __init__(self, amount_cents):
self.cents = amount_cents
@classmethod
def from_yuan(cls, yuan):
return cls(round(yuan * 100))
def to_yuan(self):
return self.cents / 100
def __add__(self, other):
return Money(self.cents + other.cents)
def __str__(self):
return f'¥{self.cents / 100:.2f}'
# 使用
price = Money.from_yuan(99.99)
tax = Money.from_yuan(5.99)
total = price + tax
print(total) # ¥105.98
九、本篇小结
浮点数精度的核心认知:
- 不是Python的bug:所有使用IEEE 754标准的语言都一样
- 根源是二进制:很多十进制小数在二进制中是无限循环的
- 永远不要直接比较浮点数相等:用
math.isclose()或容差比较 - 金融计算用Decimal:
Decimal('0.1')而不是Decimal(0.1) - 金额可以用整数:以"分"为单位存储,避免浮点数
- 了解特殊值:inf、-inf、NaN及其行为特点
浮点数精度不是要你"害怕"使用float,而是要你"正确地"使用它。科学计算、图形渲染、机器学习中的大量运算,float完全够用。但涉及"钱"的计算,请用Decimal或整数方案。下一篇我们来看一个较少用到但有时很实用的类型——复数complex。
以上就是Python基础指南之浮点数float精度问题及解决方案的详细内容,更多关于Python浮点数float精度问题的资料请关注脚本之家其它相关文章!
相关文章
python读取查看npz/npy文件数据以及数据完全显示方法实例
前两天从在GitHub下载了一个代码,其中的数据集是.npz结尾的文件,之前没有见过不知道如何处理,下面这篇文章主要给大家介绍了关于python读取查看npz/npy文件数据以及数据完全显示方法的相关资料,需要的朋友可以参考下2022-04-04


最新评论