为什么 > 运算符不能正常工作?
Why does the > operator not work correctly?
在此代码中:
#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 时,打印了“恭喜”,但我认为不应该打印,因为在这种情况下 a/b 是 0.6。
您的程序的行为取决于 float
和 double
类型的实现:
(float)a/b
可能使用 float
算术计算并产生不同于 0.6
的结果,其类型为 double
。两者都可能是值 六十 的近似值,使用基数为 2 的浮点系统无法精确表示。在您的系统上,前者大于后者,因此会打印 congratulations
,但在不同的系统上,行为可能会有所不同,尤其是在类型 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
与[=17=相同]. C 标准不保证这一点,但除法的结果和常量都会产生最接近目标类型精确值的近似值。如您所见,0.6F
实际上大于六个十,而 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.
作为 Brian Kernighan 和 P.J。 Plauger 曾经说过,*用浮点数做算术就像移动沙堆。每做一次,就丢掉一点沙子,捡起一点泥土。
在 David Goldberg 1991 年的 ACM 论文中了解更多相关信息 What Every Computer Scientist Should Know About Floating-Point Arithmetic。
让我们稍微更改一下您的程序。一起来吧
if ((float)a/b > 0.6666666666666)
假设您输入 a=2 和 b=3。在这种情况下,很容易想象 a/b 可能会出现 0.6666666666667,它比 0.6666666666666 大(稍微大一点),所以程序可能会打印“恭喜”。
现在,在您原来的程序中,您认为这不是问题。 0.6是一个很好的偶数,6/10正好等于0.6,应该没有问题吧?
错了!
0.6 是一个十进制的“漂亮的偶数”,基数 10。但是你的计算机使用二进制,base 2.
众所周知,小数的1/3是无限循环的分数,0.33333333333333...。不太为人所知的是,在二进制中,1/10 是一个无限重复的分数,0.00011001100110011011...。所以 6/10 是 0.10011001100110011011,它也是无限重复的。
因为 1/10 是一个无限重复的二进制分数,事实证明我们可以写的几乎所有“漂亮的偶数”小数——比如 1.23 或 4.56 或 7.8910——都是 not 二进制中的精确分数。所以我们的程序中总是会遇到这些“舍入错误”!一方面,解决方案非常简单:只需调整您的想法,想象 所有 个分数是“不均匀”的分数,例如 1/3 = 0.33333333333333.
脚注:在二进制中,唯一“精确”的分数是涉及 1/2 次幂的分数,这并不奇怪。所以二进制的1/2正好是0.1,1/4就是0.01,21/32就是0.10101.
在此代码中:
#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 时,打印了“恭喜”,但我认为不应该打印,因为在这种情况下 a/b 是 0.6。
您的程序的行为取决于 float
和 double
类型的实现:
(float)a/b
可能使用 float
算术计算并产生不同于 0.6
的结果,其类型为 double
。两者都可能是值 六十 的近似值,使用基数为 2 的浮点系统无法精确表示。在您的系统上,前者大于后者,因此会打印 congratulations
,但在不同的系统上,行为可能会有所不同,尤其是在类型 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
与[=17=相同]. C 标准不保证这一点,但除法的结果和常量都会产生最接近目标类型精确值的近似值。如您所见,0.6F
实际上大于六个十,而 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.
作为 Brian Kernighan 和 P.J。 Plauger 曾经说过,*用浮点数做算术就像移动沙堆。每做一次,就丢掉一点沙子,捡起一点泥土。
在 David Goldberg 1991 年的 ACM 论文中了解更多相关信息 What Every Computer Scientist Should Know About Floating-Point Arithmetic。
让我们稍微更改一下您的程序。一起来吧
if ((float)a/b > 0.6666666666666)
假设您输入 a=2 和 b=3。在这种情况下,很容易想象 a/b 可能会出现 0.6666666666667,它比 0.6666666666666 大(稍微大一点),所以程序可能会打印“恭喜”。
现在,在您原来的程序中,您认为这不是问题。 0.6是一个很好的偶数,6/10正好等于0.6,应该没有问题吧?
错了!
0.6 是一个十进制的“漂亮的偶数”,基数 10。但是你的计算机使用二进制,base 2.
众所周知,小数的1/3是无限循环的分数,0.33333333333333...。不太为人所知的是,在二进制中,1/10 是一个无限重复的分数,0.00011001100110011011...。所以 6/10 是 0.10011001100110011011,它也是无限重复的。
因为 1/10 是一个无限重复的二进制分数,事实证明我们可以写的几乎所有“漂亮的偶数”小数——比如 1.23 或 4.56 或 7.8910——都是 not 二进制中的精确分数。所以我们的程序中总是会遇到这些“舍入错误”!一方面,解决方案非常简单:只需调整您的想法,想象 所有 个分数是“不均匀”的分数,例如 1/3 = 0.33333333333333.
脚注:在二进制中,唯一“精确”的分数是涉及 1/2 次幂的分数,这并不奇怪。所以二进制的1/2正好是0.1,1/4就是0.01,21/32就是0.10101.