0

In the below code, I have bits correct (it was originally bits<float> type in C++ program, but I just used uint32 in this C program.). I want to use the bits as the ieee754 float value. Assigning just float_var = int_val won't do it because it interprets the value and casts to float. I want to just use the bit values as floating point values.

    uint32 bits = mantissa_table[offset_table[value>>10]+(value&0x3FF)] + exponent_table[value>>10];
    ab_printf("bits = %x\n", bits);
    float out;
    //memcpy(&out, &bits, sizeof(float));  // original
    char *outp = &out;
    char *bitsp  = &bits;
   outp[0] = bitsp[0];
   outp[1] = bitsp[1];
   outp[2] = bitsp[2];
   outp[3] = bitsp[3];
    ab_printf("out = %x\n", out);
    return out;

part of the program run result :

ff = 3.140000
hh = 4248
bits = 40490000
out = 40092000

There must be something basic I don't know. For your information, above run is turning float 3.14 to half-precision and back to single precision and I printed the intermediate values. 0x4248 is in half-precision 3.140625 and bits 0x40490000 is in single-precision also 3.140625, so I just need to return it as float.

ADD : After reading comments and answers, I did some experiment and found that the single-float value is seen correct inside the function(using type punning using pointer, or using union), but when it is returned to the calling function, it is not printed correctly. method 0 ~ 3 all don't work. Inline function or not doesn't make any difference. There maybe another fault in our system (an embeded, bare-metal) but hope somebody could tell me what might be wrong here.(I am using part of C++ program in a C program here). (The ldexp, ldexpf didn't work).

== half.h ==

typedef unsigned short uint16;
typedef unsigned short half;
extern uint16 float2half_impl(float value);
extern float half2float_impl(half value);

== test4.c ==

#include "half.h"

int main()
{
float vflt = 3.14;
half vhlf;
float vflt2;

ab_printf("vflt = %f\n", vflt);
vhlf = float2half_impl(vflt);
ab_printf("vhlf = %x\n", *(unsigned short *)&vhlf);
float vflt2 = half2float_impl(vhlf);
ab_printf("received : vflt2 = %f\n", vflt2);
}

== half.c ==

#include "half.h"
....
inline float half2float_impl(uint16 value)
        {
            //typedef bits<float>::type uint32;
            typedef unsigned int uint32;
            static const uint32 mantissa_table[2048] = {
....
            uint32 bits = mantissa_table[offset_table[value>>10]+(value&0x3FF)] + exponent_table[value>>10];
            ab_printf("bits = %x\n", bits);

            float out;

    #define METHOD 3
    #if METHOD == 0
            memcpy(&out, &bits, sizeof(float));
            return out;
    #elif METHOD == 1
        #warning METHOD 1
            ab_printf("xx = %f\n", *(float *)&bits); // prints 3.140625
            return bits;
    #elif METHOD == 2 // prints float ok but return value float prints wrong
        #warning METHOD 2
            union {
                unsigned int ui;
                float xx;
            } aa;
            aa.ui = bits;
            ab_printf("xx = %f\n", aa.xx); // prints 3.140625
            return (float)aa.xx; // but return values prints wrong
    #elif METHOD == 3 // prints float ok but return value float prints wrong
        #warning METHOD 3
            ab_printf("xx = %f\n", *(float *)&bits); // prints 3.140625
            return *(float *)&bits; // but return values prints wrong
    #else
        #warning returning 0
        return 0;
    #endif
        }
halfer
  • 19,824
  • 17
  • 99
  • 186
Chan Kim
  • 5,177
  • 12
  • 57
  • 112
  • 1
    Haven't seen the comment before. Is there any reason you don't use `memcpy`? – muXXmit2X Aug 21 '17 at 14:05
  • 1
    Regarding your edit, of course there must have been something else wrong. If you can be sure that your `uint32_t` doesn't have padding **and** your implementation uses the IEEE754 *binary32* representation for a `float`, *type punning* through a pointer and through a union will both work, but the `union` is preferable because it doesn't violate "strict aliasing". If you can't be sure about these things, using `ldexp()` will work in any case. –  Aug 22 '17 at 07:11
  • This looks like the answer has been merged into the question, making it less useful for future readers. I suggest you roll that back to the last time it was 100% a question, and then add any further info into a self-answer. – halfer Aug 22 '17 at 15:22
  • In the absence of a reply to my comment above, despite the OP having signed in since, I have rolled back to the latest version of this question that did not contain answer material. – halfer Aug 29 '17 at 20:25

3 Answers3

5

How about using a union?

union uint32_float_union
{
    uint32_t i;
    float    f;
};

Then you can do something like

union uint32_float_union int_to_float;
int_to_float.i = bits;
printf("float value = %f\n", int_to_float.f);

Using unions for type punning is explicitly allowed by the C specification.

The memcpy way you have commented out should work to, but really breaks strict aliasing. You could use a byte-buffer as an intermediate though:

char buffer[sizeof(float)];
memcpy(buffer, &bits, sizeof(float));

float value;
memcpy(&value, buffer, sizeof(float));

Of course, all this requires that the value in bits actually corresponds to a valid float value (including correct endianness).

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • "*Using unions for type punning is explicitly allowed by the C specification.*" <- have a reference? I only find §6.2.6.1p7 "*When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.*" suggesting the opposite ... –  Aug 21 '17 at 14:24
  • @FelixPalmen From the C11 specification (ISO/IEC 9899:2011 (E)) §6.5.2.3 in footnote 95: "If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’)" – Some programmer dude Aug 21 '17 at 14:27
  • 1
    @Someprogrammerdude that's only a footnote and it explicitly warns "*This might be a trap representation.*". I wouldn't call this "*explicitly allowed*". It's a clear warning about possible UB. –  Aug 21 '17 at 14:30
  • @FelixPalmen It *could* be UB if it's a "trap representation", but if it's not then it's no problem. That's basically what the footnote says. So basically it's a note that says it's fine. Unlike C++ where text similar to this does *not* exists, and therefore it's forbidden. – Some programmer dude Aug 21 '17 at 14:31
  • @Someprogrammerdude and something that *could* be UB isn't "explicitly allowed". The footnote just tells you that *type punning* is what will happen, without somehow changing the fact that this possibly leads to UB. The only safe thing to do is reading individual bytes using `char *`. –  Aug 21 '17 at 14:33
  • @Some programmer dude - any examples of trap representations except Nan on the modern machines? Everyone here love this term `trap representation` as it sounds very serious and dangerous. But no one could give me even a single example exept 1complement numbers which are in the common use nowadays - in the computer museum – 0___________ Aug 21 '17 at 14:46
  • It is allowed to access (in terms it won't have any alignment or optimization issues), but still modifying the object through one type and then trying to reinterpret as another type is UB as long as the "another type" is not `char`. – Eugene Sh. Aug 21 '17 at 14:48
  • 1
    @Felix - The union code is ok if the bits in the object representation form valid values for both types. In this case they are also of the same size, which avoids having some unspecified parts. – Bo Persson Aug 21 '17 at 14:53
  • 1
    @PeterJ-- "any examples of trap representations": that isn't the argument; rather, whether type punning through a `union` is explicitly allowed. My read of the Standard is that this is risky in portable code, i.e., not strictly conforming, but likely to be fine on contemporary machines. – ad absurdum Aug 21 '17 at 15:05
  • @PeterJ just for completeness, I added some sketch for *portable* code in another answer. You could do the more efficient *type punning* alternatively when you **know** it's safe -- for staying portable, e.g. check `__STDC_IEC_559__` (which would still not ensure `uint32_t` doesn't have padding ... well ...) –  Aug 21 '17 at 17:03
  • @FelixPalmen the problem of trap representations you mention, will also exist for byte by byte copy? Won't it? I think that is orthogonal to the method used for punning and has to be separately ensured. – Ajay Brahmakshatriya Aug 22 '17 at 05:00
  • @Someprogrammerdude could you please elaborate on why direct `memcpy` breaks strict aliasing? And how does it change with an intermediate? – Ajay Brahmakshatriya Aug 22 '17 at 05:04
  • @AjayBrahmakshatriya reading through `char *` is always safe, `char` doesn't have trap representations (guaranteed) and you're just possibly reading *padding bits* as well. Of course, if you write to a `float` using `char *`, you could again create a trap representation, so the only thing gained is that you don't violate "strict aliasing" (same as with using a `union`). –  Aug 22 '17 at 07:08
2

This:

out = *(float *)&bits;

Allows you to read bits as a float without any explicit or implicit conversion by using pointer magic.

Notice, however, that endinaness might get you a bit screwed doing this (just like memcpy() would too, so if it worked for you this method should work too, but keep in mind that this can change from architecture to architecture).

Havenard
  • 27,022
  • 5
  • 36
  • 62
  • 1
    Notice too that this violates the infamous *strict aliasing rule* (although unlikely to cause a problem this way), while the answer using a `union` "only" bears the risk of trap representations (on "uncommon" platforms) :) –  Aug 21 '17 at 17:13
  • I tried this method and am having another problem, please see my updated question if you have time. Thanks. – Chan Kim Aug 22 '17 at 04:57
  • @FelixPalmen That's because C cant verify that you are not reading memory out of bounds if you do that, but if you know the size of the types you should be fine. – Havenard Aug 23 '17 at 06:49
  • @Havenard that's not the reason behind it. The reason is that some optimizations **rely** on the fact that pointers of different types can't alias (they can't because the language forbids that). Of course, this one line of code doesn't bear this risk, still it violates the rule :) –  Aug 23 '17 at 07:12
  • @FelixPalmen As far as I could research the only implication of doing that is with memory boundary and endianness, and Intel architecture won't have problems with the later, since it uses little-endian specifically to make those type conversions simpler and faster. I can't find any reference about that breaking optimization. After all, in a sense all pointers are the same. Also, you will find out that libraries like `socket.h` use this kind of pointer conversion extensively without giving a damn. – Havenard Aug 23 '17 at 19:29
  • @FelixPalmen It's just a matter of knowing that by doing that you are taking away from the compiler the ability to control the things I mentioned above, so you have to know what you are doing. – Havenard Aug 23 '17 at 19:29
1

If you can be sure that the value bits of an uint32_t contain exactly the bit pattern of a IEEE754 binary32, you can "construct" your float number without requiring your uint32_t not to contain padding or your float actually conforming to IEEE754 (IOW, quite portably), by using the ldexp() function.

Here's a little example .. note it doesn't support subnormal numbers, NaN and inf; adding them is some work but can be done:

#include <stdint.h>
#include <math.h>

// read IEEE754 binary32 representation in a float
float toFloat(uint32_t bits)
{
    int16_t exp = (bits >> 23 & 0xff) - 0x96;
    // subtracts exponent bias (0x7f) and number of fraction bits (0x17)

    int32_t sig = (bits & UINT32_C(0x7fffff)) | UINT32_C(0x800000);
    if (bits & UINT32_C(0x80000000)) sig *= -1;

    return ldexp(sig, exp);
}

(you could do something similar to create a float from an uint16_t containing a half precision representation, just adapt the constants for selecting the correct bits)