Convert float to unsigned long to access float internals, in c #define

你说的曾经没有我的故事 提交于 2019-12-04 08:49:20

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.

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.

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.

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;

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
$

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

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!