简体   繁体   English

为什么以及如何,这个C程序准确显示7.21?

[英]Why and How, is this C program showing 7.21 accurately?

I am familiar with the fact that decimal fractions often don't convert neatly into binary, and so an approximation is stored and when converted back into decimal is a bit off from the original number. 我很熟悉这样的事实:十进制分数通常不会整齐地转换为二进制,因此存储近似值,并且当转换回十进制时与原始数字相差一点。 I believe 0.21 would be an example of such a fraction. 我相信0.21就是这样一个分数的一个例子。

How and why is this program compiled in gcc showing 7.21 accurately? 如何以及为什么这个程序在gcc中准确编译显示7.21?

And (the important second part to this question), why given that it's showing 7.21 accurately, is it showing 839.21 inaccurately? 而且(这个问题的重要第二部分),为什么它准确显示7.21,它是否显示839.21不准确?

I can understand why it'd show 0.21 inaccurately, or any number point 21 inaccurately, since it takes a lot of bits to put it in binary accurately, if one even at all. 我可以理解为什么它会错误地显示0.21,或者不正确地显示任何数字21,因为它需要很多位来准确地将它放入二进制,如果有的话。 But i'd expect it to consistently show inaccurately show n.21 regardless of what integer n is 但我希望它始终显示不准确显示n.21无论整数n是多少

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

Any idea why it does one accurately and one inaccurately? 知道为什么它准确地做了一个而且一个不准确吗? (on my gcc compiler anyway!) (无论如何,在我的gcc编译器上!)

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

I spoke to one person that said he got both as inaccurate, which is more what one would expect.. So i've taken a screenshow showing the behaviour 我跟一个人说过,他说两者都不准确,这更像是人们所期待的......所以我拍了一个屏幕显示行为

在此输入图像描述

Note- I notice that my cygwin gcc implementation is not the latest cygwin gcc implementation.. I just did where gcc it was running it from C:\\Perl64\\site\\bin\\gcc.exe so it wasn't running a cygwin one. 注意 - 我注意到我的cygwin gcc实现不是最新的cygwin gcc实现..我只是where gcc运行它从C:\\Perl64\\site\\bin\\gcc.exe运行它所以它没有运行cygwin。 It probably was running an old ming one(Which is what R suggested). 它可能正在运行一个老明星(这是R建议的)。 chux's is cygwin n GNU C11 (GCC) version 6.4.0 (i686-pc-cygwin)`. chux是cygwin n GNU C11(GCC)版本6.4.0(i686-pc-cygwin)`。

How and why is this program compiled in gcc showing 7.21 accurately? 如何以及为什么这个程序在gcc中准确编译显示7.21?

The older printf() handling on the least significant decimal digits is limited. 处理最低有效十进制数字的旧printf()是有限的。

printf("%.100f\\n", x); need not print x accurately passed a certain digit. 不需要打印x准确地通过一定数字。 There really is no C spec on this yet decimal output should be at least correct for DBL_DIG digits (typically 15) - this would be "weak" and problematic. 确实没有C规范,但十进制输出应该至少DBL_DIG数字(通常为15)是正确的 - 这将是“弱”并且有问题。

A better, and often acceptable, printf() will be correct for at least DBL_DECIMAL_DIG digits (typically 17). 对于至少DBL_DECIMAL_DIG数字(通常为17),更好且通常可接受的printf()将是正确的。 Without going to unlimited precision, getting the last digit correct can be troublesome. 没有无限制的精确度,最后一个数字是正确的可能是麻烦的。 See Table-maker's dilemma . 见表制造者的困境 Padding the "right" with zeros rather than the correct digits is not uncommon. 用零填充“右”而不是正确的数字并不罕见。 This is what OP's code did . 这就是OP的代码所做的 It went to the correct rounded 17 digits and then zero padded. 它转到正确的圆形17位数,然后填零。

A high quality printf() will print double x = 839.21; 高质量的printf()将打印double x = 839.21; and x = 7.21; 并且x = 7.21; correctly for all digits. 正确的所有数字。 Eg: 例如:

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's printf() is only good up to 16 or so digits. OP的printf()最多可达16个左右。

The output of 7.210000000000000000000.... only looks great because the printf() went out to a point and then padded with zeros. 7.210000000000000000000....的输出看起来很棒,因为printf()走到一个点然后用零填充。 See @Eric Postpischil and luck @Eric Postpischil运气


Note: Some optimizations will perform the task with long double (research FLT_EVAL_METHOD ) so long double as x86 extended precision results posted too. 注:某些优化将执行与任务long double (研究FLT_EVAL_METHOD ),以便long double86扩展精度结果张贴过。

Added by Barlop 由Barlop添加

Some additional points from Eric Postpischil Eric Postpischil的其他一些观点

OP's printf is not exact for 7.21. OP的printf并不精确到7.21。 OP's printf was passed exactly 7.20999999999999996447286321199499070644378662109375, but it printed 7.210…, so it is wrong. OP的printf正好通过7.20999999999999996447286321199499070644378662109375,但它打印了7.210 ......,所以这是错误的。 It just happens to be wrong in a way that the error in that the OP's printf exactly cancelled out the error that occurred when 7.21 was rounded to binary floating-point. 它碰巧是错误的,因为OP的printf错误确切地抵消了7.21被舍入到二进制浮点时发生的错误。

Eric's correct, printf("%.100f\\n", 7.20999999999999996447286321199499070644378662109375); Eric的正确,printf(“%。100f \\ n”,7.20999999999999996447286321199499070644378662109375); prints 7.210000000 打印7.210000000

