-->

Convert float to unsigned long to access float int

2019-04-09 05:18发布

问题:

I want to convert a float to a unsigned long, while keeping the binary representation of the float (so I do not want to cast 5.0 to 5!).

This is easy to do in the following way:

float f = 2.0;
unsigned long x = *((unsigned long*)&f)

However, now I need to do the same thing in a #define, because I want to use this later on in some array initialization (so an [inline] function is not an option).

This does not compile:

#define f2u(f) *((unsigned long*)&f)

If I call it like this:

unsigned long x[] = { f2u(1.0), f2u(2.0), f2u(3.0), ... }

The error I get is (logically):

lvalue required as unary ‘&’ operand

Note: One solution that was suggested below was to use a union type for my array. However, that's no option. I'm actually doing the following:

#define Calc(x) (((x & 0x7F800000) >> 23) - 127)
unsigned long x[] = { Calc(f2u(1.0)), Calc(f2u(2.0)), Calc(f2u(3.0)), ... }

So the array really will/must be of type long[].

回答1:

You should probably use a union:

union floatpun {
    float f;
    unsigned long l;
};

union floatpun x[3] = { {1.0}, {2.0}, {3.0} };

or perhaps:

union {
    float f[3];
    unsigned long l[3];
} x = { { 1.0, 2.0, 3.0 } };

(The latter will let you pass x.l where you need an array of type unsigned long [3]).

Of course you need to ensure that unsigned long and float have the same size on your platform.



回答2:

Note: One solution that was suggested below was to use a union type for my array. However, that's no option. I'm actually doing the following

#define Calc(x) (((x & 0x7F800000) >> 23) - 127)
unsigned long x[] = { Calc(f2u(1.0)), Calc(f2u(2.0)), Calc(f2u(3.0)), ... }

So the array really will/must be of type long[].

In this case you won't probably be able to omit a step in-between.

unsigned float x[] = { 1.0, 2.0, 3.0, ...};
unsigned int y[sizeof x/sizeof x[0]];
for (i=0; i<sizeof x/sizeof x[0]; i++) {
    y[i] = Calc(f2u(x[i]));
}

I admit it is not very elegant. But if you run into memory difficulties because of that (embedded sytem?), you can do this separately and automatically create a source file with the correct array.


EDIT:

Yet another solution would be to tell the compiler what you really want. Obviously, you want to calculate the exponent of a floating point number. So you could just do

#define expo(f) ((long)(log((f)) / log(2)))

That seems exactly to do what you intend to do. And it seems to me that a signed char woud be enough, and if not, a int16_t.



回答3:

lvalue means something assignable. 1.0 is a constant, not a variable, and you cannot get reference to it (neither assign to it).

Meaning:

This:

unsigned long x[3] = { f2u(1.0), f2u(2.0), f2u(3.0) }

Is actually:

