当两个std :: complex相乘时,为什么调用__muldc3?

时间:2018-03-22 20:47:02

标签: c++ gcc optimization complex-numbers

我天真地假设,复数乘法将由编译器内联,例如对于此函数:

#include <complex>

void mult(std::complex<double> &a, std::complex<double> &b){
    a*=b;
}

但是,当按照gcc(-O2)进行编译时,resulting assembler令人惊讶(至少对我而言):

mult(std::complex<double>&, std::complex<double>&):
        pushq   %rbx
        movsd   8(%rdi), %xmm3
        movsd   (%rdi), %xmm2
        movq    %rdi, %rbx
        movsd   8(%rsi), %xmm1
        movsd   (%rsi), %xmm0
        call    __muldc3
        movsd   %xmm0, (%rbx)
        movsd   %xmm1, 8(%rbx)
        popq    %rbx
        ret

调用此函数__multdc3,它以某种方式取代了对operator*=的调用(其错位名称为_ZNSt7complexIdEmLIdEERS0_RKS_IT_E,复数将按引用传递)。

但是,operator*=的{​​{3}}似乎没有任何特殊内容可以解释这种魔力:

// 26.2.5/13
  // XXX: This is a grammar school implementation.
  template<typename _Tp>
    template<typename _Up>
    complex<_Tp>&
    complex<_Tp>::operator*=(const complex<_Up>& __z)
    {
      const _Tp __r = _M_real * __z.real() - _M_imag * __z.imag();
      _M_imag = _M_real * __z.imag() + _M_imag * __z.real();
      _M_real = __r;
      return *this;
}

我必须遗漏一些东西,因此我的问题是:产生汇编程序的原因是什么?

4 个答案:

答案 0 :(得分:4)

你应该注意到,严格来说,这是错误的&#34;通过公式

实现复杂的浮点乘法
(a+i*b)*(c + i*d) = a*c - b*d + i*(b*c + a*d)

我在引号中写错了,因为C ++标准实际上并不需要正确的实现。 C确实在一些附录中指定了它。

简单的实现无法在输入中使用Inf和/或NaN提供正确的结果。

考虑(Inf + 0*i)*(Inf + 0*i):显然,对于一致的行为,结果应该与实际浮点相同,分别是Inf(Inf + 0*i)。但是,上面的公式给出了Inf + i*NaN

所以我可以想象__muldc3是一个更长的函数来正确处理这些情况。

当电话以-ffast-math消失时,这很可能是解释。

答案 1 :(得分:2)

至少对于普通的C,gcc遵循复杂乘法/除法的有些疯狂的C99附录f(?)规则;也许对于c ++也是如此。尝试-fast-math或-fcx-fortran-rules。

答案 2 :(得分:1)

Andreas H.的答案给出了指示,在这个答案中我只想尝试连接点。

首先,我对operator*=的定义有误:floating types有一个专门化,例如对于此运算符defined as的双打:

  template<typename _Tp>
    complex&
    operator*=(const complex<_Tp>& __z)
{
  _ComplexT __t;
  __real__ __t = __z.real();
  __imag__ __t = __z.imag();
  _M_value *= __t;
  return *this;
}

使用_ComplexT defined as

typedef __complex__ double _ComplexT;

_M_value defined as

private:
   _ComplexT _M_value;

这意味着,学校公式用于std::complex<int>等类型,但对于浮动类型,它归结为C乘法(__complex__是gcc扩展),这是一个部分自C99以来的标准,附录G(最重要的部分在答案的最后)。

其中一个有问题的案例是:

(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
                        =  nan + nan*j

由于惯例(G.3.1),第一个因子是非零的,有限的,第二个因子是无限的,因此(G.5.1.4)结果必须是有限的,而{{{ 1}}。

nan+nan*j中的计算策略很简单:使用学校公式,如果这不起作用(两者都是__multdc3),将使用更复杂的方法 - 这是太复杂,无法内联。 (顺便说一下,如果这个公式不起作用,clang会勾勒出学校的公式并致电nan

另一个问题可能是一个产品溢出/下溢但两个产品的总和并不存在。这可能是由公式所涵盖的,可能会引起虚假的浮点异常,但我并非100%肯定。

G.3.1 部分声明:

  

具有至少一个无限部分的复数或虚数值被视为无穷大(即使其它部分是NaN)。

     

如果复数或虚数的每个部分都是有限数(既不是无穷大也不是NaN),则它是有限数。

     

如果每个部分都为零,则复数或虚数值为零。

G.5节涉及二元运算符和状态:

  

对于大多数操作数类型,二元运算符的结果值   具有虚构或复杂操作数的完全确定   通过通常的数学公式来参考实数算术。对于   一些操作数类型,通常的数学公式是   因为它对无穷大的处理而且因为它是有问题的   过度上溢或下溢;在这些情况下,结果满足   某些属性(在G.5.1中指定),但并非完全   确定。

G.5.1节涉及乘法运算符和状态:

  
      
  1. *和/运算符满足以下无限属性   所有真实,虚构和复杂的操作数:

         

    - 如果一个操作数是无穷大而另一个操作数是a   非零有限数或无穷大,则结果为   operator是无穷大;

  2.         

    ...

         
        
    1. 如果*运算符的两个操作数都很复杂或者/运算符的第二个操作数很复杂,则运算符会引发浮点异常(如果适用于计算结果的部分),并可能引发虚假浮点异常。
    2.   

答案 3 :(得分:0)

可能是因为默认情况下软件浮点数在。根据gcc的手册页:

-msoft-float
This option ignored; it is provided for compatibility purposes only.    
software floating point code is emitted by default,
and this default can overridden by FPX options; mspfp, mspfp-compact, 
or mspfp-fast for single precision, and mdpfp, mdpfp-compact, or mdpfp-fast 
for double precision.

尝试使用-mdpfp或任何其他替代方案进行编译。