Tonmoy
Tonmoy

Reputation: 557

Ternary operator in C

Why this program is giving unexpected numbers(ex: 2040866504, -786655336)?

#include <stdio.h> 
int main()
{
     int test = 0;
     float fvalue = 3.111f;
     printf("%d", test? fvalue : 0);

     return 0;
}

Why it is printing unexpected numbers instead of 0? should it supposed to do implicit typecast? This program is for learning purpose nothing serious.

Upvotes: 13

Views: 1196

Answers (4)

haccks
haccks

Reputation: 106112

Because you are using %d for printing a float value. Use %f. Using %d to print a float value invokes undefined behavior.


EDIT: Regarding OP's comments;

Why it is printing random numbers instead of 0?

When you compile this code, compiler should give you a warning:

[Warning] format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat]

This warning is self explanatory that this line of code is invoking an undefined behavior. This is because, the conversion specification %d specifies that printf is to convert an int value from binary to a string of decimal digits, while %f does the same for a float value. On passing the fvalue compiler know that it is of float type but on the other hand it sees that printf expects an argument of type int. In such cases, sometimes it does what you expect, sometimes it does what I expect. Sometimes it does what nobody expects (Nice Comment by David Schwartz).
See the test cases 1 and 2. It is working fine with %f.

should it supposed to do implicit typecast?

No.

Upvotes: 11

Kaz
Kaz

Reputation: 58647

When we have the expression E1 ? E2 : E3, there are four types involved. Expressions E1, E2 and E3 each have a type (and the types of E2 and E3 can be different). Furthermore, the whole expression E1 ? E2 : E3 has a type.

If E2 and E3 have the same type, then this is easy: the overall expression has that type. We can express this in a meta-notation like this:

(T1 ? T2 : T2) -> T2 
"The type of a ternary expression whose alterantives are both of the same type T2
 is just T2."

If they don't have the same type, things get somewhat interesting, and the situation is quite similar to E2 and E3 being involved together in an arithmetic operation. For instance if you add together an int and float, the int operand is converted to float. That is what is happening in your program. The type situation is:

(int ? float : int) -> float

the test fails, and so the int value 0 is converted to the float value 0.0.

This float value is not compatible with the %d conversion specifier of printf, which requires an int.

More precisely, the float value undergoes one more. When a float is passed as one of the trailing arguments to a variadic function, it is converted to double.

So in fact the double value 0.0 is being passed to printf where it expects int.

In any case, it is undefined behavior: it is nonportable code for which the ISO standard definition of the C language doesn't offer a meaning.

From here on, we can apply platform-specific reasoning why we don't just see 0. Suppose int is a 32 bit, four byte type, and double is the common 64 bit, 8 byte, IEE754 representation, and that an all-bits-zero is used for 0.0. So then, why isn't a 32 bit portion of this all-bits-zero treated by printf as the int value 0?

Quite possibly, the 64 bit double argument value forces 8 byte alignment when it is put onto the stack, possibly moving the stack pointer by four bytes. And then printf pulls out garbage from those four bytes, rather than zero bits from the double value.

Upvotes: 1

stefan
stefan

Reputation: 10355

Although the existing upvoted answers are correct, I think they are far too technical and ignore the logic a beginner programmer might have:

Let's look at the statement causing confusion in some heads:

printf("%d", test? fvalue : 0);
        ^    ^     ^        ^
        |    |     |        |
        |    |     |        - the value we expect, an integral constant, hooray!
        |    |     - a float value, this won't be printed as the test doesn't evaluate to true 
        |    - an integral value of 0, will evaluate to false
        - We will print an integer!

What the compiler sees is a bit different. He agrees on the value of test meaning false. He agrees on fvalue beeing a float and 0 an integer. However, he learned that the different possible outcomes of the ternary operator must be of same type! int and float aren't. In this case, "float wins", 0 becomes 0.0f!

Now printf isn't type safe. This means you can falsely say "print me an integer" and pass an float without the compiler noticing. Exactly that happened. No matter what the value of test is, the compiler deduced that the result will be of type float. Hence, your code is equivalent to:

float x = 0.0f;
printf("%d", x);

At this point, you experience undefined behaviour. float simply isn't something integral what is expected by %d.

The observed behaviour is dependent on the compiler and machine you're using. You may see dancing elephants, although most terminals don't support that afaik.

Upvotes: 8

David Schwartz
David Schwartz

Reputation: 182885

Most likely, your platform passes floating point values in a floating point register and integer values in a different register (or on the stack). You told printf to look for an integer, so it's looking in the register integers are passed in (or on the stack). But you passed it a float, so the zero was placed in the floating point register that printf never looked at.

The ternary operator follows language rules to decide the type of its result. It can't sometimes be an integer and sometimes be a float. Those could be different sizes, stored in different places, and so on, which would make it impossible to generate sane code to handle both possible result types.

This is a guess. Perhaps something completely different is happening. Undefined behavior is undefined for a reason. These kinds of things can be impossible to predict and very difficult to understand without lots of experience and knowledge of platform and compiler details. Never let someone convince you that UB is okay or safe because it seems to work on their system.

Upvotes: 18

Related Questions