如何检查float是否可以精确表示为整数

我正在寻找一种合理有效的方法来确定浮点值( double )是否可以由整数数据类型( long ,64位)精确表示。

我最初的想法是检查指数是否为0 (或更准确地说是127 )。 但这不起作用,因为2.0将是e = 1 m = 1 …

所以基本上,我被卡住了。 我有一种感觉,我可以使用位掩码做到这一点,但我现在还没有理解如何做到这一点。

那么我怎样才能检查一个double是否可以完全表示为long?

谢谢

这是一种在大多数情况下都可以使用的方法。 如果你给它NaNINF ,非常大(溢出)的数字,我不确定它是否会/如何破坏…
(虽然我认为它们都会返回错误 – 不完全可以代表。)

你可以:

  1. 将其转换为整数。
  2. 把它扔回浮点。
  3. 与原始值比较。

像这样的东西:

 double val = ... ; // Value if ((double)(long long)val == val){ // Exactly representable } 

floor()ceil()也是公平的游戏(尽管如果值溢出整数,它们可能会失败):

 floor(val) == val ceil(val) == val 

这是一个混乱的位掩码解决方案:
这使用了union type-punning并假设IEEE双精度。 联合类型 – 惩罚仅在C99 TR2及更高版本中有效。

 int representable(double x){ // Handle corner cases: if (x == 0) return 1; // -2^63 is representable as a signed 64-bit integer, but +2^63 is not. if (x == -9223372036854775808.) return 1; // Warning: Union type-punning is only valid in C99 TR2 or later. union{ double f; uint64_t i; } val; val.f = x; uint64_t exp = val.i & 0x7ff0000000000000ull; uint64_t man = val.i & 0x000fffffffffffffull; man |= 0x0010000000000000ull; // Implicit leading 1-bit. int shift = (exp >> 52) - 1075; // Out of range if (shift < -52 || shift > 10) return 0; // Test mantissa if (shift < 0){ shift = -shift; return ((man >> shift) << shift) == man; }else{ return ((man << shift) >> shift) == man; } } 

我想我已经找到了一种方法,以符合标准的方式将double精度钳制成一个整数(这不是真正的问题,但它有很多帮助)。 首先,我们需要了解明显代码正确的原因。

 // INCORRECT CODE uint64_t double_to_uint64 (double x) { if (x < 0.0) { return 0; } if (x > UINT64_MAX) { return UINT64_MAX; } return x; } 

这里的问题是在第二次比较中, UINT64_MAX被隐式转换为double 。 C标准没有详细说明此转换的工作原理,只是将其向上舍入或向下舍入为可表示的值。 这意味着第二次比较可能是错误的,即使在数学上应该是真的(当UINT64_MAX被向上舍UINT64_MAX可能发生,并且’x’在数学上在UINT64_MAX(double)UINT64_MAX )。 因此,将double转换为uint64_t可能会导致该边缘情况下的未定义行为。

令人惊讶的是,解决方案非常简单。 考虑到虽然UINT64_MAX可能无法在double UINT64_MAX+1完全表示,但UINT64_MAX+1是2的幂(而不是太大),当然是。 因此,如果我们首先将输入舍入为整数,则比较x > UINT64_MAX等效于x >= UINT64_MAX+1 ,除了可能的加法溢出。 我们可以通过使用ldexp而不是向UINT64_MAX添加一个来修复溢出。 话虽如此,以下代码应该是正确的。

 /* Input: a double 'x', which must not be NaN. * Output: If 'x' is lesser than zero, then zero; * otherwise, if 'x' is greater than UINT64_MAX, then UINT64_MAX; * otherwise, 'x', rounded down to an integer. */ uint64_t double_to_uint64 (double x) { assert(!isnan(x)); double y = floor(x); if (y < 0.0) { return 0; } if (y >= ldexp(1.0, 64)) { return UINT64_MAX; } return y; } 

现在,回到你的问题: x是否可以在uint64_t完全表示? 只有它既不是圆形也不是夹紧的。

 /* Input: a double 'x', which must not be NaN. * Output: If 'x' is exactly representable in an uint64_t, * then 1, otherwise 0. */ int double_representable_in_uint64 (double x) { assert(!isnan(x)); return (floor(x) == x && x >= 0.0 && x < ldexp(1.0, 64)); } 

相同的算法可以用于不同大小的整数,也可以用于具有微小修改的有符号整数。 下面的代码对uint32_tuint64_t版本进行了一些非常基本的测试(可能只捕获了误报),但也适用于边缘情况的手动检查。

 #include  #include  #include  #include  #include  uint32_t double_to_uint32 (double x) { assert(!isnan(x)); double y = floor(x); if (y < 0.0) { return 0; } if (y >= ldexp(1.0, 32)) { return UINT32_MAX; } return y; } uint64_t double_to_uint64 (double x) { assert(!isnan(x)); double y = floor(x); if (y < 0.0) { return 0; } if (y >= ldexp(1.0, 64)) { return UINT64_MAX; } return y; } int double_representable_in_uint32 (double x) { assert(!isnan(x)); return (floor(x) == x && x >= 0.0 && x < ldexp(1.0, 32)); } int double_representable_in_uint64 (double x) { assert(!isnan(x)); return (floor(x) == x && x >= 0.0 && x < ldexp(1.0, 64)); } int main () { { printf("Testing 32-bit\n"); for (double x = 4294967295.999990; x < 4294967296.000017; x = nextafter(x, INFINITY)) { uint32_t y = double_to_uint32(x); int representable = double_representable_in_uint32(x); printf("%f -> %" PRIu32 " representable=%d\n", x, y, representable); assert(!representable || (double)(uint32_t)x == x); } } { printf("Testing 64-bit\n"); double x = ldexp(1.0, 64) - 40000.0; for (double x = 18446744073709510656.0; x < 18446744073709629440.0; x = nextafter(x, INFINITY)) { uint64_t y = double_to_uint64(x); int representable = double_representable_in_uint64(x); printf("%f -> %" PRIu64 " representable=%d\n", x, y, representable); assert(!representable || (double)(uint64_t)x == x); } } } 

您可以使用modf函数将float分割为整数和小数部分。 modf在标准C库中。

 #include  #include  double val = ... double i; long l; /* check if fractional part is 0 */ if (modf(val, &i) == 0.0) { /* val is an integer. check if it can be stored in a long */ if (val >= LONG_MIN && val <= LONG_MAX) { /* can be exactly represented by a long */ l = val; } } 

如何检查float是否可以精确表示为整数?

我正在寻找一种合理有效的方法来确定浮点值double可以由整数数据类型long ,64位精确表示。

需要范围( LONG_MIN, LONG_MAX )和分数( frexp() )测试。 还需要注意非数字。


通常的想法是测试像(double)(long)x == x ,但要避免直接使用。 (long)x ,当x超出范围时,是未定义的行为 (UB)。

(long)x的有效转换范围是LONG_MIN - 1 < x < LONG_MAX + 1因为代码在转换期间丢弃x任何小数部分。 因此,如果x在范围内,则代码需要使用FP数学进行测试。

 #include  #include  #define DBL_LONG_MAXP1 (2.0*(LONG_MAX/2+1)) #define DBL_LONG_MINM1 (2.0*(LONG_MIN/2-1)) bool double_to_long_exact_possible(double x) { if (x < DBL_LONG_MAXP1) { double whole_number_part; if (frexp(x, &whole_number_part) != 0.0) { return false; // Fractional part exist. } #if -LONG_MAX == LONG_MIN // rare non-2's complement machine return x > DBL_LONG_MINM1; #else return x - LONG_MIN > -1.0; #endif } return false; // Too large or NaN } 

大小等于或大于2 ^ 52或2 ^ 23的任何IEEE浮点double floatfloat值都将是整数。 将2 ^ 52或2 ^ 23添加到幅度小于该值的正数将使其舍入为整数。 减去添加的值将产生一个整数,它将等于原始iff原始数是一个整数。 请注意,此算法将失败,某些数字大于2 ^ 52,但对于大数字则不需要。

你能用模数运算符来检查双精度是否可被1整除……或者我是否完全误解了这个问题?

 double val = ... ; // Value if(val % 1 == 0) { // Val is evenly divisible by 1 and is therefore a whole number }