Разделение по комплексу <double> в clang++ против g++
Когда я компилирую следующий код с 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?
Update:
Спасибо за ваши ответы! Я сообщил об ошибке: http://llvm.org/bugs/show_bug.cgi?id=19820
Ответы
Ответ 1
Стандарт говорит в [complex.numbers]
(26.4/3):
Если результат функции не определен математически или нет в диапазон представляемых значений для своего типа, поведение undefined.
Нет никаких подробностей о том, как должно выполняться разделение для сложных чисел. Только в [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
, и это число находится в диапазоне представляемых значений для a double
, поведение корректно определено.
Таким образом, gcc получает это правильно, а clang имеет ошибку.
Ответ 2
Итак, у меня есть эта дикая догадка, что в clang комплексное числовое деление реализовано, как описано в wiki: http://en.wikipedia.org/wiki/Complex_number#Multiplication_and_division.
Видно, что знаменатель имеет вид c^2 + d^2
. Таким образом, квадрат 1.e-162
фактически выпадает из представленного диапазона IEE754 для double
, который равен std::numeric_limits<double>::min()
- 2.22507e-308
, и у нас есть underflow.
gcc
как-то это работает, но если clang
делает простой квадрат, согласно стандартной цитате из 40 он входит в UB и рассматривает его как 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
и -nan
, я не знаю, что IEE754 достаточно для оценки (0.0 * 1.e-104 - 1.0 * 0.0)
. Но все кажется непротиворечивым.
Ответ 3
Update:
Цитата из стандарта:
Если во время оценки выражения результат не математически определены или нет в диапазоне представимых значений для его тип поведение не определено. [Примечание: большинство существующих реализации С++ игнорировать целочисленные потоки. Лечение деления на нуль, образуя остаток с использованием делителя нуля и все возникающие точечные исключения различаются между машинами и обычно регулируются библиотечная функция].
Цитата из http://lists.freebsd.org/pipermail/freebsd-numerics/2014-March/000549.html:
Похоже, что разработчики clang выбрали наивный комплекс алгоритм разделения.
...
Я сделал немного grepping. Может быть, алгоритм деления содержится в файле src/contrib/llvm/tools/clang/lib/CodeGen/CGExprComplex.cpp внутри функция ComplexExprEmitter:: EmitBinDiv?
Если вы посмотрите на код, он, безусловно, выглядит как генерирующий код для выполнения сложного деления, и это определенно выглядит так, как будто они используя наивный алгоритм.
Предполагая, что в действительности кланг использует наивное комплексное деление, выражение 1.0 / c
оценивается в соответствии с наивной реализацией комплексного деления следующим выражением
,
![enter image description here]()
1.e-324 выходит из двойного диапазона. Это соответствует стандарту undefined поведения.
Также сделав поиск в списке ошибок LLVM/Clang, похоже, что существует довольно много проблем, связанных с сложным делением.
Таким образом, ваш случай является ошибкой, и вы должны сообщить об этом.
Для тех, кто заинтересован в том, как внедрено надежное комплексное подразделение, посмотрите
Ответ 4
Да. Вы должны сообщить об ошибке.
Комплексное число 1e-162 + 0i идентично действительному числу 1e-162. Это находится в пределах двойного. Взаимная величина этого значения равна 1е + 162. Любое другое значение представляет ошибку в арифметических библиотеках.
Значение, возвращаемое clang, неверно. Это ошибка.