5

Can two floating point values (IEEE 754 binary64) be compared as integers? Eg.

long long a = * (long long *) ptr_to_double1,
          b = * (long long *) ptr_to_double2;
if (a < b) {...}

assuming the size of long long and double is the same.

Paul R
  • 208,748
  • 37
  • 389
  • 560
  • For syntax question like this, it is very helpful to know which language and version you are planning on using. Is this C++, C, C#, Python, etc? – GKnight Nov 12 '15 at 18:40
  • Sorry I forgot to mention, C. –  Nov 12 '15 at 18:43
  • 1
    Why would you want to do this? – Jens Gustedt Nov 12 '15 at 19:29
  • 3
    strict aliasing: http://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – Pascal Cuoq Nov 12 '15 at 22:26
  • @JensGustedt I need to write a stack-based interpreter and its functionality includes comparisons of expressions. I was thinking if using the same instruction for integer and floating-point comparison, instead of having two distinct instructions. –  Nov 14 '15 at 00:39
  • @PascalCuoq values will be in a union anyway. –  Nov 14 '15 at 00:40

4 Answers4

16

YES - Comparing the bit-patterns for two floats as if they were integers (aka "type-punning") produces meaningful results under some restricted scenarios...

Identical to floating-point comparison when:

  • Both numbers are positive, positive-zero, or positive-infinity.
  • One positive and one negative number, and you are using a signed integer comparison.

Inverse of floating-point comparison when:

  • Both numbers are negative, negative-zero, or negative-infinity.
  • One positive and one negative number, and you are using a unsigned integer comparison.

Not comparable to floating-point comparison when:

  • Either number is one of the NaN values - Floating point comparisons with a NaN always returns false, and this simply can't be modeled in integer operations where exactly one of the following is always true: (A < B), (A == B), (B < A).

Negative floating-point numbers are a bit funky b/c they are handled very differently than in the 2's complement arithmetic used for integers. Doing an integer +1 on the representation for a negative float will make it a bigger negative number.

With a little bit manipulation, you can make both positive and negative floats comparable with integer operations (this can come in handy for some optimizations):

int32 float_to_comparable_integer(float f) {
  uint32 bits = std::bit_cast<uint32>(f);
  const uint32 sign_bit = bits & 0x80000000ul;
  // Modern compilers turn this IF-statement into a conditional move (CMOV) on x86,
  // which is much faster than a branch that the cpu might mis-predict.
  if (sign_bit) {
    bits = 0x7FFFFFF - bits;
  }
  return static_cast<int32>(bits);
}

Again, this does not work for NaN values, which always return false from comparisons, and have multiple valid bit representations:

  • Signaling NaNs (w/ sign bit): Anything between 0xFF800001, and 0xFFBFFFFF.
  • Signaling NaNs (w/o sign bit): Anything between 0x7F800001, and 0x7FBFFFFF.
  • Quiet NaNs (w/ sign bit): Anything between 0xFFC00000, and 0xFFFFFFFF.
  • Quiet NaNs (w/o sign bit): Anything between 0x7FC00000, and 0x7FFFFFFF.

IEEE-754 bit format: http://www.puntoflotante.net/FLOATING-POINT-FORMAT-IEEE-754.htm

More on Type-Punning: https://randomascii.wordpress.com/2012/01/23/stupid-float-tricks-2/

Dave Dopson
  • 41,600
  • 19
  • 95
  • 85
  • `const uint32 bits = *reinterpret_cast(&f); .... bits = 0x7FFFFFF - bits;` looks wrong as `bits` is `const` and cannot be assigned later. Suggest dropping `const`. – chux - Reinstate Monica Feb 02 '20 at 12:04
  • "Identical to floating-point comparison when: ... One positive and one negative number, and you are using a signed integer comparison." --> I tried this with `float_to_comparable_integer(+0.0f)` and `float_to_comparable_integer(-0.0f)`, yet the results did not equate. Could use some rewording to account for that. – chux - Reinstate Monica Feb 02 '20 at 12:11
  • `uint32, int32` are not standard type types. Perhaps `uint32_t, int32_t`? (Since C++11) – chux - Reinstate Monica Feb 02 '20 at 12:21
  • 2
    `*reinterpret_cast(&f)` has strict-aliasing undefined behaviour. Use memcpy, or C++20 `std::bit_cast(f)`. Or since this is a C question not C++, a union is also well-defined. – Peter Cordes Feb 02 '23 at 18:07
  • @PeterCordes - good point. I've changed it to std::bit_cast – Dave Dopson Feb 10 '23 at 05:40
  • This is a [c] not [c++] question, but I guess C++ is a good way to describe the algorithm; people can do it safely in C however they like. – Peter Cordes Feb 10 '23 at 05:42
4

No. Two floating point values (IEEE 754 binary64) cannot compare simply as integers with if (a < b).

IEEE 754 binary64

The order of the values of double is not the same order as integers (unless you are are on a rare sign-magnitude machine). Think positive vs. negative numbers.

