什么应该printf(“%。17g \ n”,。1)打印?

时间:2016-09-23 03:31:49

标签: c bash printf posix stdio

我经常使用命令行程序“printf” 测试/预测C stdio库函数将做什么。

但是我最近发现这不起作用了(还有吗?) - 至少在最近的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

我当然感到惊讶和失望的是,上述命令并不能在任何特定平台上提供完全相同的输出。 我的问题是:根据一些官方规范,是否有一个正确的答案,并且,无论如何,是否应该提交错误报告?

一些背景事实:

C中的

.1表示IEEE双精度浮点数 其数值完全是数学上的:

x  = 0.1000000000000000055511151231257827021181583404541015625

且相邻的可表示值为:

x- = 0.09999999999999999167332731531132594682276248931884765625
x+ = 0.10000000000000001942890293094023945741355419158935546875

因此,字符串表示“0.10000000000000001”和“0.1”都是无损的, 从某种意义上说,它们都比x更接近任何其他双精度值; 也就是说,任何合理的字符串到双转换函数都会产生 x给出任一字符串。所以这两个答案在这个意义上都同样好。

然而,0.10000000000000001更接近x而不是0.1。 我一直以为这意味着“0.10000000000000001”是正确答案, 但现在我不确定;也许这是一个依赖于实现的细节?

有关为什么17在任何情况下都是正确使用的数字的说明,请参阅 numeric_limits :: max_digits10的文档:

http://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2005.pdf

1 个答案:

答案 0 :(得分:4)

以下是关于C的一般信息,可能/可能不适用于bashposix规范。

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>中浮点运算(+, - ,*,/)和库函数的准确性实现定义< / strong>,以及<stdio.h><stdlib.h>和{{1}中的库函数执行的浮动内部表示与字符串表示之间转换的准确性 }。实施可能表明准确性未知。 C11dr§5.2.4.2.26

[编辑]

<wchar.h>可以有各种精度,基数,范围细节。为简化起见,本讨论的样本值不是0.1,而是0.10000000000000000555 ......

@user694733的评论很有用,但它在“推荐做法”下使用“应该是一个确切的......”。虽然没有为double指定希望的“0.10000000000000001”。它和上面的内容有助于回答OP的问题:“打印什么?”,“0.10000000000000001”打印。但这种准确性是由C定义的实现。

对于声称符合EEE floating point的C实现,我确信需要“0.10000000000000001”“0.10000000000000000”的结果,但这超出了C问题并进入IEEE 754-xxxx。我对IEEE floating point Character representation的阅读允许符合要求的实施,即纬度仅为往返printf("%.17f", .1)(实际为binary64) - &gt;文字 - &gt; double是必需的。这至少需要15个有效数字,最多可能需要17个。除了将文本转换为double之外,还没有指定这些数字超过15的数字。返回起始double

更深。对于IEEE 754,值double可以“错误地”打印为“0.10000000000000001249000902 ...”之前的任何内容,并且仍然可以正确地往返。

0.100000000000000005551115123...

注意:IEEE允许将文本转换为// 12345678901234567 //0.1000000000000000055511151231257827021181583404541015625 //0.10000000000000001249000902... half way //0.10000000000000001942890293094023945741355419158935546875 忽略超过一定数量的有效数字(double为20)。

短IEEE答案:在将double打印到各种精度时,输出应位于[C I]以下的范围内。

X