【发布时间】:2021-09-17 22:05:37
【问题描述】:
鉴于 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;
}
当 theta 为 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 的使用。我用 unsigned int 替换它,并且单元测试有效,但我使用它的更广泛的代码库没有。沮丧的是,我最终明确地将 std::exp 的输入转换为双精度:
//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++ 标准要求编译器自动将所有整数转换为双精度值,如下所示:
double = int * double * int * double;
但编译器显然没有这样做。
这是编译器错误还是在混合双精度和整数时我没有考虑到一些问题?
编辑:对于其中一个 cmets,指数行应引发警告,因为无符号类型正在乘以 -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
【问题讨论】:
-
m是size_t,这是一个无符号类型。乘以 -1 可能会给你一个警告。我建议调高你的警告级别。 -
@ChrisMM 公平点,但是由于那里有一个双精度数,编译器不应该将整个事情转换为双精度数吗?这不是 C 标准吗?
-
@KamilCuk 好的,克里斯说它至少应该发出警告,甚至在 -Wall -Wextra -pedantic-errors 上都没有
-
您正在寻找的警告选项是
-Wsign-conversion。我通过在 Clang 中使用-Weverything找到了这一点,收到了相关警告,注意到消息中提供的警告选项,并在 GCC 中尝试,因为 Clang 与 GCC 非常兼容。 -
只是关于术语的注释:该表达式中不涉及演员表。有隐式转换。强制转换是您在源代码中编写的内容,用于告诉编译器进行转换。
标签: c++