Floating point to integer casting

A

Albert

On pg 45 of K&R, the authors write that:

float to int causes truncation of any fractional part.

Then shouldn't:

#include <stdio.h>

int main(void)
{
float a = 1.0000;
int b = a;
printf("%d %d\n", a, b);
return 0;
}

give 1 1 as the output instead of 0 and garbage?
 
A

Alexander Bartolich

Albert said:
On pg 45 of K&R, the authors write that:

float to int causes truncation of any fractional part.
Correct.

Then shouldn't:

#include <stdio.h>

int main(void)
{
float a = 1.0000;
int b = a;
printf("%d %d\n", a, b);
return 0;
}

give 1 1 as the output instead of 0 and garbage?

The C compiler does not look at the contents of the first argument
to determine type information. All it has is the declaration of the
function. In your case

int printf(const char *format, ...);

There are special rules how arguments are converted (the specific
term is "promoted") when passed as part of "...". In your case
the float is converted to a double. Function printf then interprets
the bits of a double as bits of an int and outputs garbage.

To test truncation you have to specify the intended conversion
manually. For example like this

int c = b;
printf("%d %d\n", a, c);

or like this:

printf("%d %d\n", a, (int)b);

--
 
A

Albert

Alexander said:
<snip>
To test truncation you have to specify the intended conversion
manually. For example like this

int c = b;
printf("%d %d\n", a, c);

or like this:

printf("%d %d\n", a, (int)b);

I still get garbage with either of the modifications.
 
J

jacob navia

Albert a écrit :
I still get garbage with either of the modifications.

Of course you get garbage. Why do you expect anything else?

Please review in your documentation which specifier is used
with floating point data within printf.

You see now?
 
B

bartc

Albert said:
I still get garbage with either of the modifications.

I think he meant (int)a, ie. convert the float a to the int value compatible
with the first %d format.
 
B

Ben Bacarisse

Albert said:
I still get garbage with either of the modifications.

The advice was wrong (I think Alexander misread the types of a and b).
You just need the right format specifier for each argument to printf:

printf("%g %d\n", a, b);

(or %f if you prefer). Slightly confusingly, floats are converted to
double when they appear as an argument to a function like printf so %g
and %f are for printing argument expressions of type double.
 
A

Albert

jacob said:
Please review in your documentation which specifier is used
with floating point data within printf.

You see now?

No, because I don't want output to be formatted as floating point
data, I want integer values for both a and b. I don't understand why
formatting a with %d works but casting a to an integer b doesn't work
looking at the output for b.
 
B

Ben Bacarisse

Albert said:
No, because I don't want output to be formatted as floating point
data, I want integer values for both a and b. I don't understand why
formatting a with %d works but casting a to an integer b doesn't work
looking at the output for b.

As Richard has said, printf does not convert its arguments, it simply
interprets them. Interpreting the bits of a floating point number as
an integer is not the same as converting the number to int. This is
the operational description of what is wrong with your program.

There is another way to look at it. Passing anything but an int as
the argument to printf's %d format is "undefined behaviour"[1] -- all
bets are off. Anything could happen. This is a better way to think
about C if you need write robust and portable code. Understanding
what actually happens on some system or other can be interesting, but
it can lead you astray if you don't know what is assured by the C
language and what is not.

For example, I've used a system where you could inspect the
representation of a double like this:

printf("%04x%04x\n", 3.14);

but the fact that is worked was an accident and it might have stopped
working simply by using a different version of the same compiler, or
even different compile-time options with the same version. However,
just switching to:

printf("%a\n", 3.14);

is usually much more helpful and portable to all C99 implementations.

If you need to inspect the representation in more detail, you can do
so with a character pointer. For example:

double f = 3.14;
int i;
unsigned char *rep = (void *)&f;
for (i = 0; i < sizeof f; i++)
printf("%02x", rep);

does in a much more portable way what the double %04x hack tries to
do.

[1] Lots of things get converted to int when passed to printf so you
need to know about these automatic conversions to be sure of what
works and how. A good C text will discuss this at some length.
 
C

chad

No. Here's what's going on in int b = a:

The = operator takes the value of its right operand, which is 1.0F
(float), and stores this value in its left operand. Since the left
operand is an int, a conversion is performed on the value yielded by
the left operand, to coerce that value into the proper type. So b
gets an int value - but a remains a float. The conversion in no way
affects the value stored in a, or the type of a.

