【问题标题】:Why and How, is this C program showing 7.21 accurately?为什么以及如何,这个 C 程序是否准确地显示 7.21?
【发布时间】:2018-05-12 12:40:07
【问题描述】:

我很熟悉这样一个事实,即十进制分数通常不能很好地转换为二进制,因此存储了一个近似值,当转换回十进制时,它与原始数字有点偏离。我相信 0.21 就是这种分数的一个例子。

这个程序如何以及为什么在 gcc 中编译时准确显示 7.21?

而且(这个问题的重要第二部分),为什么它准确地显示 7.21,它是否不准确地显示 839.21?

我可以理解为什么它会不准确地显示 0.21 或任何数字点 21 不准确,因为它需要很多位才能准确地将它放入二进制中,即使是一个。但我希望它始终不准确地显示n.21,不管整数 n 是什么

printf("%.100f\n", 7.21);  
printf("%.100f\n", 839.21); 
produces 7.210000000000000000000... 
839.2100000000000400000....

知道为什么它做的一个准确,一个不准确吗? (无论如何在我的 gcc 编译器上!)

如果相关,gcc --version 显示gcc.exe (rubenvb-4.5.4) 4.5.4 Copyright (C) 2010 Free Software Foundation, Inc.

我和一个人交谈过,他说他得到的两个都是不准确的,这是人们所期望的。所以我拍摄了一个屏幕展示来展示这种行为

