Consider these steps:
numI
and numF
are 2 objects that happen to have the same size on your architecture, namely 4 bytes.
&numI
is an expression with type int *
and its value is the address of the object numI
.
- Casting it with
(float *)&numI
is an expression of type float *
with the same value. Aking to telling the compiler, this address is that of a float
.
- Dereferencing this expression
*(float *)&numI
produces a value of type float
that depends on the actual representation of int
for the value 3
. For example, on the intel core processors (little endian, 32-bit, 2s complement) the bytes at address intI
would contain 03 00 00 00
. float
objects are represented in memory on the same processor using the IEEE-756 Standard: 03 00 00 00
represents the very small value 1.5 x 2-148, approximately 4.2039e-45.
- Passing this value as variable argument to
printf
first converts it to type double
, with the same value and printf
converts the value to 0.000000
because the format %f
specifies only 6 decimal places and no exponent. In order to get a more precise conversion, you could use %g
which would produce 4.2039e-45
, or %a
which would produce the hexadecimal representation 0x1.8p-148
.
- The second part of the program performs the opposite conversion: the
float
value 3.0F
whose IEEE-756 representation is 00 00 40 40
is reinterpreted as an int
, producing the value 1077936128
.
Here is a modified version of your program that makes it more explicit:
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
int main(void) {
int numI;
float numF;
unsigned char *p;
assert(sizeof numI == sizeof numF);
numI = 3;
p = (unsigned char *)&numI;
printf("int value %d is represented in memory as %02X %02X %02X %02X\n",
numI, p[0], p[1], p[2], p[3]);
//numF = *(float *)&numI;
memcpy(&numF, &numI, sizeof numF);
printf("reinterpreted as float with format %%f: %f\n", numF);
printf("reinterpreted as float with format %%g: %g\n", numF);
printf("reinterpreted as float with format %%a: %a\n", numF);
printf("numF exact value: %g * 2^-148\n", numF * pow(2.0, 148));
numF = 3.0;
p = (unsigned char *)&numF;
printf("float value %.1g is represented in memory as %02X %02X %02X %02X\n",
numF, p[0], p[1], p[2], p[3]);
//numI = *(int *)&numF;
memcpy(&numI, &numF, sizeof numI);
printf("reinterpreted as int with format %%d: %d\n", numI);
printf("reinterpreted as int with format %%#X: %#X\n", numI);
return 0;
}
Output:
int value 3 is represented in memory as 03 00 00 00
reinterpreted as float with format %f: 0.000000
reinterpreted as float with format %g: 4.2039e-45
reinterpreted as float with format %a: 0x1.8p-148
numF exact value: 1.5 * 2^-148
float value 3 is represented in memory as 00 00 40 40
reinterpreted as int with format %d: 1077936128
reinterpreted as int with format %#X: 0X40400000
Note however that:
- These casts violate the strict aliasing rule, hence have undefined behavior.
- The representation in memory of types
int
and float
are architecture specific. Some systems represent int
with big-endian byte order, some have padding bits and trap values, some vintage systems even used one's complement or sign+magnitude representations. The representations for float
can be even more exotic, although most current systems use IEEE-756 with the same byte ordering as integers. So your program has undefined behavior again, and at best produces implementation specific output.
- The recommended method for reinterpreting the representation in memory is to copy the bytes with
memcpy
, as shown in the modified code. Modern compilers will produce very efficient code for this, expanding the memcpy
to a mere 2 instructions if not less.