2

In this code:

#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;
}

when I enter 6 as a and 10 as b, "congratulations" is printed but I think it shouldn't be printed because in this case a/b is 0.6.

Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • 1
    [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – pmg Jul 17 '21 at 08:15
  • 3
    `10/6` in binary is `0b1.10101010101010101010101010101010101010101010101010101010101010101......`. Depending on how accurate your computer approximates it, you get an approximation that is too small or too large, never the exact value – pmg Jul 17 '21 at 08:20
  • 6
    Rounding issues... Be aware that even such a simple number like `0.1` is periodic in binary representations. So *never ever* compare against exact values with floating point numbers, *always* have some epsilon considered as tolerance. Way better: Avoid floating point at all. I personally would go with `a * 10 / b > 6`. This rounds down always, if you want to round commercially try `(a * 10 + b / 2) / b`. – Aconcagua Jul 17 '21 at 08:23

2 Answers2

2

The behavior of your program depends on the implementation of the float and double types:

(float)a/b is likely computed using float arithmetics and produces a result different from 0.6, which has type double. Both may be approximations of the value six tens, which is not representable exactly using base 2 floating point systems. On your system, the former is greater than the latter so congratulations is printed, but the behavior may differ on a different system, especially one where types float and double are implemented using the same representation.

Here is a more elaborate illustration:

#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;
}

Output:

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)

As you can see, (float)a/b is exactly the same value as 0.6F, the constant with type float, and (double)a/b the same as 0.6. The C Standard does not guarantee that, but the result of the division and the constants both produce the closest approximation to the exact value for the target type. As you can see 0.6F is actually larger than six tens and 0.6 smaller.

Note also the many useful warnings produced by the compiler with -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.

As Brian Kernighan and P.J. Plauger once said, *Doing arithmetics with floating-point numbers is like moving piles of sand. Each time you do it, you lose a little sand and pick up a little dirt.

Learn more about this in David Goldberg's 1991 ACM paper What Every Computer Scientist Should Know About Floating-Point Arithmetic.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

Let's change your program very slightly. Let's make it

if ((float)a/b > 0.6666666666666)

And suppose you enter a=2 and b=3. In this case, it's pretty easy to imagine that a/b might come out as 0.6666666666667, which is greater (very slightly greater) than 0.6666666666666, so the program might print "congratulations".

Now, in your original program, you imagined this couldn't be a problem. 0.6 is a nice, even number, and 6/10 is exactly equal to 0.6, so there shouldn't be any problem, right?

Wrong!

0.6 is a "nice, even number" in decimal, base 10. But your computer uses binary, base 2.

As everyone knows, 1/3 in decimal is an infinitely-repeating fraction, 0.33333333333333... . What's not so well known is that in binary, 1/10 is an infinitely-repeating fraction, 0.00011001100110011011... . So 6/10 is 0.10011001100110011011, and it's infinitely repeating, also.

Because 1/10 is an infinitely-repeating fraction in binary, it turns out that almost any "nice, even" decimal fraction we can write -- like 1.23 or 4.56 or 7.8910 -- is not an exact fraction in binary. So we get these "roundoff errors" in our programs all the time! At one level, the solution is pretty easy: just adjust your thinking, and imagine that all fractions are the "uneven" ones, like 1/3 = 0.33333333333333.

Footnote: In binary, the only "exact" fractions are the ones that involve, not too surprisingly, powers of 1/2. So 1/2 in binary is exactly 0.1, and 1/4 is 0.01, and 21/32 is 0.10101.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103