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

时间:2012-01-18 04:36:53

标签: c double ieee-754

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

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

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

那么如何检查双精度是否可以完全表示为长?

由于

6 个答案:

答案 0 :(得分:10)

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

你可以:

  1. 将其转换为整数。
  2. 将其重新投射到浮点。
  3. 与原始值比较。
  4. 这样的事情:

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

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

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

    这是一个凌乱的比特掩码解决方案:
    这使用了union type-punning并假设IEEE双精度。 Union type-punning is only valid in C99 TR2 and later.

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

答案 1 :(得分:9)

我认为我找到了一种方法,以符合标准的方式将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向上舍入时可能发生,而'x'在数学上在UINT64_MAX(double)UINT64_MAX之间。因此,doubleuint64_t的转换可能会导致该边缘情况下的未定义行为。

令人惊讶的是,解决方案非常简单。考虑到虽然UINT64_MAX double可能无法在UINT64_MAX+1x > UINT64_MAX中完全代表,但是当然是2的幂(而不是太大)。因此,如果我们首先将输入舍入为整数,则比较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_t

相同的算法可用于不同大小的整数,也可用于带有微小修改的有符号整数。下面的代码对uint64_t#include <inttypes.h> #include <math.h> #include <limits.h> #include <assert.h> #include <stdio.h> 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); } } } 版本进行了一些非常基本的测试(只能捕获误报),但也适用于边缘情况的手动检查。

{{1}}

答案 2 :(得分:4)

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

#include <math.h>
#include <limits.h>   

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

答案 3 :(得分:1)

  

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

     

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

需要进行范围(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 <limits.h>
#include <stdbool.h>
#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
}

答案 4 :(得分:0)

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

答案 5 :(得分:-1)

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

double val = ... ;  //  Value

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