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

9 投票
6 回答
1782 浏览
提问于 2025-04-15 19:05

我遇到了一个烦人的问题,关于输出浮点数。当我在Windows上把11.545格式化为保留两位小数时,输出是“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的程序是我唯一可以更改的。顺便说一下,我试着查看Windows的printf源代码,但实际执行浮点数到字符串转换的函数是_cfltcvt_l,而它的源代码似乎不可用。)

编辑:事情变得复杂了!关于这可能是由于不精确表示造成的理论可能是错误的,因为0.125确实有一个精确的二进制表示,但在用'%.2f' % 0.125输出时仍然不同:

Windows: 0.13
Linux:   0.12

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

6 个回答

1

decimal模块让你可以使用几种不同的四舍五入方式:

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)

你可以用一个整数或者字符串来创建一个decimal对象。在你的情况下,可以给它一个包含更多数字的字符串,然后通过设置context.prec来截断多余的数字。

这里有一个链接,里面详细介绍了decimal模块:

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

2

首先,听起来Windows在这个问题上处理得不对对(虽然这其实没什么大不了的)。C语言标准要求用%.2f输出的值是四舍五入到合适的位数。最著名的算法是dtoa,这个算法是由David M. Gay实现的。你可以尝试把这个算法移植到Windows上,或者找一个本地的实现。

如果你还没读过Steele和White写的"如何准确打印浮点数",那就找一本来看看。这绝对是一本很有启发性的书。记得找70年代末的原版。我记得我是在ACM或IEEE买的。

2

我觉得Windows在这里并没有做什么特别聪明的事情(比如试图把浮点数转换成十进制)。我猜它只是准确计算了前17位有效数字(这会得到'11.545000000000000'),然后在后面加上额外的零,以满足小数点后需要的位数。

正如其他人所说,0.125的不同结果是因为Windows使用的是四舍五入,而Linux使用的是四舍五入到最接近的偶数。

需要注意的是,对于Python 3.1(以及即将出现的Python 2.7),格式化浮点数的结果是与平台无关的(可能在一些特殊平台上除外)。

撰写回答