请考虑以下代码:
#include <iostream>
int main() {
long long x = 123456789123456789;
std::cout << std::fixed;
auto y = static_cast<double>(x); // (1)
std::cout << static_cast<long long>(y) << "\n"; // (2)
std::cout << y << "\n";
std::cout << (x == static_cast<long long>(y)) << "\n"; // (3)
std::cout << static_cast<long long>(static_cast<double>(x)) << "\n"; // (4)
std::cout << (x == static_cast<long long>(static_cast<double>(x))) << "\n"; // (5)
}
在Linux(g++ -m32 a.cpp
)上使用32位GCC编译时,it prints as follows:
123456789123456784
123456789123456784.000000
0
123456789123456789
1
请注意,将long long
转换为double
,然后再转换回long long
的结果是不同的,这取决于它是如何完成的。如果我通过一个单独的变量double y
((1)
和(2)
行)来执行,结果以4结尾。但是如果我在一个表达式中做所有的事情(行(4)
),结果以9结束,就像原始值一样。
这是相当不方便的:不存在当转换为long long
时导致123456789123456789
的double
,并且行(3)
中的检查确认了这一点。但是,行(5)
中的检查通过,就像有一个一样。这是GCC中的bug还是我的程序?
根据上面的Godbolt链接,这种行为始于GCC 9,GCC 8工作正常。更有趣的是,如果我添加-O2
,所有表达式在编译过程中都会被优化,输出结果是:
123456789123456784
123456789123456784.000000
0
123456789123456784
0
如果我从std::cin
读取x
并保留-O2
,则中间变量以4
结束,但在转换为long long
后,会出现一个野生9
。
123456789123456789
123456789123456784.000000
1
123456789123456789
1
我相信上面的程序中没有未定义的行为:
- 从整数类型到浮点类型的转换是定义的:它会产生舍入的浮点值。所以要么是
123456789123456784
要么是123456789123456790
- 这两个值都适合
long long
。
2条答案
按热度按时间wgmfuz8q1#
这似乎是另一个臭名昭著的GCC (non-)bug 323的示例。有人可能会说,它更接近于例如。bug 85957,因为这个问题发生在整数上,而没有任何浮点数的计算。
然而,潜在的问题可能是相同的:GCC在32位模式下使用
long double
进行计算,因为这是8086的FPU(8087)在过去使用的。查看at the disassembly:因此,
(1)
行实际上将double
存储到内存位置并将其截断为64位,(2)
行稍后从内存中获取这64位。但是(4)
行直接与80位寄存器一起工作,123456789123456789
精确地适合80位IEEE扩展双精度数。因此,没有舍入,经过一段代码后,我们在long long
中得到了这个精确的值。令人惊讶的是,即使添加
-msse -msse2 -mfpmath=sse -march=skylake
选项也不会改变最新GCC 13.2的结果。我以为“使用SSE指令而不是x87”(我相信这是g++ -m64
的默认设置)会改变一些东西,但事实并非如此。如果截断到64位很重要,我建议使用一个中间变量
volatile double y
:当使用
g++ -m32
编译并将123456789123456789
作为输入it prints时:volatile
强制GCC将double
实际存储和加载到内存中,强制将80位值舍入为64位。不知道这是怎么记录的。一个程序范围的文档选项是-ffloat-store
。pw9qyyiw2#
GCC在https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html中记录了这种非标准行为。
当没有给出
-std=c++XX
选项时(即,如果使用默认-std=gnu++XX
),则-fexcess-precision
被设置为fast
。这样做的效果是,违反了C和C标准,GCC假设操作总是可以以比类型所允许的更高的精度执行,并且在任何给定示例中是否会发生这种情况是未指定的。
然而,C和C标准要求强制转换和赋值,以舍入到实际目标类型中可表示的值。因此,您的检查结果不允许是您描述的
1
。(另一方面,即。算术,在单个表达式中的操作标准 do 明确允许更高精度的操作。使用
-std=c++XX
选项自动启用的-fexcess-precision=standard
可以获得符合标准的行为。然而,对于C++,它只是在GCC 13之后才实现的。类似地,GCC默认为
-ffp-contract=fast
,这也是不一致的,并允许GCC跨语句收缩浮点操作,即。假设无限精确的中间结果,而标准同样不允许跨语句、强制转换或赋值这样做。符合标准的选项是-ffp-contract=on
(或完全禁用它的-ffp-contract=off
)。(This但是,这并不意味着没有bug会导致行为不符合要求,正如在另一个答案中链接的bug报告中所讨论的那样。)