当前位置: 首页 > news >正文

Python基础:浮点数float精度问题与解决方案

Python基础:浮点数float精度问题与解决方案

一、开篇:一个令人困惑的计算

在上一篇文章中,我们学习了Python的整数int——精确、无限、完美。今天要讲的浮点数float,就没那么"完美"了。

先看一个经典的例子:

>>>0.1+0.20.30000000000000004

⌨️ 等待——0.1加0.2不是应该等于0.3吗?那个尾部的4是怎么回事?是Python算错了吗?

答案是:不是Python的错,这是所有计算机的通病。这篇文章,我就带你搞清楚浮点数的来龙去脉,包括为什么会出现精度问题、如何在开发中正确处理浮点数。

二、浮点数的基本表示

2.1 小数表示

# 基本的浮点数写法a=3.14b=-0.5c=2.0d=.5# 0.5的简写(可以省略小数点前的0)e=5.# 5.0的简写# 科学计数法f=1.5e6# 1.5 × 10⁶ = 1500000.0g=2.5e-3# 2.5 × 10⁻³ = 0.0025h=6.02e23# 阿伏伽德罗常数# 下划线分隔(Python 3.6+)pi=3.141_592_653_589_793budget=1_000_000.50

2.2 类型确认

x=3.14print(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 浮点数的范围

importsys# Python浮点数的范围(通常基于IEEE 754双精度)print(f'浮点数最大值:{sys.float_info.max:.10e}')# ~1.8e308print(f'浮点数最小值(正数):{sys.float_info.min:.10e}')# ~2.2e-308print(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在内存中的实际值importdecimal# 用高精度decimal显示0.1的实际值print(decimal.Decimal(0.1))# 输出:0.1000000000000000055511151231257827021181583404541015625# 这个值不是0.1!但非常接近

3.2 更多让人惊讶的例子

>>>0.1+0.20.30000000000000004>>>0.1+0.1+0.10.30000000000000004>>>0.1*30.30000000000000004>>>0.3==0.1+0.2False# 这让人最惊讶!>>>1.0/3.00.3333333333333333>>>0.1*0.10.010000000000000002

⚠️ 这些不是Python独有的问题。JavaScript、Java、C++、Go等所有使用IEEE 754浮点数的语言,都会出现完全相同的情况。

3.3 哪些小数可以精确表示

能被精确表示的二进制小数,它的分母必须是2的幂次方:

# 能精确表示的0.5=1/20.25=1/40.125=1/80.75=3/40.375=3/8# 不能精确表示的0.1❌(分母有因子50.2❌(分母有因子50.3❌(分母有因子50.01
# 验证print(0.5+0.25==0.75)# Trueprint(0.1+0.2==0.3)# False!

四、浮点数的运算

4.1 基本运算

a,b=10.5,3.0print(a+b)# 13.5print(a-b)# 7.5print(a*b)# 31.5print(a/b)# 3.5print(a//b)# 3.0(浮点数的整除结果也是浮点数)print(a%b)# 1.5print(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 浮点数比较

# ❌ 直接比较相等——可能得到错误结果if0.1+0.2==0.3:print('相等')else:print('不相等')# 会输出这个!# ✅ 使用容差比较(epsilon比较)defis_close(a,b,rel_tol=1e-9,abs_tol=1e-12):"""判断两个浮点数是否"接近"(即视为相等)"""returnabs(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+)importmathprint(math.isclose(0.1+0.2,0.3))# Trueprint(math.isclose(0.1+0.2,0.3000000001))# False

五、解决浮点数精度问题的四种方案

5.1 方案一:使用math.isclose()

importmath# 判断两个浮点数是否"几乎相等"a=0.1+0.2b=0.3ifmath.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(精确小数)

fromdecimalimportDecimal# 注意:要从字符串创建Decimal,而不是从浮点数!# ❌ 错误做法print(Decimal(0.1))# 0.1000000000000000055511151231257827021181583404541015625# ✅ 正确做法print(Decimal('0.1'))# 0.1# Decimal支持精确运算a=Decimal('0.1')b=Decimal('0.2')c=a+bprint(c)# 0.3print(c==Decimal('0.3'))# True# 设置精度fromdecimalimportgetcontext getcontext().prec=50# 设置50位有效数字print(Decimal('1')/Decimal('7'))# 0.14285714285714285714285714285714285714285714285714

Decimal适合需要精确计算的场景:金融、会计、科学计算等。但它的缺点是运算速度比float慢很多。

5.3 方案三:使用分数(Fraction)

fromfractionsimportFraction# 创建分数a=Fraction(1,3)# 1/3b=Fraction(2,6)# 2/6,自动约分为1/3c=Fraction('0.25')# 从字符串创建:1/4d=Fraction('3.14')# 3.14 = 157/50# 分数运算——始终精确print(a+b)# 2/3print(a*3)# 1print(a==b)# True# 混合运算print(Fraction('0.1')+Fraction('0.2'))# 3/10print(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.1tax=0.2total=price+taxprint(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)# infprint(pos_inf>10**100)# Trueprint(math.isinf(pos_inf))# True# 负无穷大neg_inf=float('-inf')print(neg_inf)# -inf# NaN(Not a Number)nan=float('nan')print(nan)# nanprint(math.isnan(nan))# True# NaN的"奇怪"行为print(nan==nan)# False!(NaN不等于任何东西,包括它自己)print(nan>0)# Falseprint(nan<0)# False

6.2 产生特殊值的运算

# 产生无穷大print(1.0/0.0)# ZeroDivisionError(Python会报错)# print(float('inf')) # 创建无穷大的正确方式# 产生NaNimportmathprint(math.sqrt(-1.0))# ValueError# 需要使用cmath进行复数运算importcmathprint(cmath.sqrt(-1))# 1j# 无穷大的运算inf=float('inf')print(inf+1)# infprint(inf-inf)# nanprint(inf*0)# nanprint(1.0/inf)# 0.0

6.3 处理特殊值的工具函数

importmathdefdescribe_float(x):"""描述一个浮点数的状态"""ifmath.isnan(x):return'NaN(非数字)'elifmath.isinf(x):ifx>0:return'正无穷大'else:return'负无穷大'elifx==0.0:return'零'else:returnf'普通浮点数:{x}'print(describe_float(3.14))# 普通浮点数:3.14print(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.14print('{:+.2f}'.format(pi))# +3.14(显示正号)print('{:+.2f}'.format(-pi))# -3.14# 百分比rate=0.8567print(f'{rate:.1%}')# 85.7%print(f'{rate:.2%}')# 85.67%# 科学计数法num=123456789.0print(f'{num:.2e}')# 1.23e+08print(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%}')# 注意:会乘以100print(f'千分位:{value:,.2f}')print(f'右对齐10宽:{value:>10.1f}')print(f'左对齐10宽:{value:<10.1f}')print(f'居中对齐10宽:{value:^10.1f}')

八、实际开发中的最佳实践

8.1 金融计算用Decimal

fromdecimalimportDecimal,ROUND_HALF_UPdefcalculate_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)returnfloat(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但注意容差

importmathdefsafe_float_equals(a,b):"""安全的浮点数相等判断"""returnmath.isclose(a,b,rel_tol=1e-9,abs_tol=1e-12)# 不应该这样# assert my_calculation() == 0.3# 应该这样assertmath.isclose(my_calculation(),0.3)

8.3 大数据量的金额用整数

classMoney:"""用整数表示金额(分为单位)"""def__init__(self,amount_cents):self.cents=amount_cents@classmethoddeffrom_yuan(cls,yuan):returncls(round(yuan*100))defto_yuan(self):returnself.cents/100def__add__(self,other):returnMoney(self.cents+other.cents)def__str__(self):returnf'¥{self.cents/100:.2f}'# 使用price=Money.from_yuan(99.99)tax=Money.from_yuan(5.99)total=price+taxprint(total)# ¥105.98

九、本篇小结

✅ 浮点数精度的核心认知:

  1. 不是Python的bug:所有使用IEEE 754标准的语言都一样
  2. 根源是二进制:很多十进制小数在二进制中是无限循环的
  3. 永远不要直接比较浮点数相等:用math.isclose()或容差比较
  4. 金融计算用DecimalDecimal('0.1')而不是Decimal(0.1)
  5. 金额可以用整数:以"分"为单位存储,避免浮点数
  6. 了解特殊值:inf、-inf、NaN及其行为特点

💡 浮点数精度不是要你"害怕"使用float,而是要你"正确地"使用它。科学计算、图形渲染、机器学习中的大量运算,float完全够用。但涉及"钱"的计算,请用Decimal或整数方案。下一篇我们来看一个较少用到但有时很实用的类型——复数complex。

http://www.jsqmd.com/news/964310/

相关文章:

  • MIFARE Classic Tool终极指南:如何用Android手机轻松管理你的NFC门禁卡
  • 高考结束换新机!准大学生全价位手机推荐,准考证购机立省上千|2026 升学购机攻略 - 资讯速览
  • 免费分享一款站长 SEO 关键词工具:AI关键词生成器 Pro
  • 从零理解GraphSAGE:用PyTorch手把手实现一个社交网络节点分类模型
  • WPF Halcon实战:用HSmartWindowControl和HDrawingObject搞定可交互ROI(附完整源码)
  • 告别BigDecimal的繁琐!用Hutool的NumberUtil搞定商业计算(含保留小数、格式化实战)
  • 终极macOS光标定制指南:用Mousecape打造个性化桌面体验
  • 2026天水市权威认证贵金属回收 TOP5+黄金回收白银回收铂金回收门店地址电话推荐
  • LeetCode 337:打家劫舍 III(House Robber III)—— 题解 ✅
  • 解锁华硕笔记本隐藏潜能:G-Helper轻量控制工具深度体验指南
  • 别再傻傻分不清!一张图看懂SATA、M.2、NVMe硬盘怎么选(附避坑指南)
  • Python基础:字符串索引与切片操作完全指南
  • 模板驱动型文档自动化:结构化内容复用与三层架构解析
  • 政府购买服务目录中信息化项目分类与政府采购服务相关问题研究报告
  • 模拟灰度传感器原理与实战:从循迹小车到简易颜色识别
  • AD6.9授权冲突解决:局域网多机唯一序列号配置指南
  • LED路灯技术解析:从光效、散热到智能控制,全面对比高压钠灯
  • CSDN创作者必看:AI营销卡片关闭权限已灰度开放!仅限开通「专业认证」且近30天原创率>85%的账号(附自查清单)
  • 车联网多车协同通信调度代码集:含MADDPG与MADQN完整实现及仿真环境
  • 昇腾CANN集群通信库hcomm:多机分布式训练的NCCL兼容通信方案
  • Kubernetes 中 4 种容器设计模式
  • 苏州天脉:从手机散热到AI新领域,330倍估值能否靠苹果与新业务支撑?
  • 【限时可复刻】CSDN AI+内容裂变+线索评分三步法:让咨询量暴涨210%的招生闭环(附配置参数表)
  • 从开发到部署:在快马平台上构建一个可投入实战的完整winhance应用
  • RTX5消息队列创建踩坑实录:从osMessageQueueNew参数配置到Keil调试视图全解析
  • 2026年拉杆铝箱/抽屉式航空箱/储能便携拉杆箱厂家推荐:多功能与防震防护实力品牌精选 - 品牌企业推荐师(官方)
  • 从兼职工程师到行业认知:电源设计、3C认证与MCU选型的实战教训
  • 【CSDN AI数字营销实战指南】:开通后创作次数是否真有限制?3大隐藏规则99%用户不知道
  • 2026天河区搬家公司全解析|高端定制、日式精搬、正规品牌避坑指南 - gzdjxd
  • 从零构建51单片机最小系统:原理、设计与调试全攻略