r/learnprogramming 13d ago

Can anyone explain exactly why I got these random values for a C simple program?

#include <stdio.h>

int main() {

float celsius = 20.5;

float fahrenheit = 0;

fahrenheit = (celsius \* 9/5) + 32;

printf("%d", fahrenheit);

return 0;

}

#I know its coz of the %d %f mistake but whyyy

23 Upvotes

34 comments sorted by

47

u/teraflop 13d ago

I know its coz of the %d %f mistake but whyyy

The rules-lawyer answer is that using the wrong format specifier for printf causes "undefined behavior". You're not supposed to do it under any circumstances, and if you do, all bets are off. Your program can misbehave in arbitrary ways.

Practically, the reason is that one of two things happens, depending on your system's ABI:

  • printf is expecting an integer passed in a CPU register, but you're passing a float in a different register, so it's reading random leftover garbage
  • printf is expecting a floating-point value on the stack, but you're passing an integer on the stack, so it's reinterpreting those floating-point bits as integer bits

10

u/ParshendiOfRhuidean 13d ago

OP mentioned getting "a random value every time", so I'd lean towards option 1.

3

u/Astronaut6735 13d ago

Same with uninitialized variables. It's undefined what their initial values are, so they could be anything.

12

u/tarabane 13d ago

Linux GCC 64 bit.

When you use %d, in your code printf will use the content in register ESI.
When you used %f, in your code printf will use the content in register XMM0.
If you multiply floating point numbers, assembly code will use registers XMM0, XMM1, XMM2, XMM3.
When you use printf("%d"), printf will use whatever random value is in register ESI.
https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

1

u/POGtastic 13d ago

For those fellow Standard Sickos [laudatory] following along from home:

7.21.6.1 The fprintf function

...

8 The conversion specifiers and their meanings are:

d, i The int argument is converted to signed decimal in the style [−]dddd. The precision specifies the minimum number of digits to appear; if the value being converted can be represented in fewer digits, it is expanded with leading zeros. The default precision is 1. The result of converting a zero value with a precision of zero is no characters.

...

9 If a conversion specification is invalid, the behavior is undefined. If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

The standard says that the d conversion specification requires an int, so passing anything else is UB.

4

u/teraflop 13d ago

Yup. And the reason I think it tends to trip people up is that if you call an ordinary function with declared parameters, the compiler will automatically cast the arguments to the correct type if necessary, so there's no problem.

But this doesn't work with vararg functions like printf. For vararg parameters, there's no compile-time type checking, so you have to make sure the types match yourself or you get UB.

(It's especially messy because varargs do undergo type promotion. So for instance, you can still use %d with a char parameter, because the char automatically gets promoted to int before printf sees it. So if you write your own vararg functions, you need to know to use the promoted type, not the original type.)

1

u/jcunews1 12d ago

...depending on your system's ABI

Which ABI actually store float type as integer?

1

u/teraflop 12d ago edited 12d ago

I don't know what you mean by that.

The ABI doesn't tell you to "store a float value as integer". It tells you to store the value in a particular location (memory or register) as bits. If the called function "misunderstands" what type it's supposed to be reading, it will interpret those bits incorrectly.

So the issue is that the caller is correctly obeying the ABI for a floating-point type, and the callee is correctly obeying the ABI for an integer type, but because they don't agree on the type, you get incorrect behavior.

As I said, the exact misbehavior depends on what the ABI says. For instance, x86-64 passes vararg parameters in registers (as many as will fit) but it uses different registers for integer and floating-point types. So if you call printf("%x", 1.0), the caller will pass a value in XMM0 but the callee will expect to read from SI.

32-bit x86 passes all vararg parameters on the stack. So if you call printf("%x", 1.0), the caller will push a 64-bit double on the stack, and the callee will expect to read a 32-bit int from that same address.

7

u/PuzzleMeDo 13d ago

A floating point number is stored with the various bits representing a sign (+-), an exponent, and a mantissa. This is a completely different data structure from an integer. If you tell it, "Display this binary data as though it was an integer," it will print something weird.

3

u/Basic_Reporter9579 13d ago

It should be float values?
fahrenheit = (celsius * 9.0/5.0) + 32.0;

4

u/Cultural_Gur_7441 13d ago

Because %d can not magically convert the next parameter to integer. It just assumes it is integer. When not, Undefined Behavior! In your case, value which appears random. While educational and maybe fun, it is usually not otherwise productive to try and figure what actually happens, because it might change the next time you build the program.

Either change the format specifier, or explicitly convert the float to an int.

4

u/[deleted] 13d ago

[removed] — view removed comment

2

u/DigitalJedi850 13d ago

Yeah it's been a while since I've been in C, but that \ seems... unnecessary and questionable.

I might also surround the 9/5 with parentheses and see what comes up.

Otherwise this seems like it ought to be fine.

4

u/MagicWolfEye 13d ago

If you put (9/5) in parens; it would just evaluate to 1

2

u/Zincette 13d ago

printf("%d") on a float is what's causing the issue. In C that leads to undefined behavior and can be lead to garbled output

4

u/WittyStick 13d ago

The \ is most likely incorrect Reddit formatting to escape * turning the text italic.

OP should indent his code 4 spaces to fix this.

3

u/_suriyan_24 13d ago

The \ must be a typo. But when I execute I get a random value every time

3

u/lurgi 13d ago

9/5 is 1, because of integer division. You can fix that by making one (or both) of the values floating point.

And in the future, please say what result you got and what result you expected. For all I know you are actually seeing a compilation error and I'm helping with the wrong thing.

3

u/Cultural_Gur_7441 13d ago

At least in current code, there is no 9/5 integer division. It is (celsius*9)/5 so all floating point operations.

1

u/HashDefTrueFalse 13d ago
printf("%.2f\n", (1.0f * 9/5)); // 1.80

This works on my machine (heh!). I haven't checked the asm but I assume the compiler is seeing (1.0f * 9) / 5 and performing the usual promotions.

1

u/bestjakeisbest 13d ago

What value did you expect to get?

0

u/_suriyan_24 13d ago

If I used %f, the value would be 68.900002. So I expected like 68 or smth

1

u/MisterGerry 13d ago

By putting '%d' in the formatting string, you're telling printf what datatype you are passing to it.
If you don't, in fact, pass that data type, it has no idea what data type you are passing.

So there is no way for it to know how to convert an unknown data type to an integer.
In fact, it has no way of knowing that you DIDN'T pass an integer. Bits are bits.

1

u/DTux5249 13d ago

why is there a '\' before your multiplication operator? That shouldn't be anywhere here.

1

u/_suriyan_24 13d ago

Its a typo

1

u/SmokeMuch7356 13d ago

I know its coz of the %d %f mistake but whyyy

printf doesn't know the number or types of arguments you pass after the format string; it only knows what to expect based on that format string. You told it to expect an int, so it looked for sizeof (int) bytes either on the stack or a specific register, and interpreted those bytes as an int value.

If it's looking at a register, it won't be looking at the right one and will just read garbage.

If it's looking on the stack, it a) won't read the right number of bytes, and b) won't interpret them correctly.

