我正在寻找一种合理有效的方法来确定浮点值(double
)是否可以由整数数据类型(long
,64位)精确表示。
我最初的想法是检查指数是否为0
(或更确切地说是127
)。但这不起作用,因为2.0
将是e = 1 m = 1 ...
所以基本上,我被卡住了。我有一种感觉,我可以使用位掩码来做到这一点,但我现在还没有理解如何做到这一点。
那么如何检查双精度是否可以完全表示为长?
由于
答案 0 :(得分:10)
这是一种在大多数情况下都可以使用的方法。如果你给它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双精度。 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
之间。因此,double
到uint64_t
的转换可能会导致该边缘情况下的未定义行为。
令人惊讶的是,解决方案非常简单。考虑到虽然UINT64_MAX
double
可能无法在UINT64_MAX+1
,x > 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浮点double
或float
值都是整数。将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
}