在这段代码中:
#include <stdio.h>
int main()
{
int a;
int b;
scanf("%d", &a);
printf("\n");
scanf("%d", &b);
if ((float)a/b > 0.6)
{
printf("congratulations");
}
return 0;
}
当我输入 6 作为 a 和 10 作为 b 时,会打印“congratulations”,但我认为不应该打印,因为在这种情况下 a/b 是 0.6。
答案 0 :(得分:2)
您的程序的行为取决于 float
和 double
类型的实现:
(float)a/b
很可能使用 float
算术计算并产生与 0.6
不同的结果,double
类型为 congratulations
。两者都可能是六个十位的近似值,使用基数为 2 的浮点系统无法准确表示。在您的系统上,前者大于后者,因此会打印 float
,但行为在不同的系统上可能有所不同,尤其是在类型 double
和 #include <stdio.h>
int main() {
int a, b;
printf("Enter a and b: ");
if (scanf("%d%d", &a, &b) != 2)
return 1;
printf("\na=%d, b=%d\n", a, b);
if ((float)a/b > 0.6) printf("(float)a/b > 0.6\n");
if ((float)a/b == 0.6) printf("(float)a/b == 0.6\n");
if ((float)a/b < 0.6) printf("(float)a/b < 0.6\n");
if ((float)a/b > 0.6F) printf("(float)a/b > 0.6F\n");
if ((float)a/b == 0.6F) printf("(float)a/b == 0.6F\n");
if ((float)a/b < 0.6F) printf("(float)a/b < 0.6F\n");
if ((double)a/b > 0.6) printf("(double)a/b > 0.6\n");
if ((double)a/b == 0.6) printf("(double)a/b == 0.6\n");
if ((double)a/b < 0.6) printf("(double)a/b < 0.6\n");
// printing the values (converted to double when passed to printf)
printf(" (float)a/b -> %.18g (%#a)\n", (float)a/b, (float)a/b);
printf("(double)a/b -> %.18g (%#a)\n", (double)a/b, (double)a/b);
printf(" 0.6F -> %.18g (%#a)\n", 0.6F, 0.6F);
printf(" 0.6 -> %.18g (%#a)\n", 0.6, 0.6);
return 0;
}
使用相同代表。
这是一个更详细的说明:
Enter a and b: 6 10
a=6, b=10
(float)a/b > 0.6
(float)a/b == 0.6F
(double)a/b == 0.6
(float)a/b -> 0.60000002384185791 (0x1.333334p-1)
(double)a/b -> 0.599999999999999978 (0x1.3333333333333p-1)
0.6F -> 0.60000002384185791 (0x1.333334p-1)
0.6 -> 0.599999999999999978 (0x1.3333333333333p-1)
输出:
(float)a/b
如您所见,0.6F
与 float
的值完全相同,类型为 (double)a/b
的常量,而 0.6
与 0.6F
相同。 C 标准不保证这一点,但除法的结果和常量都会产生与目标类型的精确值最接近的近似值。正如您所看到的,0.6
实际上大于六个十而 -Weverything
更小。
还要注意编译器使用 clang -O3 -funsigned-char -Weverything -Wno-padded -Wno-shorten-64-to-32 -Wno-missing-prototypes -Wno-vla -Wno-missing-noreturn -Wno-sign-co
nversion -Wno-unused-parameter -Wwrite-strings -g -lm -lcurses -o sixtens sixtens.c
sixtens.c:12:17: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
if ((float)a/b > 0.6) printf("(float)a/b > 0.6\n");
~~~~~~~~^~ ~
sixtens.c:13:20: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
if ((float)a/b == 0.6) printf("(float)a/b == 0.6\n");
~~~~~~~~~~ ^ ~~~
sixtens.c:13:17: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
if ((float)a/b == 0.6) printf("(float)a/b == 0.6\n");
~~~~~~~~^~ ~~
sixtens.c:14:17: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
if ((float)a/b < 0.6) printf("(float)a/b < 0.6\n");
~~~~~~~~^~ ~
sixtens.c:16:20: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
if ((float)a/b == 0.6F) printf("(float)a/b == 0.6F\n");
~~~~~~~~~~ ^ ~~~~
sixtens.c:19:21: warning: comparing floating point with == or != is unsafe [-Wfloat-equal]
if ((double)a/b == 0.6) printf("(double)a/b == 0.6\n");
~~~~~~~~~~~ ^ ~~~
sixtens.c:22:53: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
printf(" (float)a/b -> %.18g (%#a)\n", (float)a/b, (float)a/b);
~~~~~~ ~~~~~~~~^~
sixtens.c:22:65: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
printf(" (float)a/b -> %.18g (%#a)\n", (float)a/b, (float)a/b);
~~~~~~ ~~~~~~~~^~
sixtens.c:24:45: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
printf(" 0.6F -> %.18g (%#a)\n", 0.6F, 0.6F);
~~~~~~ ^~~~
sixtens.c:24:51: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
printf(" 0.6F -> %.18g (%#a)\n", 0.6F, 0.6F);
~~~~~~ ^~~~
10 warnings generated.
产生的许多有用警告:
{{1}}
正如 Brian Kernighan 和 P.J. Plauger 曾经说过的那样,*使用浮点数进行算术运算就像移动一堆沙子。每次这样做,你都会失去一点沙子,捡起一点泥土。
在 David Goldberg 1991 年的 ACM 论文 What Every Computer Scientist Should Know About Floating-Point Arithmetic 中了解更多相关信息。
答案 1 :(得分:1)
让我们稍微改变一下你的程序。加油吧
if ((float)a/b > 0.6666666666666)
假设您输入 a=2 和 b=3。在这种情况下,很容易想象 a/b 的结果可能是 0.6666666666667,它大于(略大于)0.6666666666666,因此程序可能会打印“congratulations”。
现在,在您的原始程序中,您认为这不会成为问题。 0.6是个不错的偶数,6/10正好等于0.6,应该没问题吧?
错了!
0.6 是十进制的“不错的偶数”,基数为 10。但是您的计算机使用二进制,基数为 2。
众所周知,十进制的 1/3 是一个无限重复的分数,0.333333333333333...。不太为人所知的是,在二进制中, 1/10 是一个无限重复的分数, 0.00011001100110011011... 。所以 6/10 是 0.10011001100110011011,它也是无限重复的。
因为 1/10 是一个无限重复的二进制分数,所以我们可以写出几乎任何“好的、偶数”的十进制分数——比如 1.23 或 4.56 或 7.8910——不是 一个精确的二进制分数。所以我们总是在我们的程序中遇到这些“舍入错误”!一方面,解决方案非常简单:只需调整您的想法,想象所有分数都是“不均匀”的分数,例如 1/3 = 0.33333333333333。
脚注:在二进制中,唯一的“精确”分数是那些涉及 1/2 幂的分数,这并不奇怪。所以二进制中的 1/2 正好是 0.1,1/4 是 0.01,21/32 是 0.10101。