在GCC 7.3.1中给出了以下非常简单的程序:
//exponentTest.cc
#include <complex>
#include <iostream>
int main(int argc, char **argv)
{
std::complex<double> iCpx = std::complex<double>(0,1);
double baseline[3] = {-0.1, 0, 0};
double theta = atan2(baseline[1], baseline[0]);
std::cout << "Theta: " << theta << std::endl;
for(size_t m =1; m < 10; m++)
{
std::complex<double> thetExp = exp(-1 * m * theta * iCpx);
std::cout << "m(" << m << "): " << thetExp << std::endl;
}
return 0;
}当θ为PI时,此代码提供了一个不正确的结果:
[stix@localhost ~]$ gcc exponentTest.cc -o expTest -lm -lstdc++
[stix@localhost ~]$ ./expTest
Theta: 3.14159
m(1): (-0.963907,0.26624)
m(2): (-0.963907,0.26624)
m(3): (-0.963907,0.26624)
m(4): (-0.963907,0.26624)
m(5): (-0.963907,0.26624)
m(6): (-0.963907,0.26624)
m(7): (-0.963907,0.26624)
m(8): (-0.963907,0.26624)
m(9): (-0.963907,0.26624)然而,正确的答案应该是交替的+-1。
经过一阵哀号和咬牙切齿之后,我追踪到了for()循环中size_t的使用情况。我用一个无符号的int替换了它,单元测试也起了作用,但是我使用它的更广泛的代码库没有做到这一点。
//Improved exponentTest.cc
#include <complex>
#include <iostream>
int main(int argc, char **argv)
{
std::complex<double> iCpx = std::complex<double>(0,1);
double baseline[3] = {-0.1, 0, 0};
double theta = atan2(baseline[1], baseline[0]);
std::cout << "Theta: " << theta << std::endl;
for(size_t m = 1; m < 10; m++)
{
std::complex<double> thetExp = exp(-1 * (double)m * theta * iCpx);
std::cout << "m(" << m << "): " << thetExp << std::endl;
}
return 0;
}此版本始终给出正确的结果:
[stix@localhost ~]$ gcc exponentTest.cc -o expTest -lm -lstdc++
[stix@localhost ~]$ ./expTest
Theta: 3.14159
m(1): (-1,-1.22465e-16)
m(2): (1,2.44929e-16)
m(3): (-1,-3.67394e-16)
m(4): (1,4.89859e-16)
m(5): (-1,-6.12323e-16)
m(6): (1,7.34788e-16)
m(7): (-1,-8.57253e-16)
m(8): (1,9.79717e-16)
m(9): (-1,-1.10218e-15)所以问题解决了。但是,我不知道为什么这个问题首先会发生,而且有点像编译器的错误。最糟糕的是,如果没有exp()函数中的m显式转换,这个问题是不一致的;有时它提供了正确的结果,有时却没有。
我的理解是,C++标准要求编译器自动将所有in转换为双倍,如下所示:
double = int * double * int * double;但是编译器显然没有这样做。
这是一个编译器的错误,还是有什么问题,我没有考虑在混合双打和国际单位?
编辑:对于一个注释,指数行应该抛出一个警告,因为一个无符号类型被乘以-1,但是情况并非如此:。
[stix@localhost ~]$ gcc exponentTest.cc -o expTest -lm -lstdc++ -Wall -Wextra -pedantic-errors
exponentTest.cc: In function ‘int main(int, char**)’:
exponentTest.cc:6:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
int main(int argc, char **argv)
^~~~
exponentTest.cc:6:27: warning: unused parameter ‘argv’ [-Wunused-parameter]
int main(int argc, char **argv)
^~~~相关软件版本( GCC 7使用devtoolset- 7 ):
[stix@localhost ~]$ gcc --version
gcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[stix@localhost ~]$ cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
[stix@localhost ~]$ rpm -qa | grep devtoolset
devtoolset-7-gcc-plugin-devel-7.3.1-5.16.el7.x86_64
devtoolset-7-binutils-2.28-11.el7.x86_64
devtoolset-7-gcc-7.3.1-5.16.el7.x86_64
devtoolset-7-gcc-gfortran-7.3.1-5.16.el7.x86_64
devtoolset-7-runtime-7.1-4.el7.x86_64
devtoolset-7-libquadmath-devel-7.3.1-5.16.el7.x86_64
devtoolset-7-gcc-gdb-plugin-7.3.1-5.16.el7.x86_64
devtoolset-7-libstdc++-devel-7.3.1-5.16.el7.x86_64
devtoolset-7-gcc-c++-7.3.1-5.16.el7.x86_64发布于 2021-09-17 22:16:53
最简单的解决方法是将1更改为1.0。这强制使用doubles进行计算:
std::complex<double> thetExp = exp(-1.0 * m * theta * iCpx);为什么会这样呢?让我们看一看失败的表达式:
-1 * m * theta * iCpx这里的类型如下:
int * size_t * double * std::complex<double>C++并不会考虑所有的类型,并选择“最高”来推广所有的东西。相反,它逐个查看二进制操作,从左到右,好像有括号将它们分组如下:
(((int * size_t) * double) * std::complex<double>)您会遇到麻烦,因为首先执行int * size_t。整数提升规则适用,int被转换为size_t,因为在您的平台上,size_t更大。这意味着有符号的32位整数正在转换为64位无符号整数.
试试这个,你就会发现问题所在:
std::cout << (size_t) -1 << "\n";它打印:
18446744073709551615当您将-1 * m更改为-1 * (double) m时,解决了签名/未签名问题。-1被提升为double,这就是简单的-1.0。
发布于 2021-09-17 22:19:46
乘法算子(*)具有从左到右的结合性.因此,在您的论点表达式中:
exp(-1 * m * theta * iCpx)-1 * m首先执行,并使用size_t类型完成。这将导致非常大的数(因为-1被解释为一个无符号常量),当转换/转换为double时,几乎肯定会失去精度。
将一些诊断信息添加到您的循环中会发现这个问题的所有优点:
for (size_t m = 1; m < 10; m++) {
std::cout << (-1 * m) << " " << (-1 * (double)m) << std::endl; // Diagnostic!
std::complex<double> thetExp = exp(-1 * m * theta * iCpx);
std::cout << "m(" << m << "): " << thetExp << std::endl;
}输出:
Theta: 3.14159
18446744073709551615 -1
m(1): (-0.963907,0.26624)
18446744073709551614 -2
m(2): (-0.963907,0.26624)
18446744073709551613 -3
m(3): (-0.963907,0.26624)
18446744073709551612 -4
m(4): (-0.963907,0.26624)
18446744073709551611 -5
m(5): (-0.963907,0.26624)
18446744073709551610 -6
m(6): (-0.963907,0.26624)
18446744073709551609 -7
m(7): (-0.963907,0.26624)
18446744073709551608 -8
m(8): (-0.963907,0.26624)
18446744073709551607 -9
m(9): (-0.963907,0.26624)显式地将m转换为double强制以双精度算法执行初始乘法(正确)。(注意:将m转换为(签名) int也会有效。)
注意clang对此发出警告:
警告:隐式转换更改签名性:“int”改为“无符号长”-W符号-转换
https://stackoverflow.com/questions/69229934
复制相似问题