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