注意- 我注意到我的 cygwin gcc 实现不是最新的 cygwin gcc 实现.. 我刚刚做了 where gcc 它是从 C:\Perl64\site\bin\gcc.exe 运行它,所以它没有运行 cygwin一。它可能正在运行一个旧的 ming (这是 R 建议的)。 chux 是 cygwin n GNU C11 (GCC) 版本 6.4.0 (i686-pc-cygwin)`。

【问题讨论】:

  • 试试volatile double x = 7.21; printf("%.100f\n", x); 看看优化是否在起作用。
  • @chux volatile double 也精确地打印 7.21
  • 感谢您的检查。我相信问题是带有 FP 参数的弱 printf()。如果您的编译器可能,请使用 printf("%a\n", x); 查看十六进制有效数。
  • @chux 0x1.cd70a4p+2 弱 printf() 是什么意思?
  • barlop,printf("%.100f\n", 7.21); 是真正的代码还是打印float?甚至您的printf("%a\n"... 也可能很弱。 0x1.cd70a4p+2 预计与volatile float x = 7.21; printf("%a\n", x); 一起使用

标签: c gcc binary numbers


【解决方案1】:

这个程序如何以及为什么在 gcc 中编译时准确显示 7.21?

旧的printf() 处理最低有效十进制数字是有限的。

printf("%.100f\n", x); 不需要打印x 准确地传递了某个数字。这确实没有 C 规范,但对于 DBL_DIG 数字(通常为 15),十进制输出应该至少正确 - 这将是“弱”且有问题的。

更好的且通常可以接受的printf() 至少对于DBL_DECIMAL_DIG 位数(通常为17)是正确的。如果不达到无限的精度,正确地获取最后一位数字可能会很麻烦。见Table-maker's dilemma。用零而不是正确的数字填充“右边”并不少见。 这就是 OP 的代码所做的。它去了正确的四舍五入的 17 位数字,然后补零。

高质量的printf() 将正确打印所有数字的double x = 839.21;x = 7.21;。例如:

839.2100000000000363797880709171295166015625...
839.210000000000000019984014443252817727625370025634765625.... (as long double)
vs OP's
839.2100000000000400000....

123.4567890123456789 // digit place

7.20999999999999996447286321199499070644378662109375...
7.210000000000000000034694469519536141888238489627838134765625000... (as long double)
7.210000000000000000000

OP 的 printf() 最多只能有 16 位左右的数字。

7.210000000000000000000.... 的输出看起来很棒,因为printf() 输出到一个点,然后用零填充。见@Eric Postpischilluck


注意:一些优化将使用long double(研究FLT_EVAL_METHOD)执行任务,因此long double 也发布了x86 extended precision 结果。

由巴洛普添加

Eric Postpischil 的一些补充观点

OP 的 printf 不适用于 7.21。 OP的 printf 完全通过 7.20999999999999996447286321199499070644378662109375,但打印的是7.210…,所以是错误的。它恰好是错误的,因为 OP 的 printf 中的错误完全抵消了 当 7.21 舍入为二进制浮点数时发生。

埃里克说得对,printf("%.100f\n", 7.20999999999999996447286321199499070644378662109375);打印 7.210000000

Eric 详细说明了他如何知道它是 7.209999999999999964472863211994990706443786662109375,它被发送到 printf,而不是其他类似的长数字。

Eric 评论说他知道,通过使用良好的 C 实现,可以将源代码中的十进制数字正确转换为二进制浮点并正确打印。 (Apple 在 macOS 上的开发人员工具。)并使用他编写的一些 Maple(数学软件)代码来帮助他进行浮点运算。如果没有这些,他可能不得不花很长的路要走,就像在小学一样,但数字更多。

【讨论】:

  • 你说我的 printf 最多只能达到 16 位左右,但看看我得到的 7.21 的结果是准确的,即 7.21000000000000000000000000000000000000000...
  • @Clifford 答案已修改。 OP 的 printf() 只能正确打印到大约 15 个十进制数字,然后接下来的几个大约是正确的。在第 17 位左右的数字之后,函数只是用'0' 填充。它打印出准确的7.210000... 是运气。
  • @barlop 尝试不止 2 个示例。尝试困难的值,例如 double x = 1.0 - DBL_EPSILON;1.0 + DBL_EPSILON, DBL_MIN, DBL_MAX, DBL_TRUE_MIN
  • @barlop:您的printf 不适合 7.21。您的printf 正好通过了 7.20999999999999996447286321199499070644378662109375,但它打印了 7.210...,所以它是错误的。它恰好是错误的,因为您的 printf 中的错误完全抵消了 7.21 舍入为二进制浮点时发生的错误。
  • @EricPostpischil 糟糕,我最初发布了 long double(10 字节)值。现在包括 8 字节 binary64
【解决方案2】:

我会将已接受的答案保留为已接受。

但我会发布这个答案来解决关于 839.21 的问题和问题的另一部分

该 cygwin gcc 实现的 printf 中似乎存在错误。尽管 R 认为这是一个 ming 的事情,并且有一个与 mingw 类似的 printf 错误,它更深,是 Windows C/C++ 运行时文件的错误。虽然 Chux 发现他的 cygwin 实现很好,但他的 cygwin 实现更流行。

我找到了一个没有错误的在线 C 编译器,所以它很好地说明了事情。 https://www.tutorialspoint.com/compile_c_online.php

我的问题之一是为什么 7.21 显示准确而 839.21 显示不准确,因为它们在小数点后具有相同的分数。

printf("%.100f\n", 7.21);
printf("%.100f\n", 839.21);

值得看看没有错误的 printf。

printf("%f\n",7.21); // 7.210000

printf("%f\n",839.21); // 839.210000

我们可以看到它们是如何存储在double中的

printf("%.60f\n",7.21);

7.209999999999999964472863211994990706443786621093750000000000


printf("%.20a\n",7.21);  

0x1.cd70a3d70a3d70000000p+2


printf("%.60f\n",839.21);

839.210000000000036379788070917129516601562500000000000000000000


printf("%.20a\n",839.21);

0x1.a39ae147ae1480000000p+9

请注意,以二进制形式存储的分数,即二进制点之后的数字非常不同,7.21 与 839.21 非常不同这是因为在科学计数法中,点之前只有一位数字。因此,即使它只是科学记数法中的十进制数,小数部分也会有所不同。 7.21 和 8.3921 如果你看二进制的 839 是 1101000111 所以你可以看到 1.A 那里,因为第一位是 1,那么对于下一个半字节你有 1010 是 A。与 7.21 的情况不同

这就是为什么人们不应该期望在准确性方面得到相同的结果,存储 7.21 和 839.21

Chux 提到我的实现中存在一个问题,涉及在 %a 情况下将内部转换为浮点数。因此,值得研究一下它们是如何存储在 float 中的,方法是执行 float f; 并使用格式说明符 %.60f 和 %.20a 传递给 printf,仍然使用上面提到的 printf 工作实现。

printf("%.60f\n",f); //float f=7.21  
7.210000038146972656250000000000000000000000000000000000000000

printf("%.20a\n",f); //float f=7.21  
0x1.cd70a400000000000000p+2 <-- 7.21 in float, in hex.

printf("%.60f\n",f); //float f=839.21  
839.210021972656250000000000000000000000000000000000000000000000
0x1.a39ae200000000000000p+9 <-- 839.21  in float, in hex

现在,如果我们查看我的问题,Chux 注意到存在一个错误,即在 %a 情况下它将数字转换为浮点数。

例如,在我早期(不是最新)的 cygwin 实现中,这是我的问题以及错误在哪里,double x=7.21; printf("%a\n", x); 正在打印十六进制,但它显示的是存储为浮点数的十六进制。 0x1.cd70a4p+2

bug 可能还有更多,因为 %f 的情况可能有点不同,但无论如何,肯定是 printf 的 bug,在早期的 cygwin 实现 gcc.exe (rubenvb-4.5.4) 4.5.4 版权所有 (C) 2010 Free Software Foundation, Inc.(我不知道这是否也会影响早期的 mingw)。

这里有一个与 mingw 的 printf 相关的 printf 错误,这取决于 C/C++ 运行时文件Long double is printed incorrectly with iostreams on MinGW 但我的问题在 chux 使用的最新 cygwin gcc 中得到了解决。 (我会尝试最新的 cygwin 来仔细检查)。

【讨论】:

    猜你喜欢
    • 2015-12-19
    • 1970-01-01
    • 2021-04-04
    • 2022-06-30
    • 1970-01-01
    • 2015-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多