Eric elaborates on how he knows it is 7.20999999999999996447286321199499070644378662109375 which was sent to printf and not some other long number close to that. 埃里克详细阐述了他是如何知道它是7.20999999999999996447286321199499070644378662109375,它被发送到printf而不是其他一些接近它的长号。

Eric commented that he knows that, by using a good C implementation that converts decimal numerals in source code to binary floating-point properly and that prints properly. Eric评论说,他知道这一点,通过使用一个好的C实现,可以将源代码中的十进制数字正确转换为二进制浮点并且可以正确打印。 (Apple's developer tools on macOS.) And used some Maple (math software) code he wrote to help him with floating-point arithmetic. (Apple在macOS上的开发人员工具。)并使用他编写的一些Maple(数学软件)代码来帮助他进行浮点运算。 In the absence of those, he might have had to work it out the long way, just like in elementary school but with more digits. 在没有这些的情况下,他可能不得不长时间地解决这个问题,就像在小学一样,但有更多的数字。

I will keep the accepted answer as accepted. 我会接受接受的答案。

But I will ad this answer to address the question and the other part of the question about the 839.21 但我会回答这个问题,以解决有关839.21的问题和问题的其他部分

There appears to be a bug in printf of that cygwin gcc implementation. 在cygwin gcc实现的printf中似乎存在一个错误。 Though R thought it was a ming thing, and there is a case a similar printf bug with mingw which deeper, is a bug with the Windows C/C++ runtime files. 虽然R认为这是一件很明智的事情,并且有一个类似于与mingw相似的printf bug,这是一个Windows C / C ++运行时文件的错误。 Though Chux has found that his cygwin implementation is fine, his cygwin implementation is more current. 虽然Chux发现他的cygwin实现很好,但他的cygwin实现更新。

I have found an online C compiler which doesn't have the bug so it illustrates things well. 我找到了一个没有bug的在线C编译器,所以它很好地说明了这一点。 https://www.tutorialspoint.com/compile_c_online.php https://www.tutorialspoint.com/compile_c_online.php

One of the things my question asked was why 7.21 was displaying accurately and 839.21 was displaying inaccurately, given that they have the same fraction after the decimal point. 我的问题之一是为什么7.21准确显示和839.21显示不准确,因为它们在小数点后有相同的分数。

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

It's worth looking at a printf that doesn't have the bug. 值得一看没有bug的printf。

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

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

We can see how they are stored in double 我们可以看到它们是如何存储在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

Notice that the fraction that is stored in binary, ie the number after the binary point, is very different, is very different for 7.21 than for 839.21 This is because in scientific notation, there is only one digit before the point. 请注意,以二进制形式存储的分数(即二进制点之后的数字)是非常不同的,对于7.21而言与839.21非常不同。这是因为在科学记数法中,在该点之前只有一个数字。 So even if it were just decimal numbers in scientific notation, the fraction part would be different. 因此,即使它只是科学记数法中的十进制数,分数部分也会不同。 7.21 and 8.3921 If you look at 839 in binary it is 1101000111 So you can see the 1.A there, as the first bit is 1, then for the following nibble you have 1010 which is A. Different to the case of 7.21 7.21和8.3921如果你看二进制的839它是1101000111所以你可以看到1.A那里,因为第一位是1,然后对于下面的半字节你有1010是A.不同于7.21的情况

So that's why one shouldn't expect the same result in terms of accuracy, storing 7.21 as for 839.21 所以这就是为什么人们不应该期望在准确性方面得到相同的结果,存储7.21和839.21

Chux has mentioned there is an issue in my implementation involving an internal conversion to float in the %a case. Chux提到我的实现中存在一个问题,涉及在%a情况下内部转换为float。 So it's worth looking at how they are stored in float, by doing float f; 因此,通过执行float f;值得研究它们如何存储在浮点数中float f; and passing to printf with a format specifier of %.60f and %.20a still using the mentioned working implementation of the printf. 并使用printf的上述工作实现传递给格式说明符为%.60f和%.20a的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

Now if we look at my question, Chux noticed that there is a bug that it converts the number to float in the %a case. 现在,如果我们看一下我的问题,Chux注意到有一个错误,它将数字转换为浮动的%a情况。

So for example in my early(not latest) cygwin implementation, which is what my question asks about and where a bug is, double x=7.21; printf("%a\\n", x); 所以例如在我的早期(不是最新的)cygwin实现中,这是我的问题以及错误在哪里, double x=7.21; printf("%a\\n", x); double x=7.21; printf("%a\\n", x); was printing the hex but it was showing the hex of a number stored as float. 正在打印十六进制,但它显示存储为浮点数的十六进制。 0x1.cd70a4p+2

There may be more to the bug, because the %f case may be a bit different, but anyhow, definitely a printf bug, in the early cygwin implementation gcc.exe (rubenvb-4.5.4) 4.5.4 Copyright (C) 2010 Free Software Foundation, Inc. (Whether that affects an early mingw one too I don't know). 可能还有更多的bug,因为%f的情况可能有点不同,但无论如何,绝对是一个printf bug,在早期的cygwin实现中gcc.exe(rubenvb-4.5.4)4.5.4版权所有(C)2010自由软件基金会(自由软件基金会,公司是否会影响早期的mingw,我也不知道)。

There is a related printf bug here with mingw's printf, which is down to the C/C++ runtime files Long double is printed incorrectly with iostreams on MinGW But my issue is resolved in the latest cygwin gcc that chux uses. 这里有一个与mingw的printf相关的printf错误,这个错误归结为C / C ++运行时文件。 在MinGW上使用iostreams错误地打印了long double但是我的问题在chux使用的最新cygwin gcc中解决了。 (I will try the latest cygwin to double check that). (我会尝试最新的cygwin来仔细检查)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM