tags:

views:

142

answers:

4

Say I want a function that takes two floats (x and y), and I want to compare them using not their float representation but rather their bitwise representation as a 32-bit unsigned int. That is, a number like -495.5 has bit representation 0b11000011111001011100000000000000 or 0xC3E5C000 as a float, and I have an unsigned int with the same bit representation (corresponding to a decimal value 3286614016, which I don't care about). Is there any easy way for me to perform an operation like <= on these floats using only the information contained in their respective unsigned int counterparts?

+2  A: 

In a word, no. IEEE 754 might allow some kinds of hacks like this, but they do not work all the time and handle all cases, and some platforms do not use that floating point standard (such as doubles on x87 having 80 bit precision internally).

If you're doing this for performance reasons I suggest you strongly reconsider -- if it's faster to use the integer comparison the compiler will probably do it for you, and if it is not, you pay for a float to int conversion multiple times, when a simple comparison may be possible without moving the floats out of registers.

Billy ONeal
x87? You mean x86 right?
Martinho Fernandes
If IEEE 754 allows hacks like this (and it does), when wouldn't it work? What cases doesn't it handle?
Gabe
@Martinho Fernandes: No, I mean x87. http://en.wikipedia.org/wiki/X87
Billy ONeal
Martinho: before the floating point units were built into x86 chips, they had designations like 8087, 80287, and 80387. So we call the FPU instructions x87.
Gabe
The original math coprocessor was a companion chip, e.g. 287 to go with 80286, 387 to go with 80386. Now the FPU is part of the main CPU core, but it still implements the instruction set of the old x87 chips.
Ben Voigt
@gabe: There are several valid doubles which do not sort perfectly like integers, such as standard representations for NaN (not a number) and positive / negative infinity. Most "normal" numbers will sort correctly under IEEE 754 if I'm not mistaken.
Billy ONeal
@gabe: what two unsigned ints a and b can you find so that none of (a < b), (a == b), (a > b) is true? Many such floats exist.
Ben Voigt
I got the impression that he was collating rather than looking for NaNs, so I think he'll be fine.
Gabe
@gabe: Most normal numbers will sort fine, yes, but I'd be willing to bet it's faster to leave them as doubles anyway so that they don't need to be copied between registers first. Comparisons are fast and there are very very very few cases where such hacks are justified when a FPU is available on die.
Billy ONeal
If you're sorting numbers, they're probably in memory, so loading them into FPU registers to do a floating-point comparison is certainly not going to be faster than doing the comparison in 32-bit integer registers.
Gabe
@gabe: Unless they're already in FPU registers. It's unlikely somebody is doing a comparison on two random from memory floats -- it's likely they have been working with at least one of the floats beforehand.
Billy ONeal
On my box, the comparisons aren't equivalent, so which way is faster seems rather moot.
Dennis Zickefoose
@Billy ONeal: If you're sorting an array of numbers, they're not just random memory floats.
Gabe
The important thing to take out of this is that there might or might not be a performance benefit to such a comparison: floating point compares are pretty quick on their own, and there might or might not be overhead setting up for the integral comparison. Add in the fact that the comparison isn't portable, and they don't work in all situations [even if you're willing to ignore NaN and the infinities, positive and negative zero are pretty easy to get wrong], and you end up with a pretty compelling argument for just trusting the compiler until you're given reason not to.
Dennis Zickefoose
+2  A: 

Maybe I'm misreading the question, but I suppose you could do this:

bool compare(float a, float b)
{
    return *((unsigned int*)&a) < *((unsigned int*)&b);
}

But this assumes all kinds of things and also warrants the question of why you'd want to compare the bitwise representations of two floats.

cpalmer
Ah, the good old coerce-through-pointers hack. Ugly, but powerful.
Martinho Fernandes
If you're going to do that at the very least use `reinterpret_cast` to mark it as the platform specific hack that it is.
Billy ONeal
@BillyONeal - the question is tagged as C, C++ and Objective-C. `reinterpret_cast` is appropriate for only C++.
R Samuel Klatchko
@R Samuel Klatchko: We have already beaten this dead horse in Dennis Zickefoose' answer. I think the C++ versions should be used even in presence of such tags, because it is easy to convert from a C++ cast to a C style cast, but not always in the reverse direction.
Billy ONeal
+3  A: 

If you truly truly don't care about what the conversion yields, it isn't too hard. But the results are extremely non-portable, and you almost certainly won't get an ordering that at all resembles what you'd get by comparing the floats directly.

typedef unsigned int TypeWithSameSizeAsFloat; //Fix this for your platform

bool compare1(float one, float two)
    union Convert {
        float f;
        TypeWithSameSizeAsFloat i;
    }
    Convert lhs, rhs;
    lhs.f = one;
    rhs.f = two;
    return lhs.i < rhs.i;
}

bool compare2(float one, float two) {
    return reinterpret_cast<TypeWithSameSizeAsFloat&>(one) 
         < reinterpret_cast<TypeWithSameSizeAsFloat&>(two);
}

Just understand the caveats, and chose your second type carefully. Its a near worthless excersize at any rate.

Dennis Zickefoose
+1 for using reinterpert_cast.
Billy ONeal
It's hard to use the reinterpret_cast in C.
Richard Pennington
@Richard Pennington: Then replace with a C style cast. But the default writing should be the correct cast rather than the legacy one whenever possible, given that C-style casts are deprecated. (Unless you absolutely MUST have C compatibility here)
Billy ONeal
@Billy ONeal The question was tagged C, C++, and objective-c. Which cast works in all cases?
Richard Pennington
@Richard Pennington: If the author wishes to use the code in the languages which do not support safe casts, then I don't think it's too hard to convert back to C-style casts. C++ style casts should be the default listing when C++ is tagged, because any C++ can be converted to a C style cast.
Billy ONeal
@Billy, he's actually asking how to do a thing that probably doesn't do what he expects anyway. It doesn't matter what cast he uses.
Richard Pennington
@Richard Pennington: *Bill spits diet coke all over his monitor*
Billy ONeal
@Bill: I hope your monitor is OK. ;-)
Richard Pennington
GMan
+1 for doing `reinterpret_cast` the clean way. :P
GMan
+1  A: 

You must do a signed compare unless you ensure that all the original values were positive. You must use an integer type that is the same size as the original floating point type. Each chip may have a different internal format, so comparing values from different chips as integers is most likely to give misleading results.

Most float formats look something like this:

sxxxmmmm

s is a sign bit
xxx is an exponent
mmmm is the mantissa

the value represented will then be something like

1mmm << (xxx-k)

1mmm because there is an implied leading 1 bit unless the value is zero.

if xxx < k then it will be a right shift. k is near but not equal to half the largest value that could be expressed by xxx. it is adjusted for the size of the mantissa.

All to say that, disregarding NaN, comparing floating point values as signed integers of the same size will yield meaningful results. They are designed that way so that floating point comparisons are no more costly than integer comparisons. There are compiler optimizations to turn off NaN checks so that the comparisons are straight integer comparisons if the floating point format of the chip supports it.

As an integer, NaN is greater than infinity is greater than finite values. If you try an unsigned compare, all the negative values will be larger than the positive values, just like signed integers cast to unsigned.

drawnonward
I agree. I whipped up some tests using the unsigned values in conjunction with the sign bits, which while kind of ugly totally works.
sczizzo