将浮点数与整数进行比较

Compare floating point numbers as integers

两个浮点值(IEEE 754 binary64)可以作为整数进行比较吗?例如

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

假设 long longdouble 的大小相同。

您的问题分为两部分:

  1. 两个浮点数可以比较吗?这个问题的答案是可以。比较浮点数的大小是完全有效的。通常,由于截断问题,您希望避免相等比较,请参阅 here,但

    if (a < b)
    

    会很好用。

  2. 两个浮点数可以作为整数进行比较吗?这个答案也是可以的,但这需要强制转换。这个问题应该有助于回答这个问题:convert from long long to int and the other way back in c++

如果您严格地将浮点数的位值转换为其相应大小的有符号整数(正如您所做的那样),那么结果的有符号整数比较将与原始浮点数的比较相同-点值,不包括 NaN 值。换句话说,这种比较对于所有可表示的有限和无限数值都是合法的。

换句话说,对于双精度(64位),如果通过以下测试,则此比较有效:

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

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

对于每个操作数 x。

当然,如果您可以预先限定浮点值,那么快速的 isNaN() 测试会更加清晰。您必须分析才能了解性能影响。

没有。两个浮点值 (IEEE 754 binary64) 无法将 简单地 作为整数与 if (a < b).

进行比较

IEEE 754 binary64

double 的值的顺序与整数的顺序不同(除非您使用的是罕见的符号量级机器)。思考正数与负数。

double 具有类似于 0.0-0.0 的值,它们具有相同的值但不同的位模式。

double 有 "Not-a-number" 与它们的二进制等效整数表示不同。

如果 double 的值都是 x > 0 而不是 "Not-a-number",endian、aliasing 和 alignment 等都不是问题,OP 的想法就可以实现。

或者,更复杂的 if() ... 条件也行 - 见下文

[非 IEEE 754 binary64]

一些 double 使用一种编码,其中有相同值的多种表示形式。这与 "integer" 比较不同。


测试代码:需要 2 的补码,double 和整数的字节顺序相同,不考虑 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);
}

感谢 @David C. Rankin 更正。

测试代码

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("!");
}

是 - 比较两个浮点数的位模式,就好像它们是整数(又名 "type-punning")在某些受限情况下会产生有意义的结果...

在以下情况下与浮点比较相同:

  • 两个数都是正数、正零或正无穷大。
  • 一个正数和一个负数,并且您使用的是有符号整数比较。

浮点比较的逆运算,当:

  • 两个数都是负数、负零或负无穷大。
  • 一个正数和一个负数,并且您使用的是无符号整数比较。

在以下情况下无法与浮点数比较相比:

  • 任一数字都是 NaN 值之一 - 与 NaN 的浮点比较总是 returns 假,并且这根本无法在整数运算中建模,其中恰好以下其中一项始终为真: (A < B), (A == B), (B < A).

负浮点数有点古怪 b/c 它们的处理方式与用于整数的 2 的补码算法非常不同。在负浮点数的表示上做一个整数 +1 会使它成为一个更大的负数。

通过一点点操作,您可以使正浮点数和负浮点数都与整数运算相当(这对于某些优化可能会派上用场):

int32 float_to_comparable_integer(float f) {
  const uint32 bits = *reinterpret_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);
}

同样,这 对 NaN 值起作用,NaN 值在比较中总是 return 错误,并且有多个有效位表示:

  • 发信号 NaN(带符号位):0xFF800001 和 0xFFBFFFFF 之间的任何值。
  • 信号 NaN(w/o 符号位):0x7F800001 和 0x7FBFFFFF 之间的任何值。
  • Quiet NaN(带符号位):0xFFC00000 和 0xFFFFFFFF 之间的任何值。
  • Quiet NaN(w/o 符号位):0x7FC00000 和 0x7FFFFFFF 之间的任何值。

IEEE-754 位格式:http://www.puntoflotante.net/FLOATING-POINT-FORMAT-IEEE-754.htm

关于打字双关的更多信息:https://randomascii.wordpress.com/2012/01/23/stupid-float-tricks-2/