double has values like 0.0 and -0.0 which have the same value but different bit patterns.

double has "Not-a-number"s that do not compare like their binary equivalent integer representation.

If both the double values were x > 0 and not "Not-a-number", endian, aliasing, and alignment, etc. were not an issue, OP's idea would work.

Alternatively, a more complex if() ... condition would work - see below

[non-IEEE 754 binary64]

Some double use an encoding where there are multiple representations of the same value. This would differ from an "integer" compare.


Tested code: needs 2's complement, same endian for double and the integers, does not account for NaN.

int compare(double a, double b) {
  union {
    double d;
    int64_t i64;
    uint64_t u64;
  } ua, ub;
  ua.d = a;
  ub.d = b;
  // Cope with -0.0 right away
  if (ua.u64 == 0x8000000000000000) ua.u64 = 0;
  if (ub.u64 == 0x8000000000000000) ub.u64 = 0;
  // Signs differ?
  if ((ua.i64 < 0) != (ub.i64 < 0)) {
    return ua.i64 >= 0 ? 1 : -1;
  }
  // If numbers are negative
  if (ua.i64 < 0) {
    ua.u64 = -ua.u64;
    ub.u64 = -ub.u64;
  }
  return (ua.u64 > ub.u64)  - (ua.u64 < ub.u64);
}

Thanks to @David C. Rankin for a correction.

Test code

void testcmp(double a, double b) {
  int t1 = (a > b) - (a < b);
  int t2 = compare(a, b);
  if (t1 != t2) {
    printf("%le %le %d %d\n", a, b, t1, t2);
  }

}

#include <float.h>
void testcmps() {
  // Various interesting `double`
  static const double a[] = { 
      -1.0 / 0.0, -DBL_MAX, -1.0, -DBL_MIN, -0.0, 
      +0.0, DBL_MIN, 1.0, DBL_MAX, +1.0 / 0.0 };

  int n = sizeof a / sizeof a[0];
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      testcmp(a[i], a[j]);
    }
  }
  puts("!");
}
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

If you strictly cast the bit value of a floating point number to its correspondingly-sized signed integer (as you've done), then signed integer comparison of the results will be identical to the comparison of the original floating-point values, excluding NaN values. Put another way, this comparison is legitimate for all representable finite and infinite numeric values.

In other words, for double-precision (64-bits), this comparison will be valid if the following tests pass:

long long exponentMask = 0x7ff0000000000000;
long long mantissaMask = 0x000fffffffffffff;

bool isNumber =  ((x & exponentMask) != exponentMask)  // Not exp 0x7ff
              || ((x & mantissaMask) == 0);            // Infinities

for each operand x.

Of course, if you can pre-qualify your floating-point values, then a quick isNaN() test would be much more clear. You'd have to profile to understand performance implications.

Steve Hollasch
  • 2,011
  • 1
  • 20
  • 18
  • " then signed integer comparison of the results will be identical " --> disagree. Typical integer is 2's complement and IEEE 754 binary64 is effectively sign-magnitude. Comparing 2 different negative numbers would give the opposite answer. – chux - Reinstate Monica Nov 13 '15 at 20:56
  • 1
    Not to mention that without using a union (or forcing the compile with `-fno-strict-aliasing`) the compiler will (should) go crazy warning *dereferencing type-punned pointer will break strict-aliasing rules*. – David C. Rankin Nov 13 '15 at 23:04
  • 1
    @chux - D'oh! You're right. I gave it quick thought, but was obviously wrong. The correct approach is to check the sign bits of both values. If both values have set sign bits, then perform signed negation of their values first. After that test, compare both values as signed integers. – Steve Hollasch Nov 21 '15 at 00:54
  • @DavidC.Rankin - that's why compilers allow you to configure your warnings at the whole-file or code-block level. – Steve Hollasch Nov 21 '15 at 00:57
-3

There are two parts to your question:

  1. Can two floating point numbers be compared? The answer to this is yes. it is perfectly valid to compare size of floating point numbers. Generally you want to avoid equals comparisons due to truncation issues see here, but

    if (a < b)
    

    will work just fine.

  2. Can two floating point numbers be compared as integers? This answer is also yes, but this will require casting. This question should help with that answer: convert from long long to int and the other way back in c++

Community
  • 1
  • 1
GKnight
  • 259
  • 1
  • 9
  • Of course I can cast floating point numbers to integers and then compare them, but what I wanted to do is to take the binary representation of a floating point number and treat it as an integer, without casting/converting it. Eg. 0.001953125 = 0011111101100000000000000000000000000000000000000000000000000000 (IEEE 754 binary64) = 4566650022153682944 (decimal); -0.001953125 = 1011111101100000000000000000000000000000000000000000000000000000 (IEEE 754 binary64) = -4656722014701092864 (decimal) –  Nov 12 '15 at 20:02
  • Without casting, C will respect the type of the variable. This is non-negotiable, as C is a typed language. – GKnight Nov 12 '15 at 20:29