从 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.
考虑以下代码:
#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
orunsigned 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.