在Windows上模拟Linux的浮点字符串转换行为

时间:2010-02-10 03:55:46

标签: python c floating-point printf precision

我在输出浮点数时遇到了一个恼人的问题。当我在Windows上格式化11.545,精度为2小数点时,它输出“11.55”,正如我所料。但是,当我在Linux上执行相同操作时,输出为“11.54”!

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

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

为了更多地了解这一点,我将数字打印到20位小数("%.20f"):

Windows: 11.54500000000000000000
Linux:   11.54499999999999992895

我知道11.545不能精确地存储为二进制数。所以似乎正在发生的事情是Linux以最佳精度输出实际存储的数字,而Windows输出最简单的十进制表示,即。试图猜测用户最有可能的意思。

我的问题是:是否有任何(合理的)方法来模拟Windows上的Linux行为?

(虽然Windows的行为当然是直观的,但在我的情况下,我实际上需要将Windows程序的输出与Linux程序的输出进行比较,而Windows是唯一可以改变的程序。顺便说一下,我试着查看printf的Windows源代码,但执行float->字符串转换的实际函数是_cfltcvt_l,其源代码似乎不可用。)

编辑:情节变浓!关于这种由不精确表示引起的理论可能是错误的,因为 0.125 确实具有精确的二进制表示,并且当使用'%.2f' % 0.125输出时它仍然不同:

Windows: 0.13
Linux:   0.12

但是,round(0.125, 2)在Windows和Linux上都返回0.13。

6 个答案:

答案 0 :(得分:2)

首先,在这种情况下,Windows听起来像是错误(这并不重要)。 C标准要求%.2f输出的值四舍五入到适当的位数。最着名的算法是由dtoa实现的David M. Gay。您可以将其移植到Windows或查找本机实现。

如果您尚未阅读Steele和White的“如何准确打印浮点数”,请查找副本并阅读。这绝对是一个启发性的阅读。一定要找到70年代后期的原版。我想我在某个时候从ACM或IEEE购买了我的。

答案 1 :(得分:2)

我不认为Windows正在做任何特别聪明的事情(比如尝试重新解释基数10中的浮点数):我猜它只是准确计算前17个有效数字(这将给出'11 .545000000000000')和然后在结尾处添加额外的零以弥补该点之后所请求的位数。

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

请注意,对于Python 3.1(和Python 2.7,当它出现时),格式化浮点数的结果将与平台无关(除非可能在不寻常的平台上)。

答案 2 :(得分:1)

十进制模块可让您访问多种舍入模式:

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或字符串构造小数。在你的情况下,为它提供一个比你想要的更多数字的字符串,并通过设置context.prec进行截断。

这是一个pymotw帖子的链接,带有十进制模块的详细概述:

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

答案 3 :(得分:0)

考虑将浮点数与某些容差/ epsilon进行比较。这比尝试完全匹配要强大得多。

我的意思是,除了说两个花车在以下时间相同:

f1 == f2

在以下情况下说他们是平等的:

fabs(f1 - f2) < eps

对于某些小eps。有关此问题的更多详细信息,请访问here

答案 4 :(得分:0)

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

例如,如果您使用%.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;
}

在我的Cygwin环境中输出:

11.54499999999999992895
11.54
11.55

这对你的特定情况是好的(这是错误的方式,但也应该适用于另一个方向:你需要测试它)但你应该检查你的整个可能的输入范围,如果你想确定这将为你的所有案件工作。


更新

Evgeny,根据您的评论:

  

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

这就是为什么我说你必须检查整个范围以查看它是否可行,以及为什么我说小数据类型会更好。

如果你想要小数精度,那就是你必须要做的。但是你可能想要考虑Linux在其他方面的情况(根据你的评论) - 可能存在Linux和Windows与你发现的方向相反的情况 - 小数类型可能赢了解决这个问题。

您可能需要让比较工具更加智能化,因为他们可以忽略最后一个小数位的差异。

答案 5 :(得分:0)

您可以从该值中减去一小部分以强制进行四舍五入

print "%.2f"%(11.545-1e-12)