当我使用g ++(4.8.1或4.9.0)或clang ++(3.4)编译以下代码时,我得到不同的输出。
#include <iostream>
#include <complex>
int main() {
std::complex<double> c = {1.e-162,0};
std::cout << 1.0/c << std::endl;
return 0;
}
克++:
(1e+162,0)
铛++:
(inf,-nan)
这是clang中的错误吗?
更新
感谢您的回答!我报告了这个错误:http://llvm.org/bugs/show_bug.cgi?id=19820
答案 0 :(得分:5)
标准在[complex.numbers]
(26.4 / 3)中说:
如果函数的结果未在数学上定义或未在中 它的类型的可表示值的范围,行为是 未定义。
没有具体说明如何对复数进行划分。仅在[complex.member.ops]
中说明:
complex<T>& operator/=(const complex<T>& rhs);
效果:将复数值
rhs
除以复数值*this
并将商存储在*this
中。 返回:*this
。
和[complex.ops]
:
template<class T> complex<T> operator/(const T& lhs, const complex<T>& rhs);
返回:
complex<T>(lhs) /= rhs
。
由于1.e-162
的倒数为1.e+162
且此数字位于double
的可表示值范围内,因此行为已明确定义。
因此gcc说得对,clang有一个bug。
答案 1 :(得分:4)
好的,我有这个疯狂的猜测,在clang中复杂的数字除法的实现就像在wiki:http://en.wikipedia.org/wiki/Complex_number#Multiplication_and_division上所描述的那样。
可以看出分母的格式为c^2 + d^2
。因此,1.e-162
平方实际上超出了double
的{{1}} std::numeric_limits<double>::min()
的IEE754可表示范围,并且我们有下溢。
2.22507e-308
以某种方式解决了这个问题,但如果gcc
执行简单的方格,则根据@ 40的标准引用,它会进入UB,并在执行后将其视为clang
0
。
我测试了1.e-162^2 + 0.0^2
和clang
的数字,这个数字在平方时不会导致下溢。
gcc
结果很好:
#include <iostream>
#include <complex>
int main() {
std::complex<double> c = {1.e-104,0};
std::cout << 1.0/c << std::endl;
return 0;
}
不确定这是不是一个错误。但我认为这是正在发生的事情。
附录:
如果一个人手动评估这些表达式, luk32:~/projects/tests$ g++ --std=c++11 ./complex_div.cpp
luk32:~/projects/tests$ ./a.out
(1e+104,0)
luk32:~/projects/tests$ clang++ --std=c++11 ./complex_div.cpp
luk32:~/projects/tests$ ./a.out
(1e+104,0)
也是一致的
我们得到:
(inf,-nan)
我对real = (ac+bd) / (o) - real part
imag = (bc-ad) / (o) - imaginary part
{a,b} = {1.0, 0.0}
{c,d} = {1.e-104, 0.0}
o is (c^2 + d^2) and we assume it is 0.0.
real / o = (1.e-104 * 1.0 + 0.0 * 0.0) / o = 1/o = inf
imag / o = (0.0 * 1.e-104 - 1.0 * 0.0) / o = -0.0 / o = -nan
和-0.0
的标志并不是绝对确定,我不知道IEE754是否足以评估-nan
。但一切似乎都是一致的。
答案 2 :(得分:2)
<强>更新强>
引用标准:
如果在评估表达式期间,结果不是 在数学上定义或不在可表示值的范围内 它的类型,行为未定义。 [注:大多数现有 C ++的实现忽略整数流。治疗师 零,使用零除数形成余数,并且全部浮动 点异常因机器而异,通常可通过a调整 库函数]。
引自 http://lists.freebsd.org/pipermail/freebsd-numerics/2014-March/000549.html:
似乎clang开发人员选择了天真的复合体 除法算法。
...
我做了一些grepping。可能是划分算法 包含在文件中 src / contrib / llvm / tools / clang / lib / CodeGen / CGExprComplex.cpp里面 function ComplexExprEmitter :: EmitBinDiv?
如果你看一下代码,它看起来肯定是在生成代码 执行复杂的划分,它看起来确实如此 使用天真算法。
假设clang确实使用了幼稚的复数除法,表达式1.0 / c
根据复杂除法的朴素实现来评估以下表达式
,
1.e-324超出双重范围。这是根据未定义行为的标准得出的结果。
同样在LLVM/Clang bug list进行搜索,似乎有很多关于复杂分割的问题。
因此,您的案例是个错误,您应该报告。
对于对如何实施复杂分工感兴趣的人,请查看
答案 3 :(得分:1)
是。你应该报告一个错误。
复数1e-162 + 0i与实数1e-162相同。这完全落在双倍的范围内。该值的倒数应为1e + 162。任何其他值都代表算术库中的错误。
clang返回的值是错误的。这是一个错误。