The printf doesn't do any conversions into int type, so it's not
surprising you get garbage when you treat a non-int as if it were an
int. If you are surprised, it may be because you think of %d as a
sort of type conversion utility. It isn't.

Maybe it's just because I just got out of bed, but I don't see how b
gets an int value, but remains a float. Does this mean that b is both
an int and a float at the same time? Or is one type just a subset of
another type.
 
K

Keith Thompson

chad said:
Maybe it's just because I just got out of bed, but I don't see how b
gets an int value, but remains a float. Does this mean that b is both
an int and a float at the same time? Or is one type just a subset of
another type.

No, it's actually simpler than you're making it out to be.

a is of type float, and it can only hold a float value.
b is of type int, and it can only hold an int value.

In the declaration
int b = a;
the float value that's stored in a is *converted* from float to int,
yielding the int value 1. That int value is then stored in b.

The following statement:
printf("%d %d\n", a, b);
fails because the %d format requires an int argument; by passing
the value of a, which is a float value, the call lies to printf.

(The failure of the printf call can take any form. The behavior is
undefined, so anything can happen. In practice, the most Likely
result is that some garbage value is printed, but it could crash
the program.)
 
J

James Kuyper

chad said:
....
Maybe it's just because I just got out of bed, but I don't see how b
gets an int value, but remains a float.

Re-read. He said that 'a' remains a float; he wasn't talking about 'b'.
 
C

chad

No, it's actually simpler than you're making it out to be.

a is of type float, and it can only hold a float value.
b is of type int, and it can only hold an int value.

In the declaration
    int b = a;
the float value that's stored in a is *converted* from float to int,
yielding the int value 1.  That int value is then stored in b.

The following statement:
    printf("%d %d\n", a, b);
fails because the %d format requires an int argument; by passing
the value of a, which is a float value, the call lies to printf.

(The failure of the printf call can take any form.  The behavior is
undefined, so anything can happen.  In practice, the most Likely
result is that some garbage value is printed, but it could crash
the program.)

--

Why does b print some garbage value? I mean, the type for b is an int
and the second %d in printf() is also an int. Is this because he tried
to pass float as an int in the first arg to printf()?
 
K

Keith Thompson

chad said:
Why does b print some garbage value? I mean, the type for b is an int
and the second %d in printf() is also an int. Is this because he tried
to pass float as an int in the first arg to printf()?

Given that a is a float and b is an int, the behavior of

printf("%d %d\n", a, b);

is undefined. That doesn't mean the implementation can print anything
it likes for a, but must still get b and everything else right. Once
undefined behavior has occurred, all bets are off; the standard no
longer says *anything* about how the program behaves.

As far as the standard is concerned, that's all that there is to be
said about it. But examining the behavior on some particular system
can be useful as an *example* of how undefined behavior can manifest
itself.

Here's the original program, with the variable names changed for
clarity:

#include <stdio.h>

int main(void)
{
float f = 1.0000;
int i = f;
printf("%d %d\n", f, i); /* undefined behavior */
return 0;
}

On my system, I get a compile-time warning:

c.c:7: warning: format `%d' expects type `int', but argument 2 has type `double'

The compiler isn't required to warn about this, but it's kind enough
to do so anyway.

The run-time output is:

0 1072693248

Here's what I think is happening.

On the call to printf, the float value of f is promoted (implicitly
converted) to double. The int value of i is passed as an int. On my
system, float is 4 bytes, double is 8 bytes, and int is 4 bytes.

Arguments to printf are (I think) passed consecutively on the stack;
printf then uses the <stdarg.h> mechanism, or something very much like
it, to extract the argument values as directed by the format string.
The call passes 12 bytes of data, 8 for the double value (promoted from
float) and 4 for the int value. The "%d %d\n" format instructs printf
to extract 8 bytes of data, 4 for each of two expected int arguments.

The double representation of 1.0, when interpreted as a pair of 32-bit
ints, happens to look like (0, 1072693248). (Note that 1072693248 is
0x3ff00000 in hex. Consult the IEEE floating-point standard to see if
this matches; I haven't checked it myself, but it's certainly
plausible.) The passed int value of i is quietly ignored, because
it's beyond the 8 bytes of data specified by the format string.

