Consider these steps:
- numIand- numFare 2 objects that happen to have the same size on your architecture, namely 4 bytes.
- &numIis an expression with type- int *and its value is the address of the object- numI.
- Casting it with (float *)&numIis an expression of typefloat *with the same value. Aking to telling the compiler, this address is that of afloat.
- Dereferencing this expression *(float *)&numIproduces a value of typefloatthat depends on the actual representation ofintfor the value3. For example, on the intel core processors (little endian, 32-bit, 2s complement) the bytes at addressintIwould contain03 00 00 00.floatobjects are represented in memory on the same processor using the IEEE-756 Standard:03 00 00 00represents the very small value 1.5 x 2-148, approximately 4.2039e-45.
- Passing this value as variable argument to printffirst converts it to typedouble, with the same value andprintfconverts the value to0.000000because the format%fspecifies only 6 decimal places and no exponent. In order to get a more precise conversion, you could use%gwhich would produce4.2039e-45, or%awhich would produce the hexadecimal representation0x1.8p-148.
- The second part of the program performs the opposite conversion: the floatvalue3.0Fwhose IEEE-756 representation is00 00 40 40is reinterpreted as anint, producing the value1077936128.
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 intandfloatare architecture specific. Some systems representintwith 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 forfloatcan 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 thememcpyto a mere 2 instructions if not less.