LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

Python为什么不解决四舍五入(round)的“bug”?

admin
2025年12月26日 0:18 本文热度 1156
使用round()这个函数:

>>> i=1.5>>> ii=round(i,0)>>> ii2.0>>> i=1.25>>> ii=round(i,1)>>> ii1.2>>> i=1.245>>> ii=round(i,2)>>> ii1.25>>> i=1.45>>> ii=round(i,1)>>> ii1.4>>> i=1.415>>> ii=round(i,2)>>> ii1.42
奇怪的是四舍五入的规则时而可以时而不行。API 中的解释是:
Note
The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.

问题是,为什么内置的库函数 round 不解决下这个问题?

用Python做四舍五入时,肯定会遇到过这种 “诡异” 情况:

round(1.25, 1) 结果不是 1.3 而是 1.2,

round(1.45, 1) 得到 1.4 而非预期的 1.5,

甚至round(18.2, -1) 直接算出20—— 明明感觉不符合 “四舍五入” 常识,这难道是 Python 的漏洞?

其实这根本不是bug!无数开发者踩坑的背后,是二进制浮点数的 “天然特性” 和round()的特殊设计逻辑。今天就从底层原理到实际用法,一次性讲清round()的所有 “门道”。

先看现象:那些让人困惑的round()结果

先一起围观几个典型案例,看看你是否也曾被这些结果劝退:

# 基础案例print(round(1.5))      # 2.0(符合直觉)print(round(1.251))  # 1.2(预期1.3,实际1.2)print(round(1.451))  # 1.4(预期1.5,实际1.4)print(round(18.2, -1)) # 20(预期18,实际20)# 更隐蔽的情况def show(x):    """打印20位精度,暴露浮点数真实面貌"""    print('{:.20f}'.format(x))show(1.245)  # 1.24500000000000010658show(1.45)   # 1.44999999999999995559show(1.415)  # 1.41500000000000003553

为什么1.25保留1位小数不是1.3?为什么1.45 的真实值不是1.45?要解答这些问题,得先从计算机存储数字的底层逻辑说起。

核心原因 1:二进制浮点数的 “天生缺陷”

我们习惯用十进制(0-9)表示数字,但计算机底层只认识二进制(0 和 1),所有十进制浮点数都会被转换成二进制后存储。而问题的关键的是:不是所有十进制小数都能被二进制精确表示

十进制与二进制的转换规则

一个十进制有理数 n/d(既约分数)能被 B 进制有限表示的条件是:d 的所有素因子都能整除 B。

  • 十进制(B=10)的素因子是 2 和 5,所以分母仅含 2、5 的分数(如 1/2=0.5、1/4=0.25、3/8=0.375)能精确表示;
  • 二进制(B=2)的素因子只有 2,所以只有分母是 2 的幂的分数(如 1/2=0.1、1/4=0.01)能精确表示。

像 0.2(1/5)、0.3(3/10)、1.45(29/20)这类分数,分母含 2 和 5 以外的素因子,转成二进制后是无限循环小数。但计算机存储容量有限,只能截取有限位数保留,这就导致了存储误差

比如 1.45 转成二进制后是无限循环序列,Python 的 float 类型(64 位浮点数,53 位尾数)会截取并存储为近似值,其十进制等效值就是 1.44999999999999995559—— 你以为在对 1.45 做四舍五入,实际是在对一个有误差的近似值操作,结果自然不符合预期。

关键结论

在调用 round () 之前,很多十进制浮点数已经因为 “十进制→二进制” 的转换产生了微小误差。round () 的 “反常” 结果,往往是这种误差的延续,而非函数本身的问题。

核心原因 2:round () 的设计逻辑 —— 不是 “四舍五入”,是 “精度对齐 + 银行家舍入”

除了存储误差,round () 的核心设计逻辑也和我们认知的 “四舍五入” 不同,这是另一个重要 “坑点”。

第一层逻辑:round () 是 “对齐到指定精度的倍数”

round () 的完整语法是round(x, n),其本质不是 “保留 n 位小数”,而是 “找到最接近 x 的 10^(-n) 的倍数”:

  • n>0:对齐到小数点后 n 位(如 n=1→0.1 的倍数、n=2→0.01 的倍数);
  • n=0:对齐到整数(1 的倍数);
  • n<0:对齐到整数的指定位数(如 n=-1→10 的倍数、n=-2→100 的倍数)。

这就能解释为什么round(18.2, -1)会得到 20:18.2 需要对齐到 10 的倍数,10 的 1 倍是 10、2 倍是 20,18.2 离 20 更近(距离 1.8),所以结果是 20 而非 10。

