2
float sqrt_approx(float z) {
    int val_int = *(int*)&z; /* Same bits, but as an int */
    /*
     * To justify the following code, prove that
     *
     * ((((val_int / 2^m) - b) / 2) + b) * 2^m = ((val_int - 2^m) / 2) + ((b + 1) / 2) * 2^m)
     *
     * where
     *
     * b = exponent bias
     * m = number of mantissa bits
     *
     * .
     */

    val_int -= 1 << 23; /* Subtract 2^m. */
    val_int >>= 1; /* Divide by 2. */
    val_int += 1 << 29; /* Add ((b + 1) / 2) * 2^m. */

    return *(float*)&val_int; /* Interpret again as float */
}

I was reading a wiki article on methods of computing square root. I came to this code and starred at this line.

 int val_int = *(int*)&z; /* Same bits, but as an int */

Why are they casting z to an int pointer then dereference it? Why not directly say val_int = z; Why use pointers at all? PS: I'm beginner.

2 Answers2

6

This is called type punning. This particular usage violates strict aliasing rules

By taking the address of the float value z, and reinterpreting it as the address of an integer value, the author is trying to get access to in-memory bytes representing this float but in the convenience of a int.

It's not the same as int val_int = z; which would convert the float value to an integer, resulting in different bits in memory.

A big problem here, apart from the strict aliasing issue, is that the code makes assumptions about the size of int on any target system and the endianness. As a result, the code is not portable.

The correct way to access the bytes of z is as char array:

const uint8_t* zb = (const uint8_t*)&z;

You could then construct an appropriately-sized integer from these with the a specific endianness:

uint32_t int_val = ((uint32_t)zb[0]) |
                   (((uint32_t)zb[1]) << 8) |
                   (((uint32_t)zb[2]) << 16) |
                   (((uint32_t)zb[3]) << 24);

This is similar to a simpler call, assuming you are on a little-endian system:

uint32_t int_val;
memcpy(&int_val, &z, sizeof(int_val));

But this isn't the full picture because float endianness is standardized (at least, assuming IEEE 754 which your code is targeting) whereas int is system-dependent.

At this point, the whole example breaks down. At the fundamental level the original code is a (supposedly) fast approximation based on tricks. If you want to do these tricks "correctly", it becomes a bit of a mess.

paddy
  • 60,864
  • 6
  • 61
  • 103
  • 1
    Since `zb` is `const char *`, `zb[i]` may be negative. Then converting it to `uint32_t` will wrap, producing 1s in the high 24 bits, which ruins the value being constructed. – Eric Postpischil Jul 08 '20 at 00:27
  • Yes, I had realized this just after posting and added extra casts. – paddy Jul 08 '20 at 00:27
  • 1
    Just make `zb` `const unsigned char *` in the first place. But even this is not suitable to the situation: This code hard-codes the endianness, making a worse assumption than the original code. The original code worked with big endian or little endian, as long as `int` and `float` had the same endianness. A simple `memcpy(&val_int, &z, sizeof val_int);` suffices. – Eric Postpischil Jul 08 '20 at 00:29
  • Ahh fair point. I think I confused myself along the way. Normally when dealing with bits I always work with char. However, using `memcpy` still breaks down because `float` endianness is not necessarily the same as `int`. – paddy Jul 08 '20 at 00:30
3

What happens is that the line int val_int = *(int*)&z reinterprets the float's bits as integers or rather bitfield and operate on sign, mantissa, and exponent directly of the floating point number directly instead of relying on the processors' operations.

int val_int = z would apply conversion from float to int - a completely different operation.

Generally, such operations are ill advised as in different platforms there might be different conventions on interpretation and location of mantissa, exponent and sign. Also int may be of a different size. Also, most surely native operations are more efficient and reliable.

ALX23z
  • 4,456
  • 1
  • 11
  • 18