代码之家  ›  专栏  ›  技术社区  ›  Jason R

Python是否记录了舍入到指定小数位数的行为?

  •  0
  • Jason R  · 技术社区  · 6 年前

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

    我可以想到的最简单的函数实现(考虑到 到零位小数)将是:

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

    round() 所有值的函数 ndigits

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

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

    执行Python指定的舍入的具体方法是什么?我在测试中使用的是CPython 2.7.15,但我的目标是v2.7+。

    2 回复  |  直到 6 年前
        1
  •  3
  •   Dietrich Epp    6 年前

    也可参考 What Every Programmer Should Know About Floating-Point Arithmetic ,其中有更详细的解释说明了为什么会发生这种情况。

    float 当你写0.493125时,实际上得到的是:

    所以这个数字不完全在两个小数之间,它实际上更接近于0.49312,而不是0.49313,所以它肯定会四舍五入到0.49312,这点很清楚。

    问题是当你乘以10 5个 ,你会得到确切的数字49312.5。所以这里发生的是乘法给了你一个 不精确结果 巧合 取消了原始数字中的舍入误差。两个舍入错误互相抵消了,耶!但问题是,当你这样做的时候,四舍五入实际上是不正确的。。。至少如果你想绕过去 向上的

    Python 2

    如果两个倍数相等,则从0开始舍入

    Python 3

    …如果两个倍数相等,则对偶数选择进行舍入。。。

    摘要

    在Python 2中,

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

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

    在这两种情况下,0.493125是 写0.4931249999999998001598555674718227237462974365234375只是很短的一段路。

    我认为有两种可能的方法 round() 事实上

    1. 用指定的位数选择最近的十进制数,然后将该十进制数四舍五入到 浮动 精确。这很难实现,因为它需要以比从 .

    2. 取两个最接近的十进制数和指定的位数,四舍五入到 浮动

    而Python选择。。。选项1!完全正确,但实现版本要困难得多。参考 Objects/floatobject.c:927 double_round() . 它使用以下过程:

    1. 十进制的

    2. 浮动 .

    它使用基于 David Gay's dtoa library . 如果您希望C++代码得到 像Python一样,这是一个好的开始。幸运的是你可以包括 dtoa.c

        2
  •  0
  •   YSelf    6 年前

    和的Python文档 2.7 指定行为:

    数值四舍五入到与幂减最接近的10的倍数 ndigits;如果两个倍数相等,则舍入 从0开始。

    3.7 :

    对于支持round()的内置类型,值被舍入为 最接近幂的10的倍数减去ndigits;如果两个倍数 同样接近,取整朝着均匀选择进行

    更新:

    (cpython)实现 can be found floatobjcet.c 在函数中 float___round___impl ,它调用 round ndigits 没有给出,但是 double_round 如果是的话。

    有两个实现。 一个将double转换为字符串(也称为decimal)并返回double。 另一个做一些浮点运算,调用 pow 在它的核心号召 10**-ndigits .

    要获得精确的算法,请查看链接的源文件。