我天真地假设,复数乘法将由编译器内联,例如对于此函数:
#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;
}
我必须遗漏一些东西,因此我的问题是:产生汇编程序的原因是什么?
答案 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节涉及乘法运算符和状态:
- 醇>
*和/运算符满足以下无限属性 所有真实,虚构和复杂的操作数:
- 如果一个操作数是无穷大而另一个操作数是a 非零有限数或无穷大,则结果为 operator是无穷大;
...
- 如果*运算符的两个操作数都很复杂或者/运算符的第二个操作数很复杂,则运算符会引发浮点异常(如果适用于计算结果的部分),并可能引发虚假浮点异常。
醇>
答案 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
或任何其他替代方案进行编译。