将浮点数转换为整数进行比较

5

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

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

假设long longdouble的大小相同。

对于这样的语法问题,了解您计划使用的编程语言和版本非常有帮助。这是C++、C、C#、Python等吗? - GKnight
抱歉,我忘了提到C语言。 - user4259083
1
你为什么想要这样做? - Jens Gustedt
3
strict aliasing:https://dev59.com/questions/43VD5IYBdhLWcg3wE3RoStrict aliasing是C/C++中的一条规则,它规定不同类型的指针不能指向同一存储区域。违反该规则可能导致未定义的行为,例如出现无法预测的错误结果。该规则的目的是优化编译器生成的代码,但需要开发者在编写代码时遵循。在某些情况下,可以使用特殊的技术来允许类型别名,但这需要谨慎处理,以避免潜在的问题。 - Pascal Cuoq
@JensGustedt 我需要编写一个基于堆栈的解释器,其功能包括表达式的比较。我在考虑是否可以使用相同的指令进行整数和浮点数比较,而不是有两个不同的指令。 - user4259083
@PascalCuoq 值无论如何都会在一个联合体中。 - user4259083
4个回答

16

是的 - 在某些受限制的情况下,将两个浮点数的位模式进行比较,就像它们是整数一样(也称为“类型转换”),会产生有意义的结果...

与浮点数比较相同的情况包括:

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

与浮点数比较相反的情况包括:

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

不可与浮点数比较的情况包括:

  • 任何一个数是NaN值 - 浮点数与NaN的比较始终返回false,而这在整数运算中根本无法建模,因为以下一个条件总是成立:(A < B),(A == B),(B < A)。

负浮点数处理方式与用于整数的二进制补码算术非常不同。对负浮点数的表示进行整数+1操作将使其变成一个更大的负数。

通过一些简单操作,您可以使用整数运算比较正数和负数浮点数(这对于某些优化很有用):

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);
}

需要注意的是,这种方法不适用于NaN值,因为它们始终从比较中返回false,并且有多个有效的二进制表示方式:

  • 带符号位的信号NaN:介于0xFF800001和0xFFBFFFFF之间。
  • 不带符号位的信号NaN:介于0x7F800001和0x7FBFFFFF之间。
  • 带符号位的Quiet NaN:介于0xFFC00000和0xFFFFFFFF之间。
  • 不带符号位的Quiet NaN:介于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/


const uint32 bits = *reinterpret_cast<uint32*>(&f); .... bits = 0x7FFFFFF - bits; 看起来是错误的,因为 bitsconst 类型的,不能在后面被赋值。建议去掉 const - chux - Reinstate Monica
与浮点数比较相同的情况是:...当一个为正数,一个为负数时,并且您正在使用有符号整数比较。我尝试了float_to_comparable_integer(+0.0f)float_to_comparable_integer(-0.0f),但结果并不相等。可能需要重新措辞来解决这个问题。 - chux - Reinstate Monica
uint32, int32 不是标准类型。也许应该使用 uint32_t, int32_t? (自 C++11 起) - chux - Reinstate Monica
2
*reinterpret_cast<uint32*>(&f)存在严格别名未定义行为。请使用memcpy,或C++20的std::bit_cast<uint32_t>(f)。或者,由于这是一个C问题而不是C ++,所以联合体也是明确定义的。 - Peter Cordes
@PeterCordes - 很好的观点。我已经将其更改为std :: bit_cast。 - Dave Dopson
这是一个[C]而不是[C++]的问题,但我猜C++是描述算法的好方法;人们可以在C中以任何他们喜欢的方式安全地完成它。 - Peter Cordes

4
两个浮点数值(IEEE 754 binary64)不能简单地像整数一样用 if (a < b) 进行比较。 IEEE 754 binary64 双精度的排序顺序与整数不同(除非你在一个罕见的符号-幅值机器上)。想想正数和负数。
双精度有像0.0-0.0 这样的值,它们具有相同的值但不同的位模式。
双精度还有“不是数字”的东西,它们不像其二进制等效整数表示那样进行比较。
如果两个双精度值都是 x > 0 且都不是“不是数字”,则 OP 的想法可以实现,而字节顺序、别名和对齐等问题也不是问题。
或者,更复杂的 if() ... 条件也可以使用——请参见下面的内容
【非 IEEE 754 binary64】
一些 double 使用一种编码方式,其中同一值有多个表示。这与“整数”比较不同。

测试代码:需要使用二进制补码,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("!");
}

0

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

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

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

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

对于每个操作数 x。

当然,如果您可以预先确定浮点值,则快速的 isNaN() 测试会更加清晰。您需要进行性能分析以了解其影响。


然后,结果的有符号整数比较将是相同的"-->不同意。典型的整数是2的补码,IEEE 754 binary64实际上是符号-数量。比较两个不同的负数将得出相反的答案。 - chux - Reinstate Monica
1
更不用说如果不使用联合体(或强制编译时加上-fno-strict-aliasing选项),编译器就会(应该会)发出警告,指出“解引用类型转换后的指针将违反严格别名规则”。 - David C. Rankin
1
@chux - 哎呀!你说得对。我草率地想了想,但显然是错的。正确的方法是检查两个值的符号位。如果两个值都设置了符号位,则首先执行有符号否定它们的值。在进行此测试后,将两个值作为有符号整数进行比较。 - Steve Hollasch
@DavidC.Rankin - 这就是为什么编译器允许您在整个文件或代码块级别配置警告的原因。 - Steve Hollasch

-3

你的问题有两个部分:

  1. 两个浮点数可以进行比较吗? 答案是肯定的。比较浮点数的大小是完全有效的。通常,由于截断问题,您希望避免等于比较,请参见此处,但是

    if (a < b)
    

    将正常工作。

  2. 两个浮点数可以作为整数进行比较吗? 答案也是肯定的,但这需要进行强制转换。这个问题应该会帮助您回答:在C++中从long long转换为int,以及另一种方式


当然,我可以将浮点数转换为整数,然后进行比较,但是我想做的是获取浮点数的二进制表示,并将其视为整数,而不进行强制转换。例如:0.001953125 = 0011111101100000000000000000000000000000000000000000000000000000(IEEE 754 binary64)= 4566650022153682944(十进制);-0.001953125 = 1011111101100000000000000000000000000000000000000000000000000000(IEEE 754 binary64)= -4656722014701092864(十进制)。 - user4259083
不进行强制类型转换,C将尊重变量的类型。这是不可谈判的,因为C是一种类型化语言。 - GKnight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接