我经常使用命令行程序"printf“来测试/预测C库函数将做什么。
但我最近发现(不再起作用了)--至少在最近的linux/ubuntu发行版上,命令行printf ( bash内置版和/usr/bin/printf)有时会给出与C函数不同的结果。
这意味着,如果我想了解C函数将做什么,我必须运行一些其他程序,这些程序实际上调用了C函数,例如(显然) python、perl、gnuplot或一个实际的C程序。
下面是一个在linux上的演示:
bash$ uname -srvmpio
Linux 3.13.0-95-generic #142-Ubuntu SMP Fri Aug 12 17:00:09 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
bash$ bash --version
GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
bash$ type printf
printf is a shell builtin
bash$ printf "%.17g\n" .1
0.1
bash$ /usr/bin/printf "%.17g\n" .1
0.1
bash$ python -c 'print "%.17g" % .1'
0.10000000000000001
bash$ perl -e 'printf("%.17g\n", .1);'
0.10000000000000001
bash$ gnuplot -e 'print sprintf("%.17g", .1)'
0.10000000000000001
bash$ echo -e '#include <stdio.h>\nint main(){printf("%.17g\\n",.1);}' | gcc -xc - -o /tmp/printf_.1 && /tmp/printf_.1
0.10000000000000001
在其他一些平台上,例如最新的macbook,它们都给出了"0.10000000000000001“(我一直认为这是正确的答案):
bash$ uname -srvmp
Darwin 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 23 18:25:34 PDT 2016; root:xnu-3248.60.10~1/RELEASE_X86_64 x86_64 i386
bash$ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin15)
bash$ type printf
printf is a shell builtin
bash$ printf "%.17g\n" .1
0.10000000000000001
bash$ /usr/bin/printf "%.17g\n" .1
0.10000000000000001
当然,我感到惊讶和失望的是,上面的命令并没有在任何特定的平台上提供完全相同的输出。我的问题是:根据某些官方规范,是否有一个正确的答案,在任何情况下,是否应该提交错误报告?
一些背景事实:
.1在C中表示IEEE双精度浮点数,其值在数学上准确:
x = 0.1000000000000000055511151231257827021181583404541015625
相邻的可表示值是:
x- = 0.09999999999999999167332731531132594682276248931884765625
x+ = 0.10000000000000001942890293094023945741355419158935546875
因此,两个字符串表示"0.10000000000000001“和"0.1”都是无损的,因为它们都更接近x,而不是任何其他双精度值;也就是说,任何合理的字符串到双精度转换函数在给定任一字符串时都会产生x。所以这两个答案在这个意义上都是同样好的。
然而,0.10000000000000001比0.1is更接近x。我一直认为这意味着"0.10000000000000001“是正确的答案,但现在我不确定;也许这是一个依赖于实现的细节?
要了解为什么17是在任何情况下要使用的正确数字的原因,请参阅numeric_limits::max_digits10的文档:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2005.pdf
发布于 2016-09-23 07:20:47
以下是关于C的一般情况,可能不适用于bash
或posix
规范。
预计printf(" %.17f\n", .1)
将至少打印DBL_DECIMAL_DIG
正确的有效数字。DBL_DECIMAL_DIG
是保证double
到十进制文本表示返回到double
并获得相同结果的精度。要做到这一点,至少有10位,通常是17位有效数字是正确的。这是OP的情况,OP当然很清楚。
//0.09999999999999999167332731531132594682276248931884765625
//0.1000000000000000055511151231257827021181583404541015625
//0.10000000000000001942890293094023945741355419158935546875
// 12345678901234567
0.10000000000000001 // DBL_DECIMAL_DIG is commonly 17
问题是,printf()
不需要非常正确地进行任何操作。因此,printf()
的质量将被认为是轻微可接受的,如果它打印了以下两种文字之一,因为0.1
可以通过任何一种文本成功往返。
// 12345678901234567
0.10000000000000001
0.10000000000000000
浮点操作(+,-,*,/)和返回浮点结果的
<math.h>
和<complex.h>
中的库函数的准确性是实现定义的,以及由库函数在<stdio.h>
、<stdlib.h>
和<wchar.h>
中执行的浮点内部表示和字符串表示()之间转换的准确性。实现可能会说,准确性是未知的。C11dr§5.2.4.2.2 6
编辑
double
可以具有不同的精度、基础、范围细节。为了简化,本文讨论的是一个样本值,而不是它的0.1,而是0.10000000000000000555.
@user694733 694733的评论是有用的,但它是在“推荐的实践”下使用的“应该是一个确切的.”。虽然它没有为printf("%.17f", .1)
指定所希望的"0.10000000000000001“。它和上面的内容一起帮助回答了OP的问题:“应该打印什么?”,"0.10000000000000001“应该打印。但这种准确性是由C.
对于声称符合EEE浮点要求的C实现,我确信需要"0.10000000000000001“或"0.10000000000000000”的结果,但这超出了C问题的范围,进入了IEEE754-xxxx。我对IEEE浮点字符表示的理解允许一个符合要求的实现,即只需要往返double
(实际上是binary64) --> text -> double
。这至少需要15位有效位数,并且可能需要17位数。除了将文本转换为double
以外,不指定超过15位数的数字将返回起始double
。
更深一点。对于IEEE754,0.100000000000000005551115123...
值可以“错误地”打印为“0.1000000000001249000902”。但还是正确的往返。
// 12345678901234567
//0.1000000000000000055511151231257827021181583404541015625
//0.10000000000000001249000902... half way
//0.10000000000000001942890293094023945741355419158935546875
注意: IEEE允许将文本转换为double
,忽略超过一定数量的重要数字(double
为20)。
简短的IEEE回答:当将[C I]
打印到各种精度时,输出应该在低于X
的范围内。
12345678901234567
A 0.099999999999999991673327315... preceding X
B 0.099999999999999998612221219... half way
C 0.099999999999999998613 half way rounded to 20 sig digits toward x
D 0.099999999999999999 "%.17e" candidate
E 0.10000000000000000 "%.17e", "%.17f" candidate
X 0.100000000000000005551115123...
G 0.10000000000000001 "%.17e", "%.17f" candidate
H 0.10000000000000001249 half way rounded to 20 sig digits toward x
I 0.100000000000000012490009027... half way
J 0.100000000000000019428902930... following X
https://stackoverflow.com/questions/39652130
复制相似问题