为什么 > 运算符不能正常工作?

时间:2021-07-17 08:08:46

标签: c operators

在这段代码中:

#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。

2 个答案:

答案 0 :(得分:2)

您的程序的行为取决于 floatdouble 类型的实现:

(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.6Ffloat 的值完全相同,类型为 (double)a/b 的常量,而 0.60.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。

相关问题