• 技术文章 >后端开发 >Python教程

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

    2016-06-06 16:22:59原创1159

    回复内容:

    因为二进制浮点数不能解决这个问题。

    先看一个现象,和 round 无关的:
    >>> def show(x):
    ...     """打印一个数,20 位精度"""
    ...     print('{:.20f}'.format(x))
    ...
    >>> show(1.5)
    1.50000000000000000000
    >>> show(1.25)
    1.25000000000000000000
    >>> show(1.245)
    1.24500000000000010658
    >>> show(1.45)
    1.44999999999999995559
    >>> show(1.415)
    1.41500000000000003553
    
    四舍五入是基于十进制的,在二进制无法精确表示的时候是会有误差的。
    任何需要十进制运算的地方,都需要用 decimal.Decimal 取代 float:
    >>> Decimal(1.45)
    Decimal('1.4499999999999999555910790149937383830547332763671875')
    >>> Decimal('1.45')
    Decimal('1.45')
    >>> Context(prec=2, rounding=ROUND_HALF_UP).create_decimal('1.45')
    Decimal('1.5')
    >>> Decimal('1.45').normalize(Context(prec=2, rounding=ROUND_HALF_UP))
    Decimal('1.5')
    >>> Decimal(Decimal('1.45').quantize(Decimal('.1'), rounding=ROUND_HALF_UP))
    Decimal('1.5')
    
    Note that this is in the very nature of binary floating-point: this is not a bug in Python, and it is not a bug in your code either. You’ll see the same kind of thing in all languages that support your hardware’s floating-point arithmetic (although some languages may not display the difference by default, or in all output modes).
    • 某些十进制数(如0.1)在机器内部无法用有限数目的二进制位0和1精确表示,只能通过增加固定的位数来提高精度,从而更逼近原来十进制数。

    round(number[, ndigits])
    Return the floating point value number rounded to ndigits digits after the decimal point. If ndigits is omitted, it defaults to zero. The result is a floating point number. Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0.
    • python的round函数定义为 在任意整数*10^(-ndigits) 中取最靠近number的数值,如果有两个整数距离number相等则按照远离0的一侧(负数-0-正数)取值 。
    round(0.5) is 1.0 and round(-0.5) is -1.0)
    
    题主测试的和那个bug无关。round是四舍六入五成双的。 python 的官方实现确实有问题,至于为什么作者不改进,原作者可能是什么心态,可以参见刘海洋的答案。大致心态也许是:反正这个错误不在我,所以虽然有方法,但我也不会去解决这个问题。

    咋一看,很多人会把这个问题理解为浮点数不精确的问题。浮点数不精确,这一点是常识,是对的。

    但浮点数并非在所有情况下都不精确,也并非「只要浮点数不精确,所涉及的相关计算问题就毫无解决的价值」。

    Python 的 round 问题是个典型的例子,推测作者的观点是认为这类与浮点数精确度有关的问题没有解决的必要。如同 @刘海洋 的观点一样,所以导致了现在的结果。

    不过仔细分析会发现,0.5 这种浮点数是可以被精确表示的,而 round 这个函数的特定性在于,round 舍入之后的精确性毫无意义。所以 round 这个问题本身造成的不精确性是可以被解决的。

    对于 round 的不精确性,重要的在于结果是进一还是去尾。我写了一个简单的例子来说明这个问题,当然这个函数在特定情况下也不准确,不过在题目给出的情况下都可以得到正确的结果。

    #include 
    
    float my_round(float src, int idx)
    {
        int i;
        for (i=idx;i--;)
            src *=10;
        float dest = (int)src;
        if (src >= dest+0.5)
            dest += 1;
        for (i=idx;i--;)
            dest /=10;
        return dest;
    }
    
    
    int main()
    {
        printf("result=%f\n", my_round(1.5, 0));
        printf("result=%f\n", my_round(1.25, 1));
        printf("result=%f\n", my_round(1.245, 2));
        printf("result=%f\n", my_round(1.45, 1));
        printf("result=%f\n", my_round(1.415, 2));
        printf("result=%f\n", my_round(2.675, 2));
    }
    
    两个问题,一个是
    浮点数在计算机中的真实存储(这里是在 Python 中的存储)
    expect:  1.0
    actual:  1
    expect:  1.1
    actual:  1.100000000000000088817841970012523233890533447265625
    expect:  1.2
    actual:  1.1999999999999999555910790149937383830547332763671875
    expect:  1.3
    actual:  1.3000000000000000444089209850062616169452667236328125
    expect:  1.4
    actual:  1.399999999999999911182158029987476766109466552734375
    expect:  1.5
    actual:  1.5
    expect:  1.6
    actual:  1.600000000000000088817841970012523233890533447265625
    expect:  1.7
    actual:  1.6999999999999999555910790149937383830547332763671875
    expect:  1.8
    actual:  1.8000000000000000444089209850062616169452667236328125
    expect:  1.9
    actual:  1.899999999999999911182158029987476766109466552734375
    
    很简单,因为2.675在表示的时候可能是2.6749,所以round以后还是2.67了 你的需求本质是:精确小数运算。然而,float不是为了满足这一需求而设计的,decimal才是。所以,为float单独定制一个round,不符合float的设计意图,也很难实现。以你的函数为例,temp*10这个操作在float下不是精确的。

    >>> 1.222*10
    12.219999999999999 我的意思是为啥不写个可以解决这个问题的函数:例如(仅仅一个例子,可能从性能啊之类肯定不行,官方是出于什么原因不写一个类似的)
    def myround(par,l):
    temp = 1
    for i in range(l):
    temp*=10
    v = int((par+0.5/temp)*temp) / temp
    return v

    i = 1.25
    print(myround(i,1))
    i = 1.245
    print(myround(i,2))
    i = 1.21
    print(myround(i,1))
    i = 1.249
    print(myround(i,2))

    ----
    1.3
    1.25
    1.2
    1.25 他的解释应该说的是这个不是个bug。默认数值都是float,float本来就不精确,所以python应该是只对decimal精确。我觉得这个也挺正常的。
    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:
    Web大前端开发直播班

    相关文章推荐

    • Python归纳总结之json标准库• 详细讲解Python之Seaborn(数据可视化)• python基础语法详解之函数• 完全掌握Python数学相关模块• Python详细解析之多线程爬虫与常见搜索算法

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网