如何确定给定的 int64_t 是否可以无损存储在 double 中?

How to determine if a given int64_t can be stored losslessly in a double?

我想确定给定的 64 位整数是否可以无损地存储在双精度数中。现在我有这个代码:

static_cast<int64_t>(static_cast<double>(value)) == value

但是,由于 excess precision 在某些平台上,我认为这并不总是准确的。

请注意,我不是要 largest integer such that all smaller integers can be stored losslessly,它是 2^53。我需要知道给定的整数 N 是否可无损存储,即使 N+1 和 N-1 不是。

标准库中是否有类似于 std::numeric_limits 的东西可以告诉我这个?

您需要测试低位。如果它们为零,则数字将被无损存储。

只要低位为0,就可以设置更多的高位(因为可以增加double的指数)。我减少了对无符号的要求,使位移不必担心符号位,但我相信它应该是适应性的。

bool fits_in_double_losslessly (uint64_t v)
{
    const uint64_t largest_int = 1ULL << 53;

    while ((v > largest_int) && !(v & 1))
    {
        v >>= 1;
    }

    return v <= largest_int;
}

从最低位组中减去最高位组,加一以避免栅栏错误,与尾数中的位数进行比较。请记住,尾数的前导 1 在 IEEE 中是隐含的。如果尾数可以容纳整个使用范围的位,它可以准确地存储该数字。

或者,查看是否 static_cast<double>(m) != static_cast<double>(m+1) && static_cast<double>(m) != static_cast<double>(m-1)。这适用于 unsigned 值,但对于 signed 类型,可移植代码需要检查该值不会溢出或下溢,因为是未定义的行为。再一次,一些实现可能也没有 int64_t 。然而,在许多实现中,带符号的溢出或下溢仍然会给你一个不同的数字,但符号相反,这将正确地测试为不同。

其他答案中的一个共同主题是担心您是否可以假设双打是通过 IEEE 754 实现的。

我认为,对于许多用途,您的原始方法是最好的:将给定的 int64_t 转换为双精度数,然后将结果分配回第二个 int64_t,然后比较两者。

关于您对精度过高的担忧,只要您知道该值实际上已写入内存并读回,那就不适用了,因为在那个阶段,编译器不应该有任何方法为该变量存储 "excess precision"。根据您最初 link 的回答,我相信以下示例足以强制执行该行为:

bool losslessRoundTrip(int64_t valueToTest)
{
    double newRepresentation;
    *((volatile double *)&newRepresentation) = static_cast<double>(valueToTest);
    int64_t roundTripValue = static_cast<int64_t>(newRepresentation);
    return roundTripValue == valueToTest;
}

我怀疑简单地将 newRepresentation 声明为 volatile 就足够了,但我没有充分使用 volatile 关键字来确定,所以我只是从你的 link 中改编了解决方案。

您的原始方法的好处在于它应该适用于几乎所有支持正确的转换和运算符的东西,只要您只关心是否可以像这样返回到初始类型。例如,这是一个通用实现:

template<typename T1, typename T2>
bool losslessRoundTrip(T1 valueToTest)
{
    T2 newRepresentation;
    *((volatile T2 *)&newRepresentation) = static_cast<T2>(valueToTest);
    T1 roundTripValue = static_cast<T1>(newRepresentation);
    return roundTripValue == valueToTest;
}

请注意,这可能不是您问题的最佳答案,因为通过...将其存储为双精度来检查是否可以将其存储为双精度似乎仍然是一种作弊。不过,我不知道还有什么方法可以避免依赖内部表示的机制。

它也不会相当检查它是否被无损地存储在双精度中,而是检查是否从 int64_t 到双精度,然后返回int64_t 在该平台上是无损的。我不知道在两个方向上都应用强制转换时是否会出现被抵消的强制转换错误(我会向对此有更多了解的人求助),但在我看来,我通常认为它足够接近"lossless"如果我转换回相同的类型,我是否可以取回初始值。