我天真地假设,复数乘法将被编译器内联,例如,对于这个函数:
#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*=
的实现中似乎没有什么特别的东西可以解释这个魔术:
// 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条答案
按热度按时间7lrncoxx1#
你应该注意到,严格地说,用公式实现复杂的浮点乘法是“错误的”
我写错了引号,因为C++标准实际上并不需要正确的实现。C在某个附录中明确说明了这一点。
简单的实现在输入中使用
Inf
和/或NaN
时不会给予正确的结果。考虑
(Inf + 0*i)*(Inf + 0*i)
:显然,对于一致的行为,结果应该与真实的浮点相同,即分别为Inf
或(Inf + 0*i)
。然而,上面的公式给出Inf + i*NaN
。因此,我可以想象
__muldc3
是一个更长的函数,可以正确处理这些情况。当调用与
-ffast-math
一起消失时,这很可能是解释。s1ag04yj2#
至少对于纯C,gcc遵循有点疯狂的C99附件f(?)复数乘/除的规则; C++也可以。尝试-fast-math或-fcx-fortran-rules。
voase2hg3#
Andreas H.给出了方向,在这个答案中,我只是试着把这些点联系起来。
首先,我对
operator*=
的定义是错误的:对于浮点类型有专门化,例如对于double,这个运算符定义为:_ComplexT
定义为_M_value
定义为这意味着,school-formula用于
std::complex<int>
等类型,但对于浮点类型,它归结为C乘法(__complex__
是gcc扩展),这是标准的一部分,因为C99,附录G(最重要的部分在答案的末尾)。其中一个问题是:
由于约定(G.3.1),第一个因子是非零的,有限的,而第二个因子是无限的,因此由于(G.5.1.4),结果必须是有限的,而
nan+nan*j
不是这种情况。__multdc3
中的计算策略很简单:使用school公式,如果这不起作用(两者都是nan
),将使用更复杂的方法-这只是太复杂了,无法内联。(顺便说一下,clang会内联学校公式,如果这个公式不起作用,就会调用__multdc3
)。另一个问题可能是一个产品上溢/下溢,但两个产品的总和不会。这可能包含在“可能引发虚假浮点异常”的表述中,但我不是100%确定。
第G.3.1节规定:
一个复数或虚数至少有一个无穷部分被认为是无穷大(即使它的另一部分是NaN)。
复数或虚数是一个有限数,如果它的每个部分都是有限数(既不是无限的也不是NaN)。
如果复值或虚数的每个部分都是零,那么它就是零。
第G.5节涉及二元运算符和状态:
对于大多数操作数类型,具有虚操作数或复操作数的二元运算符的结果的值完全由通常的数学公式参考真实的算术确定。对于某些操作数类型,通常的数学公式是有问题的,因为它对无穷大的处理和由于不适当的上溢或下溢;在这些情况下,结果满足某些性质(在G.5.1中规定),但不完全确定。
第G.5.1节涉及乘法算子和状态:
1.* 和/运算符满足所有真实的、虚数和复数操作数的下列无穷大属性:
...
1.如果 * 运算符的两个操作数都是复数,或者如果/运算符的第二个操作数是复数,则该运算符在适合计算结果部分时引发浮点异常,并且可能引发虚假浮点异常。
vc9ivgsu4#
这可能是因为软件浮点运算在默认情况下是开启的。根据gcc手册页:
尝试使用
-mdpfp
或任何其他替代方法进行编译。