找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

5225

积分

1

好友

720

主题
发表于 2 小时前 | 查看: 4| 回复: 0

「四舍五入」是小学数学就学过的知识,也是日常计算中的常见需求。然而在 Python 编程中,实现真正符合直觉的四舍五入竟出人意料地复杂,网络上还充斥着各种不准确的教程。

假设有一个变量 a = 1.135,需要将其四舍五入保留两位小数,通常能找到两种方法:

第一种,使用 round 函数。第一个参数是原数字,第二个参数是要保留的小数位数。

round(a, 2)

运行结果是 1.14,看起来没问题。

第二种,通过格式说明符 .f 进行字符串格式化,在 f 前加上要保留的小数位数。这种方法在 % 格式化、format 方法和 f-string 中均适用。

print('%.2f' % a, '{:.2f}'.format(a), f'{a:.2f}')

输出结果同样是 1.14

似乎这两种方法都能完美实现四舍五入……吗?

显然没这么简单。如果将 a 的值改为 1.125,再次运行上述代码,就会发现两种方法的结果都变成了 1.12,而非期望的 1.13

Python代码运行结果截图,显示round和字符串格式化方法处理1.125时输出1.12

千万别小看这微小的误差。实际场景中,例如计算成绩,这种偏差完全可能导致结果判定的不同。

黑人困惑表情包,周围环绕着问号

一些不够深入的教程会解释说,这是因为 Python 采用了一种名为「四舍六入五成双」的舍入机制:当要舍去的数字是5时,看5前面的数字是奇数则进位,是偶数则舍去。因此 1.135 (奇数3) 得到 1.14,而 1.125 (偶数2) 得到 1.12

数值转换图:1.135 → 1.14;1.125 → 1.12

还有些教程会提供一种“传统”方法来实现四舍五入:将数字乘以10的N次方,加上0.5后取整,再除以10的N次方。

int(a * 10 ** 2 + 0.5) / 10 ** 2

1.125 使用此方法确实得到了正确结果 1.13

但问题到此就解决了吗?

如果再把 a 改为 1.035。你会发现结果变得不可预测:它既不符合奇数进位的规律,也未通过先乘再除的方法实现四舍五入。

代码截图显示处理1.035时,round、格式化和先乘后除法都输出1.03

为了探究规律,我们可以编写一个循环,测试从 1.0051.995 的所有以 .005 结尾的数字。

for i in range(100):
    s = f'1.{i:02}5'
    a = float(s)
    b = int(a * 100 + 0.5) / 100
    print(a, round(a, 2), f'{a:.2f}', b, sep='\t')

批量测试代码截图
批量测试结果部分截图
批量测试结果延续截图

观察输出结果可以发现:round 和字符串格式化得到的结果完全一致,并且没有明确的规律可循。而先乘后除法在大多数情况下符合四舍五入,但仍存在一些例外。

导致这种现象的根本原因,与经典的 0.1 + 0.2 != 0.3 问题一样,都源于浮点数的精度限制。由于二进制表示的限制,很多在十进制下以5结尾的小数,其内部的二进制表示值其实略小于5,因此按照“小于5则舍去”的规则,自然不会被进位。

输出更多小数位数后,就能清楚地看到这个现象:

高精度输出截图,显示浮点数实际存储值略小于显示值

那么,如何在 Python 中实现精确且可控的四舍五入呢?答案是使用内置的 decimal 模块,它专门用于高精度的十进制算术运算。

使用 round 函数处理 Decimal 类型对象,才真正实现了「四舍六入五成双」的银行家舍入法。

from decimal import Decimal

x = 1.035
print(round(Decimal(str(x)), 2))

使用decimal模块进行舍入的结果截图

这种「银行家舍入」机制其实比传统四舍五入更科学。因为5正好是两个相邻数字的中间值,如果全部进位,会导致统计上的整体偏差偏大。而银行家舍入规则让舍入误差在大量计算中相互抵消,趋向于零。

如果你坚持需要传统的“四舍五入”规则,同样可以通过 decimal 模块实现。只需将 Context 中的 rounding 属性设置为 ROUND_HALF_UP

from decimal import Decimal, ROUND_HALF_UP, getcontext

x = 1.045
getcontext().rounding = ROUND_HALF_UP
print(round(Decimal(str(x)), 2))

另一种等效的写法是使用 Decimal 对象的 quantize 方法,直接指定保留位数和舍入规则。

from decimal import Decimal

x = 1.045
print(Decimal(str(x)).quantize(Decimal('0.01'), rounding='ROUND_HALF_UP'))

这样,就能完美地按照四舍五入规则保留小数了。

但这里还有一个至关重要的细节:必须通过字符串来创建 Decimal 对象。如果直接用浮点数初始化,该浮点数本身携带的精度误差会传入 Decimal,从而导致舍入规则再次失效。

对比截图:用字符串和浮点数创建Decimal对象,执行四舍五入结果不同

总结一下,在 Python 中处理金融、科学计算等对精度要求高的四舍五入时,最可靠的方法是结合 decimal 模块与字符串输入。理解这一机制,是深入 计算机基础 中数值计算原理的重要一步。希望这篇解析能帮助你彻底避开这个看似简单却暗藏玄机的“坑”。




上一篇:告别枯燥命令行:用Python Textual库轻松构建现代化终端UI
下一篇:30B开源模型REDSearcher:深度搜索Agent低成本训练框架详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-17 03:05 , Processed in 0.612561 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表