首页 > 解决方案 > Python 是否记录了舍入到指定小数位数的行为?

问题描述

用于将 Python 中的 a 舍入为float任何 Python 文档中指定的指定位数的算法?零小数位数(即舍入为整数)的语义round很容易理解,但我不清楚如何实现位数非零的情况。

我能想到的最直接的函数实现(鉴于round零小数位的存在)是:

def round_impl(x, ndigits):
    return (10 ** -ndigits) * round(x * (10 ** ndigits))

我正在尝试编写一些 C++ 代码来模仿 Pythonround()函数对 的所有值的行为ndigits,并且当转换为等效的 C++ 调用时,上述内容在很大程度上与 Python 一致。但是,在某些情况下它会有所不同,例如:

>>> round(0.493125, 5)
0.49312
>>> round_impl(0.493125, 5)
0.49313

当要四舍五入的值处于或非常接近两个潜在输出值之间的确切中点时,显然会出现差异。因此,如果我想要类似的结果,我尝试使用相同的技术似乎很重要。

执行舍入的具体方式是 Python 指定的吗?我在测试中使用 CPython 2.7.15,但我专门针对 v2.7+。

标签: pythonpython-2.7

解决方案


另请参阅What Every Programmer Should Know About Floating-Point Arithmetic,其中更详细地解释了为什么会发生这种情况。

这是一团糟。首先,就目前float而言,没有 0.493125 这样的数字,当你写 0.493125 时,你实际得到的是:

0.493124999999999980015985556747182272374629974365234375

所以这个数字并不完全在两位小数之间,它实际上比 0.49313 更接近 0.49312,所以它肯定应该四舍五入到 0.49312,这一点很清楚。

问题是当你乘以 10 5时,你会得到确切的数字 49312.5。所以这里发生的事情是乘法给你一个不精确的结果巧合地抵消了原始数字中的舍入误差。两个舍入错误相互抵消,耶!但问题是,当你这样做时,四舍五入实际上是不正确的......至少如果你想在中点四舍五入,但 Python 3 和 Python 2 的行为不同Python 2 从 0 舍入,Python 3 舍入到最低有效位。

蟒蛇2

如果两个倍数同样接近,则从 0 开始舍入

蟒蛇 3

...如果两个倍数同样接近,则朝偶数选择进行舍入...

概括

在 Python 2 中,

>>> round(49312.5)
49313.0
>>> round(0.493125, 5)
0.49312

在 Python 3 中,

>>> round(49312.5)
49312
>>> round(0.493125, 5)
0.49312

在这两种情况下,0.493125实际上只是写 0.493124999999999980015985556747182272374629974365234375 的一种简短方式。

那么它是怎样工作的?

我看到了两种看似可行的round()实际行为方式。

  1. 选择具有指定位数的最接近的十进制数,然后将该十进制数四舍五入到float精度。这很难实现,因为它需要比从float.

  2. 取两个最接近的指定位数的十进制数,将它们四舍五入到float精度,然后返回更接近的那个。这将给出不正确的结果,因为它会将数字四舍五入。

Python 选择了……选项 #1!完全正确,但更难实现的版本。请参阅Objects/floatobject.c:927 double_round()。它使用以下过程:

  1. 使用请求的精度将浮点数写入十进制格式的字符串。

  2. 将字符串解析为float.

这使用基于David Gay 的 dtoa 库的代码。如果您希望 C++ 代码能够像 Python 一样获得实际正确的结果,那么这是一个好的开始。幸运的是,您可以将其包含dtoa.c在您的程序中并调用它,因为它的许可非常宽松。


推荐阅读