第二层逻辑:中点情况的 “银行家舍入”

当 x 刚好在两个目标倍数的正中间(如 1.25 在 1.2 和 1.3 中间、2.5 在 2 和 3 中间),round () 会采用 “银行家舍入(Banker’s Rounding)” 规则,而非传统的 “逢五进一”。

银行家舍入规则

当数值处于中点时,舍入到最近的偶数(最终结果的末位为偶数),目的是避免大规模计算时的系统性偏差。

看几个典型例子:

  • round(1.25, 1)→1.2(1.2 和 1.3 的中点,1.2 的末位是偶数);
  • round(1.35, 1)→1.4(1.3 和 1.4 的中点,1.4 的末位是偶数);
  • round(2.5)→ 2(2 和 3 的中点,2 是偶数);
  • round(3.5)→ 4(3 和 4 的中点,4 是偶数)。

为什么需要银行家舍入?

传统 “逢五进一” 会导致结果整体偏大:比如计算 0.5、1.5、2.5、3.5 的平均值,传统舍入结果是 1、2、3、4(平均值 2.5),而银行家舍入结果是 0、2、2、4(平均值 2.0),后者更均衡,无系统性偏差,这在金融、统计等对精度敏感的领域至关重要。

解决方案:不同场景的正确用法

了解底层逻辑后,我们可以根据场景选择合适的方式,避免踩坑。

场景 1:日常展示 / 普通报表

直接使用 round () 即可。银行家舍入在统计学上更合理,且日常场景对精度要求不高,无需额外处理。

场景 2:需要传统 “四舍五入”(如价格计算、游戏数值)

自定义函数实现 “远离 0 的四舍五入”,支持正负数值和任意小数位:

import mathdef round_half_away_from_zero(x, n=0):    factor = 10 ** n    # 符号感知的0.5,避免负数翻车    return int(x * factor + math.copysign(0.5, x)) / factor# 测试print(round_half_away_from_zero(1.251))  # 1.3(符合传统四舍五入)print(round_half_away_from_zero(-2.5))     # -3(负数正确处理)print(round_half_away_from_zero(1.451))  # 1.5(符合预期)

场景3:金融/高精度计算(如记账、转账)

使用Python的 decimal 模块,支持十进制精确运算和自定义舍入规则,彻底避免二进制存储误差:

from decimal import Decimal, ROUND_HALF_UP# 注意:用字符串传入数值,避免float的存储误差# 示例1:精确保留1位小数,传统四舍五入num1 = Decimal('1.45')result1 = num1.quantize(Decimal('.1'), rounding=ROUND_HALF_UP)print(result1)  # 1.5(完全符合预期)# 示例2:自定义精度和舍入规则num2 = Decimal('1.245')# 保留2位小数,传统四舍五入result2 = num2.quantize(Decimal('.01'), rounding=ROUND_HALF_UP)print(result2)  # 1.25(无存储误差)

场景 4:特殊取整需求


场景
推荐方法
理由
分页 / 补齐
math.ceil()
向上取整,确保余量覆盖
上限控制
math.floor()
向下取整,不超过上限
数组索引
math.trunc()/int()
向 0 取整,避免索引越界

速查表:常见 round () 结果汇总(建议收藏)


代码
结果
核心原因
round(1.5)
2.0
无存储误差,中点舍入到偶数 2
round(2.5)
2.0
中点舍入到偶数 2
round(1.25, 1)
1.2
中点舍入到偶数 1.2
round(1.35, 1)
1.4
中点舍入到偶数 1.4
round(1.45, 1)
1.4
1.45 实际存储为 1.449...,无中点情况
round(18.2, -1)
20
对齐到 10 的倍数,离 20 更近
round(1234, -2)
1200
对齐到 100 的倍数




Python 的 round () 从来不是 bug,而是 “二进制浮点数存储特性” 和 “银行家舍入设计” 共同作用的结果:

  1. 误差根源:十进制到二进制的转换无法避免存储误差,部分浮点数本身就是近似值;
  2. 设计逻辑:round () 是 “精度对齐”+“银行家舍入”,优先保证统计公平性;
  3. 解决方案:日常用 round (),传统四舍五入用自定义函数,高精度场景用 decimal 模块。

掌握这些原理后,你不仅能轻松应对 round () 的各种 “反常” 结果,还能根据实际场景选择最合适的取整方式 —— 这才是真正理解了 Python 的设计思想,而非单纯记用法~

阅读原文:https://mp.weixin.qq.com/s/udtd6b3WcUsXXepvEcCw1g


该文章在 2025/12/26 12:07:18 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved