在 GCC 7.3.1 中给出以下非常简单的程序:
\n//exponentTest.cc\n#include <complex> \n#include <iostream> \n\nint main(int argc, char **argv)\n{\n std::complex<double> iCpx = std::complex<double>(0,1); \n double baseline[3] = {-0.1, 0, 0};\n \n double theta = atan2(baseline[1], baseline[0]); \n std::cout << "Theta: " << theta << std::endl;\n\n for(size_t m =1; m < 10; m++)\n {\n std::complex<double> thetExp = exp(-1 * m * theta * iCpx); \n std::cout << "m(" << m << "): " << thetExp << std::endl;\n }\n return 0; \n}\nRun Code Online (Sandbox Code Playgroud)\n当 theta 为 PI 时,此代码给出错误结果:
\n[stix@localhost ~]$ gcc exponentTest.cc -o expTest -lm -lstdc++\n[stix@localhost ~]$ ./expTest\nTheta: 3.14159\nm(1): (-0.963907,0.26624)\nm(2): (-0.963907,0.26624)\nm(3): (-0.963907,0.26624)\nm(4): (-0.963907,0.26624)\nm(5): (-0.963907,0.26624)\nm(6): (-0.963907,0.26624)\nm(7): (-0.963907,0.26624)\nm(8): (-0.963907,0.26624)\nm(9): (-0.963907,0.26624)\nRun Code Online (Sandbox Code Playgroud)\n然而,正确的答案应该是交替+-1。
\n经过一番哀嚎和咬牙切齿之后,我追踪到了 for() 循环中 size_t 的使用。我用 unsigned int 替换了它,并且单元测试有效,但使用它的更广泛的代码库却没有。沮丧的是,我最终明确地将 std::exp 的输入转换为 double:
\n//Improved exponentTest.cc\n#include <complex> \n#include <iostream> \n\n\n\nint main(int argc, char **argv)\n{\n std::complex<double> iCpx = std::complex<double>(0,1); \n double baseline[3] = {-0.1, 0, 0};\n \n double theta = atan2(baseline[1], baseline[0]); \n std::cout << "Theta: " << theta << std::endl;\n\n for(size_t m = 1; m < 10; m++)\n {\n std::complex<double> thetExp = exp(-1 * (double)m * theta * iCpx); \n std::cout << "m(" << m << "): " << thetExp << std::endl;\n }\n return 0; \n}\nRun Code Online (Sandbox Code Playgroud)\n该版本始终给出正确的结果:
\n[stix@localhost ~]$ gcc exponentTest.cc -o expTest -lm -lstdc++\n[stix@localhost ~]$ ./expTest\nTheta: 3.14159\nm(1): (-1,-1.22465e-16)\nm(2): (1,2.44929e-16)\nm(3): (-1,-3.67394e-16)\nm(4): (1,4.89859e-16)\nm(5): (-1,-6.12323e-16)\nm(6): (1,7.34788e-16)\nm(7): (-1,-8.57253e-16)\nm(8): (1,9.79717e-16)\nm(9): (-1,-1.10218e-15)\nRun Code Online (Sandbox Code Playgroud)\n所以问题解决了。但是,我不知道为什么会出现这个问题,而且有点像编译器错误的味道。最糟糕的是,如果没有在 exp() 函数中显式转换 m,问题就会不一致;有时它会提供正确的结果,有时则不会。
\n我的理解是,C++ 标准要求编译器自动将所有整数转换为双精度数,如下所示:
\ndouble = int * double * int * double;\nRun Code Online (Sandbox Code Playgroud)\n但编译器显然没有这样做。
\n这是编译器错误还是在混合双精度数和整数时我没有考虑到一些问题?
\n编辑:根据其中一条评论,指数行应该引发警告,因为无符号类型正在乘以 -1,但是,情况并非如此:
\n[stix@localhost ~]$ gcc exponentTest.cc -o expTest -lm -lstdc++ -Wall -Wextra -pedantic-errors\nexponentTest.cc: In function \xe2\x80\x98int main(int, char**)\xe2\x80\x99:\nexponentTest.cc:6:14: warning: unused parameter \xe2\x80\x98argc\xe2\x80\x99 [-Wunused-parameter]\n int main(int argc, char **argv)\n ^~~~\nexponentTest.cc:6:27: warning: unused parameter \xe2\x80\x98argv\xe2\x80\x99 [-Wunused-parameter]\n int main(int argc, char **argv)\n ^~~~\nRun Code Online (Sandbox Code Playgroud)\n相关软件版本(我使用的是 GCC 7 的 devtoolset-7):
\n[stix@localhost ~]$ gcc --version\ngcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)\nCopyright (C) 2017 Free Software Foundation, Inc.\nThis is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n[stix@localhost ~]$ cat /etc/redhat-release\nCentOS Linux release 7.9.2009 (Core)\n[stix@localhost ~]$ rpm -qa | grep devtoolset\ndevtoolset-7-gcc-plugin-devel-7.3.1-5.16.el7.x86_64\ndevtoolset-7-binutils-2.28-11.el7.x86_64\ndevtoolset-7-gcc-7.3.1-5.16.el7.x86_64\ndevtoolset-7-gcc-gfortran-7.3.1-5.16.el7.x86_64\ndevtoolset-7-runtime-7.1-4.el7.x86_64\ndevtoolset-7-libquadmath-devel-7.3.1-5.16.el7.x86_64\ndevtoolset-7-gcc-gdb-plugin-7.3.1-5.16.el7.x86_64\ndevtoolset-7-libstdc++-devel-7.3.1-5.16.el7.x86_64\ndevtoolset-7-gcc-c++-7.3.1-5.16.el7.x86_64\nRun Code Online (Sandbox Code Playgroud)\n
最简单的修复方法是更改1为1.0. 这迫使计算用 s 完成double:
std::complex<double> thetExp = exp(-1.0 * m * theta * iCpx);
Run Code Online (Sandbox Code Playgroud)
这是为什么?让我们看一下失败的表达式:
-1 * m * theta * iCpx
Run Code Online (Sandbox Code Playgroud)
这里的类型有:
int * size_t * double * std::complex<double>
Run Code Online (Sandbox Code Playgroud)
C++ 不会考虑所有类型并选择“最高”的类型来提升所有类型。相反,它从左到右逐一查看二元运算,就好像有括号将它们分组一样:
(((int * size_t) * double) * std::complex<double>)
Run Code Online (Sandbox Code Playgroud)
你会遇到麻烦,因为int * size_t首先执行。整数促销规则适用,并且int会转换为,size_t因为您的平台上size_t的值更大。这意味着有符号 32 位整数将转换为 64 位无符号整数。
尝试一下,你就可以看到问题所在:
std::cout << (size_t) -1 << "\n";
Run Code Online (Sandbox Code Playgroud)
它打印:
18446744073709551615
Run Code Online (Sandbox Code Playgroud)
当您更改-1 * m为-1 * (double) m摆脱签名/未签名问题时。-1晋升为一double,这简直就是-1.0。