问题
When representing two float32's as int32 or uint32 (using union or casting pointer types), Within a limited range (0..1), can these values be compared against each other, that would match the result of the comparison when executed as floats?
Example:
int float_as_int(float f) {
union { int i; float f; } u;
u.f = f;
return u.i;
}
/* Assert the following is always true for values in a limited range. */
void assert_compare_works_as_expected(float a, float b) {
assert(float_as_int(a) < float_as_int(b) == a < b);
}
/* Psudocode */
int main() {
for f in 0..1 {
assert_compare_works_as_expected(f_prev, f);
f_prev = f;
}
}
If not, what float ranges map directly to ints so comparison can be done?
回答1:
The way I interpret you question: When interpreting the bit pattern of a floating point number in memory as a int, will the usual comparison operators behave the same for floating point numbers and integers.
The answer is no. Strictly speaking, the C standard has a few possible integer representations and even more possible float representations. But even limiting ourselves to what pretty much everyone does - two's complement integers and ieee754 binary32 floating point, the answer is still no.
The simplest example: floating points have two zeroes. The bit pattern 0x00000000 and the bit pattern 0x80000000 represent the numbers 0 and -0. They both compare equal as floating point, they wouldn't if interpreted as integers. Then you have two infinities, and a whole lot of bit patterns that represent NaN.
You could enumerate the special cases and avoid them and then I'm pretty sure that a comparison as integers would work, but whatever performance gain you get from integer comparisons (which I assume is the point here) you'd gain you'd lose much more in avoiding the special cases.
Responding to your last edit: Yes, in the range 0 to 1, the bit pattern for ieee754 floating point numbers is such that when interpreted as integers the normal integer comparison operators will behave the way you want. It's a bad idea, but it would work.
回答2:
The answer first
Yes, you can compare a binary representations of float
as long as they are using IEEE-754 format, integers are 2's complement, you are comparing positive values or positive value with the negative, and you are treating the special cases properly.
Snippet on Ideone.com in C
The explanation
An IEEE-754 float (4 bytes) has the following structure:
s::::::::....................... meaning
31 0 bit #
s = sign bit, : = exponent, . = mantissa
The value is coded as a mantissa * 2 ^ exponent
. The exponent range is 0x00
to 0xFF
, and it represents values -127
to 128
(0x7F
corresponds to 0
).
The mantissa represents a binary fraction with an implied 1b
always present to the left of the decimal. Each bit of mantissa represents a value of 2 ^ -(n+1)
, where n
is an index of a bit from the left. The leftmost bit of mantissa has a value of 0.5
, second on the left 0.25
, third on the left 0.125
, and so on.
The value 0.5
will have mantissa b0000...
(b1.0
) and the exponent 0x7E
(-1
).
The value 0.03
will have mantissa b1110...
(b1.1110...
) and the exponent 0x79
(-6
).
These values would be stored as:
0 01111110 00000000000000000000000 (0x3F000000)
0 01111001 11101011100001010001111 (0x3CF5C28F)
The lowest value you can store is 1.0 * 2 ^ -127
(all bits of mantissa and exponent are zero). That is a special case and represents a 0
. The exponent value 0xFF
is reserved for NaN
and infinity
.
Floating point number representation
You can compare a negative float
value with the positive if you are using signed int
because the sign flag is being stored in the highest bit for both signed int
and float
. You can not compare two negative float
values like this tho, as the negation of int
is not being done by inversion of the sign flag only.
回答3:
The long answer is: Yes, but with multiple restrictions and caveats.
Comparing floats in the range 0
to 1
as integers may yield the correct result in many cases. As a matter of fact, the values need not be restricted to this range:
Assuming the
float
type has 32-bit single precision IEEE-754 representation and theint
type has 32-bit 2's complement representation without padding bits (ieint is
int32_t`);Assuming neither of the
float
values are NaN;Assuming the values are in normal or sub-normal form;
Assuming
float
andint
representations have the same endianness in memory;if the values are equal, comparing as
int
will yield the correct result unless you are comparing+0.0
and-0.0
.if the values are not both negative, the comparison as
int
may indeed yield the correct result, but the C Standard makes no guarantee and accessing thefloat
values asint
breaks the strict aliasing rule in most circumstances.if the values are both negative and the other constraints are matched, the comparison result should be negated.
For your side question: some integer values may indeed have an identical representation as int
and float
, notably 0
, but the floating point value -0.0
has a different representation yet compares equal to 0.0
as float
. There are only two other special cases of integers that have the same representation as int32_t
and IEEE-754 float
: 1318926965
(0x4E9D3A75
) and -834214802
(0xCE46E46E
).
You can look at the actual representation of floating point values for the IEEE format, overwhelmingly common but not mandated by the C Standard here:
- IEEE 754
- Single-precision floating-point format
For your specific case, if you know the values are in the range 0
to 1
, the only special case you should handle is the negative 0
. You can for the sign as positive by masking the value and the code will look like:
/* return 0 if p1 and p2 point to the same float value,
-1 if p1 points to a smaller value and
+1 otherwise.
*/
int compare_floats_as_ints(const int32_t *p1, const int32_t *p2) {
int32_t i1 = *p1 & 0x7fffffff;
int32_t i2 = *p2 & 0x7fffffff;
return (i1 > i2) - (i1 < i2);
}
回答4:
Can 32 bit floats between zero and one be compared (with the same outcome) if they're transmuted to int/uint?
Using a union
will work for comparing float
in the range (zero and one) and if all the list below can be insured.
Otherwise the general answer is no. It might work, it might not.
union x {
float f;
int32_t i;
uint32_t u;
} a,b ;
a.f = foo();
b.f = foo();
// for all a.f and b.f in range
assert((a.u > b.u) == (a.f > b.f));
assert((a.u < b.u) == (a.f < b.f));
assert((a.u == b.u) == (a.f == b.f));
// also for a.i
float
is binary32- The integer type is not padded. (example:
(u)intN_t
) types. - The integer may be any encoding: 2's complement, 1's complement, sign-magnitude
- The integer has same endian as
float
. C does not require this. - The integer/FP type size are the same. (This was implied in the question).
This also works for the extended range of [+0.0 ... +INF]. It does not hold for -0.0.
To compare 2 any finite/infinite float
given the same endian-ness, expected float
encoding and the above conditions:
int32_t sequence_f(float x) {
union {
float f;
int32_t i32;
uint32_t u32;
} u;
assert(sizeof(float) == sizeof(uint32_t));
u.f = x;
if (u.i32 < 0) {
u.u32 = 0x80000000 - u.u32;
}
// printf("% 16.8e % 11d\n", x, u.i32);
return u.i32;
}
// return + +,0,- without FP math.
// valid for all finite/infinite values, not valid for NaN
// +0.0 -0.0 compare the same.
int compare(float fa, float fb) {
int32_t ia = sequence_f(fa);
int32_t ib = sequence_f(fb);
return (ia > ib) - (ia < ib);
}
This is useful for embedded applications that need to perform FP compare, yet not other FP math.
In general, casting is UB due to anti-aliasing.
回答5:
The answer is yes, with the qualifications that:
- float & int are the same size.
- inputs are positive, float values that don't include:
-0.0
,-inf
,nan
,-nan
.
Details:
- Both
signed int
andunsigned int
can be used with matching comparisons. - Assumes IEEE754 32bit floating point.
- Both int & float need to be the same endian (do architectures exist where this is not the case?)
- Not just
0-1
,0.0..FLT_MAX
andinf
can be compared. - For completeness, negative values:
-0.0..-FLT_MAX
can be compared too but will always have a flipped order, eg:assert(float_as_int(a) > float_as_int(b) == a < b);
In this case, 2x floats represented as ints can be compared, giving the same results.
Here is a evidence that this can work:
C code, tests the full, unsigned float range.
#include <math.h>
#include <float.h>
#include <stdio.h>
int main() {
unsigned step = 0;
union {
float f;
unsigned u;
} value;
value.f = 0.0;
unsigned u_prev = value.u;
while (value.f != FLT_MAX) {
value.f = nextafterf(value.f, FLT_MAX);
unsigned u = value.u;
if (u <= u_prev) {
printf("At value %f, step %u, comparisons don't match\n", value.f, step);
break;
}
u_prev = u;
step++;
}
printf("Tested %u times\n", step);
return 0;
}
a Python3 script, checking all values from 0-1 with nextafterf
, note that this is slow (hence the C version above).
def main():
from struct import pack, unpack
as_u32_prev = None
value = 0.0
while True:
# transmute float to int
as_u32 = unpack('I', pack('f', value))
if as_u32_prev is not None:
if as_u32_prev > as_u32:
raise Exception("f32/u32 comparisons don't match")
as_u32_prev = as_u32
if value == 1.0:
break
value = nextafterf(value, 1.0)
# Boiler Plate, see: https://stackoverflow.com/questions/6063755
import ctypes
import sys
from sys import platform as _platform
if _platform == "linux" or _platform == "linux2":
_libm = ctypes.cdll.LoadLibrary('libm.so.6')
_funcname_f = 'nextafterf'
elif _platform == "darwin":
_libm = ctypes.cdll.LoadLibrary('libSystem.dylib')
_funcname_f = 'nextafterf'
elif _platform == "win32":
_libm = ctypes.cdll.LoadLibrary('msvcrt.dll')
_funcname_f = '_nextafterf'
else:
# these are the ones I have access to...
# fill in library and function name for your system math dll
print("Platform", repr(_platform), "is not supported")
sys.exit(0)
nextafterf = getattr(_libm, _funcname_f)
nextafterf.restype = ctypes.c_float
nextafterf.argtypes = [ctypes.c_float, ctypes.c_float]
main()
Note that other answers here say that this wont work in all cases, I'd be interested to know which cases the examples in this answer would fail, (besides architectures where float and int aren't 4 bytes in size).
回答6:
As from my understanding, you want to read float number from memory as unsigned/signed integer and I will give you answer for that.
You cannot compare float
with signed
or unsigned
int (32-bit).
For example, number 4
in int
is represented as 0x00000004
while the same number 4
is represented in float (by IEEE754 standard) as 0x40800000
.
回答7:
It does not matter if you compare uint from the union with float or the float itself. Same rules.
if you have two operations:
union {
float fl;
uint32_t ui;
} a, b
float a.fl = (math expressions);
float b.fl = (another math expressions); // both giving theoretically the same results
// comparition of
if(a.fl == b.fl) .... //UB - as float shall not be compared for being equal
if(a.ui == b.ui) .... //UB - as ui is an unsigned representation of the bytes of fl
来源:https://stackoverflow.com/questions/45184955/can-32-bit-floats-between-zero-and-one-be-compared-with-the-same-outcome-if-th