问题
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 float
s 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
nan
does not have any order to keep floats
are stored as magnitude and sign bit, whileints
are twos compliment (assuming a sane architecture). So for negative values, you must flip all the bits except the sign bit- If
float
andint
does 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