When I modify the program as follows:

#include <stdio.h>

int main(void)
{
float f = 1.0000;
int i = 42;
printf("%d %d %d\n", f, i); /* undefined behavior */
return 0;
}

I get even more warnings:

c2.c:7: warning: format `%d' expects type `int', but argument 2 has type `double'
c2.c:7: warning: too few arguments for format

and the following run-time output:

0 1072693248 42

I passed 12 bytes of data (a double and an int), and the format
string specifies 12 bytes of data (3 ints). So, at run time,
everything is, in some strange sense, consistent, and we see the
actual value of i.

Do not, I repeat, *DO NOT*, use techniques like this for real-world
code. I wrote this purely for the purpose of exploring the internal
workings of printf on my system. It could work entirely differently
on a different system. For example, doubles and ints might be
passed as arguments using different mechanisms.

If you really want to examine the internal contents of a
floating-point object, there are ways to do that; the safest is to
alias it with an array of unsigned char.

The format string in a call to printf should *always* match the
(promoted) arguments. If it doesn't, you might or might not get
a warning from your compiler, and the actual behavior could be
almost literally anything. The behavior I've shown on my system
is relatively benign, and it still took several paragraphs just to
describe it.

Understanding this kind of system-specific behavior can be very
useful for debugging. For example, if you see an output value
of 1072693248 where you expected a small int value, it's likely
that something other than an integer is somehow being interpreted
as if it were of an integer. If a pointer value, printed with
"%p", looks like "0x00000003", it's probably a small integer being
misinterpreted as a pointer value.

But the point of such analysis is to fix the problem; it's almost
never a good idea to take advantage of it.

Here's another example of undefined behavior:

#include <stdio.h>

int main(void)
{
double f = 0.0;
long n = f;
printf("%d %d\n", f, n); / *undefined behavior */
return 0;
}

Again, I've lied to printf about the arguments I'm passing to it, and
any behavior is permitted. But here's the output I get on my system:

0 0

Types double and long just happen to be the same size, and 0.0 and 0L
just happen to have the same representation, all-bits-zero. So the
output *looks* correct even though it's garbage and even though it
might be different on a different system.

This is the worst way that undefined behavior can manifest itself.
There's a serious bug in the program, but it's very difficult to
detect. It will probably show itself at the worst possible time, when
the values of f and n have been changed, or when the program is run on
the intended target system rather than on my test system. Fortunately
gcc warns about this:

c3.c:7: warning: format `%d' expects type `int', but argument 2 has type `double'
c3.c:7: warning: format `%d' expects type `int', but argument 3 has type `long int'

but there are plenty of similar errors for which it can't or won't
issue a warning.
 
K

Keith Thompson

Ted DeLoggio said:
The output of this program on my machine is:

0 1072693248

If float is 32 bits, IEEE 754 specifies 1 sign bit, 8 exponent bits, and
23 fraction bits.

The value 1.0000 should be:

sign = 0
exponent = 127 (0 + 2^(8-1) bias)
fraction = 0 (+1)

Which, if interpreted as an integer would be 1065353216 (3F800000h). The
output of the above program is 1072693248 (3FF00000h), which, if I
haven't made an error in my arithmetic, is the floating-point value 1.875.

Can anyone explain this discrepancy? Where did the extra fraction bits
(0.875) come from?

First off, the behavior is undefined; the language itself has nothing
to say about the output you're seeing.

The particular results you're seeing on your system, with whatever
compiler and options you're using and the current phase of the moon,
are likely to have something to do with the fact that float is
promoted to double when passed to a variadic function as an argument
corresponding to the "..." in the function's prototype.

But really, the program is just wrong. You can probably learn
something from its behavior, but in most cases your time is better
spent fixing the code.
 
A

Anand Hariharan

No. Here's what's going on in int b = a:

The = operator takes the value of its right operand, which is 1.0F
(float), and stores this value in its left operand. Since the left
operand is an int, a conversion is performed on the value yielded by
the left operand, to coerce that value into the proper type. So b
gets an int value - but a remains a float. The conversion in no way
affects the value stored in a, or the type of a.

My English isn't as good as yours, but did you by any chance possibly
mean to say "a conversion is performed on the value yielded by the
_RIGHT_ operand" or "a conversion is performed on the value yielded
_TO_ the left operand" [emphasis mine]? This might explain Chad's
confusion. Or perhaps it's my comprehension skills that are lacking.

