如何检查float是否可以精确表示为整数
我正在寻找一种合理有效的方法来确定浮点值( double
)是否可以由整数数据类型( long
,64位)精确表示。
我最初的想法是检查指数是否为0
(或更准确地说是127
)。 但这不起作用,因为2.0
将是e = 1 m = 1 …
所以基本上,我被卡住了。 我有一种感觉,我可以使用位掩码做到这一点,但我现在还没有理解如何做到这一点。
那么我怎样才能检查一个double是否可以完全表示为long?
谢谢
这是一种在大多数情况下都可以使用的方法。 如果你给它NaN
, INF
,非常大(溢出)的数字,我不确定它是否会/如何破坏…
(虽然我认为它们都会返回错误 – 不完全可以代表。)
你可以:
- 将其转换为整数。
- 把它扔回浮点。
- 与原始值比较。
像这样的东西:
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_t
和uint64_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
float
或float
值都将是整数。 将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 }