首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么这段代码在使用int时失败,而在使用double (可能的编译器错误)时失败?

为什么这段代码在使用int时失败,而在使用double (可能的编译器错误)时失败?
EN

Stack Overflow用户
提问于 2021-09-17 22:05:37
回答 2查看 95关注 0票数 2

在GCC 7.3.1中给出了以下非常简单的程序:

代码语言:javascript
复制
//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时,此代码提供了一个不正确的结果:

代码语言:javascript
复制
[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替换了它,单元测试也起了作用,但是我使用它的更广泛的代码库没有做到这一点。

代码语言:javascript
复制
//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; 
}

此版本始终给出正确的结果:

代码语言:javascript
复制
[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转换为双倍,如下所示:

代码语言:javascript
复制
double = int * double * int * double;

但是编译器显然没有这样做。

这是一个编译器的错误,还是有什么问题,我没有考虑在混合双打和国际单位?

编辑:对于一个注释,指数行应该抛出一个警告,因为一个无符号类型被乘以-1,但是情况并非如此:

代码语言:javascript
复制
[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 ):

代码语言:javascript
复制
[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
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-09-17 22:16:53

最简单的解决方法是将1更改为1.0。这强制使用doubles进行计算:

代码语言:javascript
复制
std::complex<double> thetExp = exp(-1.0 * m * theta * iCpx);

为什么会这样呢?让我们看一看失败的表达式:

代码语言:javascript
复制
-1 * m * theta * iCpx

这里的类型如下:

代码语言:javascript
复制
int * size_t * double * std::complex<double>

C++并不会考虑所有的类型,并选择“最高”来推广所有的东西。相反,它逐个查看二进制操作,从左到右,好像有括号将它们分组如下:

代码语言:javascript
复制
(((int * size_t) * double) * std::complex<double>)

您会遇到麻烦,因为首先执行int * size_t。整数提升规则适用,int被转换为size_t,因为在您的平台上,size_t更大。这意味着有符号的32位整数正在转换为64位无符号整数.

试试这个,你就会发现问题所在:

代码语言:javascript
复制
std::cout << (size_t) -1 << "\n";

它打印:

代码语言:javascript
复制
18446744073709551615

当您将-1 * m更改为-1 * (double) m时,解决了签名/未签名问题。-1被提升为double,这就是简单的-1.0

票数 5
EN

Stack Overflow用户

发布于 2021-09-17 22:19:46

乘法算子(*)具有从左到右的结合性.因此,在您的论点表达式中:

代码语言:javascript
复制
exp(-1 * m * theta * iCpx)

-1 * m首先执行,并使用size_t类型完成。这将导致非常大的数(因为-1被解释为一个无符号常量),当转换/转换为double时,几乎肯定会失去精度。

将一些诊断信息添加到您的循环中会发现这个问题的所有优点:

代码语言:javascript
复制
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;
}

输出:

代码语言:javascript
复制
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符号-转换

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69229934

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档