- Anand
 
A

Albert

Keith said:
<snip>
Here's the original program, with the variable names changed for
clarity:

#include <stdio.h>

int main(void)
{
float f = 1.0000;
int i = f;
printf("%d %d\n", f, i); /* undefined behavior */
return 0;
}
<snip>
The run-time output is:

0 1072693248

Here's what I think is happening.

On the call to printf, the float value of f is promoted (implicitly
converted) to double. The int value of i is passed as an int. On my
system, float is 4 bytes, double is 8 bytes, and int is 4 bytes.

<snip>
The call passes 12 bytes of data, 8 for the double value (promoted from
float) and 4 for the int value. The "%d %d\n" format instructs printf
to extract 8 bytes of data, 4 for each of two expected int arguments.

The double representation of 1.0, when interpreted as a pair of 32-bit
ints, happens to look like (0, 1072693248). (Note that 1072693248 is
0x3ff00000 in hex...The passed int value of i is quietly ignored, because
it's beyond the 8 bytes of data specified by the format string.

<snip>

Thank you - that last sentence has completed my understanding of the
original problem :)
 
B

bartc

Ted DeLoggio said:
The output of this program on my machine is:

0 1072693248

If float is 32 bits, IEEE 754 specifies 1 sign bit, 8 exponent bits, and
23 fraction bits.

The value 1.0000 should be:

sign = 0
exponent = 127 (0 + 2^(8-1) bias)
fraction = 0 (+1)

Which, if interpreted as an integer would be 1065353216 (3F800000h). The
output of the above program is 1072693248 (3FF00000h), which, if I
haven't made an error in my arithmetic, is the floating-point value 1.875.

Can anyone explain this discrepancy? Where did the extra fraction bits
(0.875) come from?

The float is probably converted to double, so is 64 bits. 64 bits I believe
uses an 11-bit exponent. The mantissa is 0 because there is an implied 1 in
there.
 
B

bartc

Keith Thompson said:
First off, the behavior is undefined; the language itself has nothing
to say about the output you're seeing.

The particular results you're seeing on your system, with whatever
compiler and options you're using and the current phase of the moon,
are likely to have something to do with the fact that float is
promoted to double

I didn't see this bit when I gave my own reply. I'd only got as far as the
phase of the moon..

Sometimes it's useful to give a concrete example of why some behaviours
happen, as you did in your excellent reply to Chad earlier in the thread.

The Standard only goes on about Undefined Behaviour so much, because /it
doesn't know what machine the code is being run on/, so it can't really
comment.

On the other hand, a programmer trying to debug a piece of code usually does
know, and knowing exactly why it's not working can be useful to learn for
next time.
 
N

Nick Keighley

I didn't see this bit when I gave my own reply. I'd only got as far as the
phase of the moon..

Sometimes it's useful to give a concrete example of  why some behaviours
happen, as you did in your excellent reply to Chad earlier in the thread.

yes, I was contemplating doing one but Keith's was far better.
Sometimes we need to know what is going on under the hood.
Particularly
when we are learning. I'm less interested now in exactly what the bits
are doing when UB is invoked but I used to be interested.
The Standard only goes on about Undefined Behaviour so much, because /it
doesn't know what machine the code is being run on/, so it can't really
comment.

On the other hand, a programmer trying to debug a piece of code usually does
know, and knowing exactly why it's not working can be useful to learn for
next time.

not sure about that. Once you've spotted the UB you've found the bug
and you don't need to study the implementations actual behaviour.
You just remove the UB!
 
T

Tim Rentsch

Richard Heathfield said:
On pg 45 of K&R, the authors write that:

float to int causes truncation of any fractional part.
Right.


Then shouldn't:

#include <stdio.h>

int main(void)
{
float a = 1.0000;
int b = a;
printf("%d %d\n", a, b);
return 0;
}

give 1 1 as the output instead of 0 and garbage?

No. Here's what's going on in int b = a:

The = operator takes the value of its right operand, which is 1.0F
(float), and stores this value in its left operand. Since the left
operand is an int, a conversion is performed on the value yielded by
the
operand, to coerce that value into the proper type. [snip]​


A minor point -- assignment _always_ performs a conversion,
whether the types of the two sides are the same or different.​
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top