7

我在输出浮点数时遇到了一个烦人的问题。当我在 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

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

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

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

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

Windows: 0.13
Linux:   0.12

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

4

6 回答 6

2

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

如果您还没有阅读过 Steele 和 White的“如何准确地打印浮点数”,请找一份并阅读。这绝对是一本启蒙读物。确保找到 70 年代后期的原件。我认为我在某个时候从 ACM 或 IEEE 购买了我的。

于 2010-02-10T04:40:27.267 回答
2

我不认为Windows在这里做任何特别聪明的事情(比如试图重新解释以10为底的浮点数):我猜它只是准确地计算前17个有效数字(这将给出'11.545000000000000')然后附加额外的最后的零以弥补该点之后所请求的位置数。

正如其他人所说,0.125 的不同结果来自使用半舍入的 Windows 和使用舍入半到偶数的 Linux。

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

于 2010-02-10T11:13:29.743 回答
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

于 2010-02-10T04:17:48.973 回答
0

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

我的意思是,除了在以下情况下说两个浮点数相等:

f1 == f2

在以下情况下说它们相等:

fabs(f1 - f2) < eps

对于一些小eps。可以在此处找到有关此问题的更多详细信息。

于 2010-02-10T04:02:52.033 回答
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 与您发现的相反方向不一致的情况 - 十进制类型可能会获胜解决不了。

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

于 2010-02-10T04:16:45.950 回答
0

您可以从该值中减去一小部分以强制向下舍入

print "%.2f"%(11.545-1e-12)
于 2010-02-10T04:59:22.997 回答