代码之家  ›  专栏  ›  技术社区  ›  EMP

Windows环境下Linux浮点字符串转换行为的仿真

  •  7
  • EMP  · 技术社区  · 14 年前

    我在输出浮点数时遇到了一个恼人的问题。当我在Windows上以2个小数点的精度格式化11.545时,它会输出“11.55”,正如我所期望的那样。然而,当我在Linux上做同样的操作时,输出是“11.54”!

    我最初在Python中遇到了这个问题,但进一步的研究表明,不同之处在于底层的C运行时库。(在这两种情况下,体系结构都是x86-x64)在Windows和Linux上运行下面的C行会产生不同的结果,与在Python中一样。

    printf("%.2f", 11.545);
    

    为了更清楚地说明这一点,我把数字打印到小数点后20位( "%.20f" ):

    Windows: 11.54500000000000000000
    Linux:   11.54499999999999992895
    

    我的问题是:

    printf ,但执行浮点-字符串转换的实际函数是 _cfltcvt_l 而且它的来源似乎不可用。)

    情节愈演愈烈!关于这是由不精确表示引起的理论可能是错误的,因为 它有一个精确的二进制表示,当输出为 '%.2f' % 0.125 :

    Windows: 0.13
    Linux:   0.12
    

    然而, round(0.125, 2) 在Windows和Linux上返回0.13。

    6 回复  |  直到 14 年前
        1
  •  2
  •   D.Shawley    14 年前

    首先,听起来像是Windows有它 错误的 %.2f 四舍五入到适当的位数 dtoa 实施人 David M. Gay

    如果你还没读过 “如何准确打印浮点数” 斯蒂尔和怀特,找一本读一下。这绝对是一本很有启发性的读物。一定要找到70年代末的原件。我想我是从ACM或IEEE那里买的。

        2
  •  2
  •   Mark Dickinson Alexandru    14 年前

    我不认为Windows在这里做了什么特别聪明的事情(比如试图重新解释以10为基数的浮点值):我猜它只是准确地计算前17位有效数字(这将得到“11.545000000000000”),然后在末尾加上额外的零,以补补上点后所要求的位数。

    正如其他人所说,0.125的不同结果来自Windows使用round-half-up和Linux使用round-half-to-even。

    请注意,对于Python3.1(以及Python2.7,当它出现时),格式化浮点值的结果将与平台无关(除非可能在不寻常的平台上)。

        3
  •  1
  •   Cyrus    14 年前

    import decimal
    
    fs = ['11.544','11.545','11.546']
    
    def convert(f,nd):
        # we want 'nd' beyond the dec point
        nd = f.find('.') + nd
        c1 = decimal.getcontext().copy()
        c1.rounding = decimal.ROUND_HALF_UP
        c1.prec = nd
        d1 = c1.create_decimal(f)
        c2 = decimal.getcontext().copy()
        c2.rounding = decimal.ROUND_HALF_DOWN
        c2.prec = nd   
        d2 = c2.create_decimal(f)
        print d1, d2
    
    for f in fs:
        convert(f,2)
    

    可以从int或字符串构造十进制数。在你的例子中,输入一个比你想要的数字更多的字符串,然后通过设置上下文.prec.

    下面是一个链接,链接到pymotw post,其中包含十进制模块的详细概述:

    http://broadcast.oreilly.com/2009/08/pymotw-decimal---fixed-and-flo.html

        4
  •  0
  •   Eli Bendersky    14 年前

    我的意思是,除了说两个浮点数相等外:

    f1 == f2
    

    fabs(f1 - f2) < eps
    

    对于一些小的 eps . 关于这个问题的更多细节可以找到 here .

        5
  •  0
  •   John La Rooy    14 年前

    print "%.2f"%(11.545-1e-12)
    
        6
  •  0
  •   paxdiablo    14 年前

    您可以尝试减去(或为负数加上)一个小的增量,这对距离精度足够远的数字的舍入没有影响。

    例如,如果你用 %.2f ,在Windows上尝试此版本:

    printf("%.2f", 11.545 - 0.001);
    

    浮点数是出了名的问题,如果你不知道下面发生了什么。在这种情况下,最好的办法是编写(或使用)十进制类型库来缓解问题。


    #include <stdio.h>
    int main (void) {
        printf("%.20f\n", 11.545);
        printf("%.2f\n", 11.545);
        printf("%.2f\n", 11.545 + 0.001);
        return 0;
    }
    

    11.54499999999999992895
    11.54
    11.55
    


    更新:

    叶夫根尼,根据你的评论:

    它适用于这个特定的情况,但不是一般的解决方案。例如,如果我要格式化的数字是0.545而不是11.545,则“%.2f”(0.545-0.001)返回“0.54”,而Linux上的“%.2f”%0.545则正确返回“0.55”。

    这就是为什么我说你必须检查整个范围,看看它是否能工作,为什么我说十进制数据类型更好。

    如果你想要小数精度,那就是你必须要做的。但是你可能想考虑一下Linux也会走另一条路的情况(根据你的评论)——可能会有Linux和Windows在与你发现的相反的方向上不一致的情况——十进制类型可能解决不了这个问题。

    你可能需要让你的比较工具更智能一点,因为它们可以忽略最后一个分数的1的差异。