6

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[].

8 Answers8

2

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.

caf
  • 233,326
  • 40
  • 323
  • 462
  • Hi, thanks for the solution. But that won't work either, because my array already has another type! In the original problem I'm trying to solve I do another calculation (some bit masking and shifting) with this value again. – Arnaud Gouder de Beauregard Aug 23 '11 at 07:18
  • @Arnaud Gouder: I think, if you want to do calculations on the resulting value, that you are sunk as far as C goes - you will have either have to do those calculations at runtime, use generated C source, or perhaps cobble something together with C++ templates. – caf Aug 23 '11 at 11:33
1

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.

MByD
  • 135,866
  • 28
  • 264
  • 277
  • This does not answer the question. As I stated above I know that this won't work in a #define. – Arnaud Gouder de Beauregard Aug 23 '11 at 07:05
  • Yes it has. The *reason* that I'm using #defines is because I want to pass constants to the #define, which in term is used in the initialization of an array (so these values have to be constants). – Arnaud Gouder de Beauregard Aug 23 '11 at 07:08
  • Please re-read my original question. I *realize* that this is not related to the #define itself! My main goal is to use this 'cast' in the initialization of an array, and since I want to use it for every element of the array, I don't want to type the same thing over and over again, and *therefore* I want to use a #define. However, the main problem is that I want to convert a float *number/constant* to a unsigned long, while keeping the internal representation! – Arnaud Gouder de Beauregard Aug 23 '11 at 07:16
  • 1
    @MByD: `lvalue` in C means something that has a location in memory, not necessarily assignable. Array objects are lvalues in C, yet array objects are not assignable. – AnT stands with Russia Aug 23 '11 at 07:25
  • @Arnaud If you were using C++, I think you could use a constructor...? `#define f2u(f) *((unsigned long*)&float(f))` – Mateen Ulhaq Aug 23 '11 at 07:26
  • @Arnaud In fact, I think if you want this degree of control, you *should* be using C++, not C. – Mateen Ulhaq Aug 23 '11 at 07:48
  • About C++: I agree, but sometimes you don't have that option :-) – Arnaud Gouder de Beauregard Aug 23 '11 at 08:43
1

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.

glglgl
  • 89,107
  • 13
  • 149
  • 217
1

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
Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • Thanks. This is exactly what I was looking for, and the 'union' way of doing this is definitely the cleanest too. Thanks a lot! – Arnaud Gouder de Beauregard Aug 23 '11 at 08:37
  • Oops, it still doesn't work... As I stated above, I wanted to use this in a (global) array. Now I get the error: 'initializer element is not constant' – Arnaud Gouder de Beauregard Aug 23 '11 at 08:48
  • @Arnuad: hmm, seems it can't fold it correctly at global scope :(, only option I can see is to initialize it at run time, in which case you can use any of the given methods here, or use the array at a local scope, but having a `#define` to create it, so its constant throughout – Necrolis Aug 23 '11 at 09:31
  • Love the solution; have a vote. Does not work on all compilers and violates C99 standards. See: https://stackoverflow.com/questions/43050819/error-119-cast-to-type-is-not-allowed-when-using-armcc – trindflo Nov 02 '21 at 17:47
0

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;
glglgl
  • 89,107
  • 13
  • 149
  • 217
0

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
$
Kaos
  • 984
  • 7
  • 17
-1

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.

Kevin Lacquement
  • 5,057
  • 3
  • 25
  • 30
  • Hi, I know about the fact that this is not very portable. However, it will work for the platform that I'm using, so that's good enough. I'm also afraid that there no way to do this using a macro, that's the reason I'm asking it here :-) – Arnaud Gouder de Beauregard Aug 23 '11 at 07:06
-1

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765