Convert float to unsigned long to access float int

2019-04-09 05:06发布

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条回答
在下西门庆
2楼-- · 2019-04-09 05:30

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.

查看更多
Ridiculous、
3楼-- · 2019-04-09 05:33

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.

查看更多
倾城 Initia
4楼-- · 2019-04-09 05:35

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.

查看更多
smile是对你的礼貌
5楼-- · 2019-04-09 05:39

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.

查看更多
不美不萌又怎样
6楼-- · 2019-04-09 05:41

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.

查看更多
Juvenile、少年°
7楼-- · 2019-04-09 05:41

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;
查看更多
登录 后发表回答