-2

Note: This question started with a faulty premise: the values that appeared to be 0.0 were in fact very small numbers. But it morphed into a discussion of different ways of reinterpreting the bits of one type as a different type. TL;DR: until C++20 arrives with its new bit_cast class, the standard, portable solution is memcpy.

Update 3: here's a very short app that demonstrates the problem. The bits interpreted as float always give a value of 0.0000:

#include <iostream>
int main() {

    uint32_t i = 0x04501234;  // arbitrary bits should be a valid float
    uint32_t bitPattern;
    uint32_t* ptr_to_uint = &bitPattern;

    volatile float bitPatternAsFloat;
    volatile float* ptr_to_float = &bitPatternAsFloat;

    do {
        bitPattern = i;

        memcpy( (void*)ptr_to_float, (const void*)ptr_to_uint, 4 );

        // The following 2 lines both print the float value as 0.00000
        //printf( "bitPattern: %0X, bitPatternAsFloat: %0X, as float: %f \r\n", bitPattern, *(unsigned int*)&bitPatternAsFloat, bitPatternAsFloat );
        printf( "bitPattern: %0X, bitPatternAsFloat: %0X, as float: %f \r\n", bitPattern, *(unsigned int*)&bitPatternAsFloat, *ptr_to_float );

        i++;

    } while( i < 0x04501254 );

    return 0;
}

(original post)

float bitPatternAsFloat; 
for (uint32_t i = 0; i <= 0xFFFFFFFF; i = (i & 0x7F800000) == 0 ? i | 0x00800000 : i + 1 ) 
    {
        bitPatternAsFloat = *(float*)&i;
...

The loop steps through every bit pattern, skipping those in which the float exponent field is 0. Then I try to interpret the bits as a float. The values of i look OK (printed in hex), but bitPatternAsFloat always comes back as 0.0. What's wrong?

Thinking that maybe i is held in a register and thus &i returns nothing, I tried copying i to another uint32_t variable first, but the result is the same.

I know I'm modifying the loop variable with the ternary in the for statement, but as I understand it this is OK.

Update: after reading about aliasing, I tried this:

union FloatBits { 
    uint32_t uintVersion;
    float floatVersion;
};
float bitPatternAsFloat;

// inside test method: 
union FloatBits fb;  // also tried it without the 'union' keyword

// inside test loop (i is a uint32_t incrementing through all values)
fb.uintVersion = i;
bitPatternAsFloat = fb.floatVersion;

When I print the values, fb.uintVersion prints the expected value of i, but fb.floatVersion still prints as 0.000000. Am I close? What am I missing? The compiler is g++ 6, which allows "type punning".

Update 2: here's my attempt at using memcpy. It doesn't work (the float value is always 0.0):

uint32_t i = 0;
uint32_t bitPattern;
float bitPatternAsFloat;

uint32_t* ptr_to_uint  = &bitPattern;
float*    ptr_to_float = &bitPatternAsFloat;

do {
    bitPattern = i; //incremented from 0x00000000 to 0xFFFFFFFF
    memcpy( (void*)ptr_to_float, (const void*)ptr_to_uint, 4 );
    // bitPatternAsFloat always reads as 0.0
    ...
    i++;
    } while( i );
Robert Lewis
  • 1,847
  • 2
  • 18
  • 43

1 Answers1

0

Thanks to @geza for pointing out that the "failures" were an artifact of printf not being able to show very small numbers in %f format. With %e format the correct values are displayed.

In my tests, all of the various conversion methods discussed work, even the undefined behavior. Considering simplicity, performance, and portability it seems the solution using a union is best:

uint32_t bitPattern; 
volatile float bitPatternAsFloat;

union UintFloat {
    uint32_t asUint;
    float asFloat;
} uintFloat;

do {
    bitPattern = i;  // i is loop index
    uintFloat.asUint = bitPattern;                               
    bitPatternAsFloat = uintFloat.asFloat;

An interesting question is whether the float member in the union should be declared volatile: it is never explicitly written, so might the compiler cache the initial value and reuse it, failing to notice that the corresponding uint32_t is being updated? It appears to work OK on my current compiler without the volatile declaration, but is this a potential 'gotcha'?

P.S. I just discovered [reinterpret_cast][1] and wonder if that's the best solution.

Robert Lewis
  • 1,847
  • 2
  • 18
  • 43
  • `union` trick causes undefined behaviour, regardless of `volatile`. – HolyBlackCat Aug 04 '18 at 18:36
  • @HolyBlackCat Are you referring to the rule that reading a member of a union that is not the last member written is undefined? I don't understand this restriction as it seems to obviate much of the reason for using a `union`, and I can't actually think of a use case for it. Also, it seems many compilers (like mine) do allow this "type punning"(?) – Robert Lewis Aug 04 '18 at 18:44
  • @HolyBlackCat OK, I thought of one use case: reusing a "scratchpad" area of RAM as temporary storage for different types of data, as a memory-saving tactic. Is that the whole official reason for the existence of unions? – Robert Lewis Aug 04 '18 at 18:52
  • *"reading a member of a union that is not the last member written [causes UB]"* Yes. `reinterpret_cast` solution leads to UB as well. AFAIK, the right thing is to use `memcpy`, and hope that compiler will optimize it away. In C++20 we'll have `std::bit_cast` for this purpose. *"use case for unions"* Assume you need to write a function that has to be able to return objects of different types, and you know all possible types in advance. You can return a union, along with `enum` that tells which field is currently active. (In C++ `std::variant` should be preferred, but it's a common trick in C.) – HolyBlackCat Aug 04 '18 at 20:05
  • Some compilers indeed treat union type-punning as a well-defined thing. E.g. in GCC manual there is a remark saying that while it's technically undefined, it's so widely used that they decided to allow it. – HolyBlackCat Aug 04 '18 at 20:08
  • Use `memcpy`. It is the best current (C++17) solution. Efficient, and well defined (this issue comes up a lot in SO, I think you can find a lot of information about this). reinterpret_cast is not different from C-style cast in this regard, it is UB as well. – geza Aug 04 '18 at 21:20
  • Perhaps worth noting that the C++20 documentation states explicitly that `memcpy` is the workaround until `bit_cast` is available. Seems pretty likely that it would be slower than a simple cast of pointers, though. – Robert Lewis Aug 05 '18 at 19:15