unsigned long x[3] = { *((unsigned long*)&1.0, *((unsigned long*)&2.0, *((unsigned long*)&3.0 }

and 1.0, 2.0 and 3.0 has no address.

The problem is not related to #define as define is a simple substitution, This code is invalid as well:

unsigned long x = *((unsigned long*)&1.0;

The problem is that you are trying to reference to immediate values, which have no address.



回答4:

If it is only this array where you need that, another approach could be

float x[3] = { 1.0, 2.0, 3.0 };
unsigned long * y = (unsigned long*)&x;


回答5:

Why not simply run a init function on the data yourself. You can update the unsigned long table with your calculations during runtime rather then compile time.

#include <stdio.h>

#define Calc(x) (((x & 0x7F800000) >> 23) - 127)

float f[] = {1.0, 2.0, 3.0, 5.0, 250.0, 300.5};
unsigned long *l = (unsigned long *)f;

int main(int argc, const char *argv[])
{
    int i;

    for (i = 0; i < sizeof(f) / sizeof(f[0]); i++)
    {
        printf("[%d] %f %p", i, f[i], l[i]);
        l[i] = Calc(l[i]);
        printf(" | %f %p\n", f[i], l[i]);
    }

    return 0;
}

Sample output:

Andreas Stenius@Neo /opt
$ gcc float2long.c && ./a.exe
[0] 1.000000 0x3f800000 | 0.000000 0x0
[1] 2.000000 0x40000000 | 0.000000 0x1
[2] 3.000000 0x40400000 | 0.000000 0x1
[3] 5.000000 0x40a00000 | 0.000000 0x2
[4] 250.000000 0x437a0000 | 0.000000 0x7
[5] 300.500000 0x43964000 | 0.000000 0x8

Andreas Stenius@Neo /opt
$


回答6:

following along @caf's answer, you can use a union:

#define F2L(x) ((union{float f;unsigned long l;})(x)).l

int main(int argc, char *argv[])
{
    unsigned long array[] = {F2L(1.0f),F2L(2.0f),F2L(3.0f)};
    printf("%x %x %x\n",array[0],array[1],array[2]);
    printf("%x\n",array[1] - array[0]);  
  system("PAUSE");  
  return 0;
}

this prints (under GCC 3.4.5, old I know :(, but thats all I have where I am atm, using -O3):

3f800000 40000000 40400000
800000

and the generated asm confirms its treating them as unsigned longs:

CPU Disasm
Address   Hex dump          Command                                  Comments
004012A8  |.  C745 E8 00008 MOV DWORD PTR SS:[LOCAL.6],3F800000
004012AF  |.  B9 0000803F   MOV ECX,3F800000
004012B4  |.  BA 00004040   MOV EDX,40400000
004012B9  |.  894C24 04     MOV DWORD PTR SS:[LOCAL.13],ECX          ; /<%x> => 3F800000
004012BD  |.  B8 00000040   MOV EAX,40000000                         ; |
004012C2  |.  895424 0C     MOV DWORD PTR SS:[LOCAL.11],EDX          ; |<%x> => 40400000
004012C6  |.  C745 EC 00000 MOV DWORD PTR SS:[LOCAL.5],40000000      ; |
004012CD  |.  C745 F0 00004 MOV DWORD PTR SS:[LOCAL.4],40400000      ; |
004012D4  |.  894424 08     MOV DWORD PTR SS:[LOCAL.12],EAX          ; |<%x> => 40000000
004012D8  |.  C70424 003040 MOV DWORD PTR SS:[LOCAL.14],OFFSET 00403 ; |format => "%x %x %x
"
004012DF  |.  E8 6C050000   CALL <JMP.&msvcrt.printf>                ; \MSVCRT.printf
004012E4  |.  C70424 0A3040 MOV DWORD PTR SS:[LOCAL.14],OFFSET 00403 ; /format => "%x
"
004012EB  |.  8B55 E8       MOV EDX,DWORD PTR SS:[LOCAL.6]           ; |
004012EE  |.  8B45 EC       MOV EAX,DWORD PTR SS:[LOCAL.5]           ; |
004012F1  |.  29D0          SUB EAX,EDX                              ; |
004012F3  |.  894424 04     MOV DWORD PTR SS:[LOCAL.13],EAX          ; |<%x> => 800000
004012F7  |.  E8 54050000   CALL <JMP.&msvcrt.printf>                ; \MSVCRT.printf


回答7:

The problem here is that you're trying to take the address of a constant. Unfortunately, constants are not lvalues, and they do not have an address to take.

As far as I know, there is no way to do this using a macro. Also, if I remember correctly, the C standard does not guarantee that a long and a float will use the same number of bits, so even your original method may be unreliable on different architectures.



回答8:

The method you are trying to use is formally illegal. Pointer-based raw memory reinterpretation constitutes so called "type punning", which some compilers will detect and warn you about. Type punning in general case leads to undefined behavior in C. And this is not a theoretical undefined behavior at all, since some compilers rely on this for optimization (see strict value semantics in GCC, for example).

Another variety of type punning is using unions to reinterpered raw memory data. Using unions in that way is formally as illegal (i.e. leads to undefined behavior) as ordinary pointer-based type punning, even though some compilers openly allow it. (Update: TC3 for C99 formally allowed this use of unions.)

The most safe and legal way to inspect object of one type as object of another (unrelated) type is by using memcpy. Just copy your source object to your destination object and use/inspect the "copy" instead of the original

float f = 2.0;
unsigned long x;

assert(sizeof f == sizeof x); /* STATIC_ASSERT */
memcpy(&x, &f, sizeof x);

This, of course, is not exactly what you need in your application, since you are looking for something that will work for reinterpretation of constants in an aggregate initializer. However, you have to keep in mind that, firstly, this kind of reinterpretation (in all its forms) is only applicable to lvalues and not to immediate constants. And, secondly, all aggregate initializers in C89/90 (aggregate initializers for static objects in C99) are required to be constants, while reinterpretation does not produce constants.