问题
I have found a lot of answers on SO focusing on converting float to int.
I am manipulating only positive floating point values. One simple method I have been using is this:
unsigned int float2ui(float arg0) {
float f = arg0;
unsigned int r = *(unsigned int*)&f;
return r;
}
The above code works well yet it fails to preserve the numeric order. By order I mean this:
float f1 ...;
float f2 ...;
assert( ( (f1 >= f2) && (float2ui(f1) >= float2ui(f2)) ) ||
( (f1 < f2) && (float2ui(f1) < vfloat2ui(f2)) ));
I have tried to use unions with the same results. Any idea? I use Homebrew gcc 5.3.0.
回答1:
The code you're using, as writen, has undefind behavior. If you want to access the representation of floats semi-portably (implementation-defined, well-defined assuming IEEE 754 and that float and integer endianness match), you should do:
uint32_t float2ui(float f){
uint32_t r;
memcpy(&r, &f, sizeof r);
return r;
}
For non-negative values, this mapping between floating point values and representation is order-preserving. If you think you're seeing it fail to preserve order, we'll need to see exactly what values you think are a counterexample.
回答2:
If f1 and f2 are floating points, and f1 <= f2, and (int)f1 and (int)f2 are valid conversions, then (int)f1 <= (int)f2.
In other words, a truncation to an integral type never swaps an order round.
You could replace float2ui with simply (int)arg0, having checked the float is in the bounds of an int.
Note that the behaviour of float to int and float to unsigned is undefined if the truncated float value is out of the range for the type.
Your current code - somehow intrepreting the float memory as int memory - has undefined behaviour. Even type-punning through a union will give you implementation defined results; note in particular that sizeof(int) isn't necessarily the same as sizeof(float).
If you are using an IEEE754 single-precision float, a 32 bit 2's complement int with no trap representation, a positive value for conversion, consistent endianness, and some allowances for the various patterns represented by NaN and +-Inf, then the transformation effected by a type pun is order preserving.
回答3:
Extracting the bits from a float using a union should work. There is some discussion if the c standard actually supports this. But whatever the standard says, gcc seems to support it. And I would expect there is too much existing code that demands it, for the compilers to remove support.
There are some things you must be aware of when putting a float in an int and keeping order.
- Funny values like
nandoes not have any order to keep floatsare stored as magnitude and sign bit, whileintsare twos compliment (assuming a sane architecture). So for negative values, you must flip all the bits except the sign bit- If
floatandintdoes not have the same endianess on your architecture, you must also convert the endianess
Here is my implementation, tested with gcc (Gentoo 6.4.0-r1 p1.3) 6.4.0 on x64
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
union ff_t
{
float f;
unsigned char a[4];
int i;
};
int same_endianess = 0;
void
swap_endianess(union ff_t *ff)
{
if (!same_endianess)
{
unsigned char tmp;
tmp = ff->a[0];
ff->a[0] = ff->a[3];
ff->a[3] = tmp;
tmp = ff->a[1];
ff->a[1] = ff->a[2];
ff->a[2] = tmp;
}
}
void
test_endianess()
{
union ff_t ff = { ff.f = 1 };
if (ff.i == 0x3f800000)
same_endianess = 1;
else if (ff.i == 0x803f)
same_endianess = 0;
else
{
fprintf(stderr, "Architecture has some weird endianess");
exit(1);
}
}
float
random_float()
{
float f = random();
f -= RAND_MAX/2;
return f;
}
int
f2i(float f)
{
union ff_t ff = { .f = f };
swap_endianess(&ff);
if (ff.i >= 0)
return ff.i;
return ff.i ^ 0x3fffffff;
}
float
i2f(int i)
{
union ff_t ff;
if (i >= 0)
ff.i = i;
else
ff.i = i ^ 0x3fffffff;
swap_endianess(&ff);
return ff.f;
}
int
main()
{
/* Test if floats and ints uses the same endianess */
test_endianess();
for (int n = 0; n < 10000; n++)
{
float f1 = random_float();
int i1 = f2i(f1);
float f2 = random_float();
int i2 = f2i(f2);
printf("\n");
printf("0x%08x, %f\n", i1, f1);
printf("0x%08x, %f\n", i2, f2);
assert ( f1 == i2f(i1));
assert ( f2 == i2f(i2));
assert ( (f1 <= f2) == (i1 <= i2));
}
}
来源:https://stackoverflow.com/questions/52370587/converting-floating-point-to-unsigned-int-while-preserving-order