从 long double 到 unsigned long long 的转换在 MSVC C++ 编译器中出现问题

Casting from long double to unsigned long long appears broken in the MSVC C++ compiler

考虑以下代码:

#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    long double test = 0xFFFFFFFFFFFFFFFF;
    cout << "1: " << test << endl;
    unsigned long long test2 = test;
    cout << "2: " << test2 << endl;
    cout << "3: " << (unsigned long long)test << endl;
    return 0;
}

使用 GCC g++ (7.5.0) 和 运行 编译此代码会按预期产生以下输出:

1: 1.84467e+19
2: 18446744073709551615
3: 18446744073709551615

但是使用 Microsoft Visual C++ 编译器(16.8.31019.35,64 位和 32 位)和 运行 编译它会产生以下输出:

1: 1.84467e+19
2: 9223372036854775808
3: 9223372036854775808

当将值转换为 unsigned long long 时,MSVC 编译器不会给出大于(有符号)long long.

的最大值的值

我是不是做错了什么?

我是否 运行 遇到了我不知道的编译器限制?

有人知道这个问题的可能解决方法吗?

因为 MSVC long double 实际上只是一个 double(正如@drescherjm 在评论中指出的那样),它没有足够的精度来包含 0xFFFFFFFFFFFFFFFF 的精确值。当此值存储在 long double 中时,它会“四舍五入”为大于 0xFFFFFFFFFFFFFFFF 的值。当转换为 unsigned long long.

时,这会导致未定义的行为

您看到了未定义的行为,因为正如评论中所指出的,long double 与 MSVC 中的 double 以及 [=14 的 'converted' 值相同=](或ULLONG_MAX)实际上使'rounded'稍微(但显着)更大的值,如以下代码所示:

int main(int argc, char* argv[])
{
    long double test = 0xFFFFFFFFFFFFFFFF;
    cout << 0xFFFFFFFFFFFFFFFFuLL << endl;
    cout << fixed << setprecision(16) << endl;
    cout << test << endl;
    return 0;
}

输出:

18446744073709551615
18446744073709551616.0000000000000000

因此,当将 floating-point 值转换回 unsigned long long 时,您违反了 this Microsoft document:

中指定的转换规则
  • For conversion to unsigned long or unsigned long long, the result of converting an out-of-range value may be some value other than the highest or lowest representable value. Whether the result is a sentinel or saturated value or not depends on the compiler options and target architecture. Future compiler releases may return a saturated or sentinel value instead.

这个 UB 可以进一步 'verified'(因为需要一个更好的术语)切换到可以在 Visual Studio 中使用的 clang-cl 编译器。对于您的 原始 代码,这将为“2”和“3”输出行上的值提供 0

假设 clang (LLVM) 编译器不受上述“Microsoft 规则”的约束,我们可以转而使用 the C++ Standard:

7.10 Floating-integral conversions      [conv.fpint]

1     A prvalue of a floating-point type can be converted to a prvalue of an integer type. The conversion truncates; that is, the fractional part is discarded. The behavior is undefined if the truncated value cannot be represented in the destination type.