【问题标题】:Why does the > operator not work correctly? [duplicate]为什么 > 运算符不能正常工作? [复制]
【发布时间】:2021-09-25 19:51:22
【问题描述】:

在这段代码中:

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

【问题讨论】:

  • 10/6 二进制是0b1.10101010101010101010101010101010101010101010101010101010101010101......。根据您的计算机对其进行近似的准确程度,您得到的近似值太小或太大,而不是确切的值
  • 舍入问题...请注意,即使像0.1 这样简单的数字在二进制表示中也是周期性的。因此,从不将精确值与浮点数进行比较,总是将一些 epsilon 视为容差。更好的方式:完全避免使用浮点数。我个人会选择a * 10 / b &gt; 6。这总是四舍五入,如果你想在商业上四舍五入,请尝试(a * 10 + b / 2) / b

标签: c operators


【解决方案1】:

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

(float)a/b 可能使用float 算术计算并产生与0.6 不同的结果,double 的类型。两者都可能是值 6 个 10 的近似值,使用以 2 为底的浮点系统无法准确表示。在您的系统上,前者大于后者,因此会打印 congratulations,但在不同的系统上行为可能会有所不同,尤其是在使用相同表示形式实现类型 floatdouble 的情况下。

这里有一个更详细的说明:

#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/b0.6F 的值完全相同,float 类型的常量,(double)a/b0.6 相同。 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 中了解更多信息。

【讨论】:

    【解决方案2】:

    让我们稍微改变一下你的程序。让我们来吧

    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 为底。但是您的计算机使用二进制,基数为 2。

    众所周知,十进制的1/3是一个无限重复的分数,0.33333333333333...。不太为人所知的是,在二进制中,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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-07
      • 1970-01-01
      • 2019-08-29
      • 2014-11-20
      • 2019-01-22
      • 1970-01-01
      • 2023-04-07
      • 1970-01-01
      相关资源
      最近更新 更多