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.