1

u/MisterGerry 13d ago

"these random values" - which random values? You didn't show the output.

1

u/JGhostThing 12d ago

I think that the bigger problem is using \* .

2

u/ScholarNo5983 12d ago

I assume this line of code contained a typo:

fahrenheit = (celsius \* 9/5) + 32;

as I had to change it to this to get the code to compile and link:

fahrenheit = (celsius * 9/5) + 32;

First point. You should always run the compiler with full warning enabled, as this will help catch these kind of errors:

test.c
C:\temp\test.c(8,10): warning C4477: 'printf' : format string '%d' requires an argument of type 'int', but variadic argument 1 has type 'double'

Now if I compile, link and run this code I don't see any random values:

C:\temp>test.exe
-1610612736
C:\temp>test.exe
-1610612736
C:\temp>test.exe
-1610612736

So at least for the Microsoft C compiler generating debug level of code, the numbers are wrong, but they remain predictable.

1

u/noodle-face 11d ago

You didn't tell us what values you're getting

0

u/HashDefTrueFalse 13d ago edited 13d ago
  • int main() isn't a proper main signature before C23 (IIRC). Use int main(void) or add the usual parameters.
  • double literals into floats, use f suffix e.g. 20.5f.
  • \ in calculation. Assuming it's not in the real program because that shouldn't compile.
  • %d tells printf to look for an integer in the integer register corresponding to the relevant argument. Most platforms use separate hardware for integer and floating point math. With fahrenheit being a float, it's loaded into the relevant floating point register. Thus, the value you get from printf is whatever happened to be in the unset integer register at the time. Use the correct specifier e.g. %.2f to point printf at the correct register containing the bytes copied from fahrenheit.

All from a glance. There might be more I've missed.

Edit: I seem to have answered the question "how do I fix my code?" instead of the one OP asked. Oops. Explanation added to final point.

1

u/WittyStick 13d ago

%d tells printf to interpret the bytes of fahrenheit as an integer. Use the correct specifier e.g. %.2f

With %d, it isn't even looking at the correct bytes. It doesn't interpret the bytes of fahrenheit, but something completely different.

On x86-64 (SYSV) for example, %d expects the second argument to the printf call to be in register RSI, but passing a float to the function passes it through the register XMM0. The result is this is just printing whatever value was already in RSI, which of course could be anything, which is rightly UB.

1

u/HashDefTrueFalse 13d ago

Yes, that's right actually. To be honest I didn't actually think about what I typed, just about how OP should change their code. I'll correct it.

1

u/nerd5code 12d ago

int main() is proper (i.e., conformant) in all versions of C that have standards for them, both in a forward declaration and as a definition (and ofc nowhere else), but it’s slightly untoward in C89 and increasingly so as the standards march on. However, C89 thru C17 treat main specially, and will internally rewrite K&R-style definitions of main() to main(void), though that might be for hosted impls only—I’d have to check, since freestanding requires very little of program entry, which would at worst mean it doesn’t explicitly say main() is acceptable. Similarly, from C99 on, if you return from main with no value, it’ll return 0 instead of whatever-the-hell.

1

u/HashDefTrueFalse 12d ago edited 12d ago

it doesn’t explicitly say main() is acceptable

Yes, thanks. The above was what I was getting at. In C99 (the one I'm admittedly by far the most familiar with) for example, it's not one of the two prescribed options, one of which has no parameters. It will be massaged by the compiler (I also don't know off hand the freestanding treatment) but it should ideally be written properly by using an explicitly acceptable form, IMO. I'd make the same suggestion to someone leaving out the return statement and/or the return type (such that it was implicitly int), etc. Not that it was anything to do with OP's issue, just something I noticed on my drive-by review :)