Разделение по комплексу <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,

enter image description here

1.e-324 выходит из двойного диапазона. Это соответствует стандарту undefined поведения.

Также сделав поиск в списке ошибок LLVM/Clang, похоже, что существует довольно много проблем, связанных с сложным делением.

Таким образом, ваш случай является ошибкой, и вы должны сообщить об этом.

Для тех, кто заинтересован в том, как внедрено надежное комплексное подразделение, посмотрите

Ответ 4

Да. Вы должны сообщить об ошибке.

Комплексное число 1e-162 + 0i идентично действительному числу 1e-162. Это находится в пределах двойного. Взаимная величина этого значения равна 1е + 162. Любое другое значение представляет ошибку в арифметических библиотеках.

Значение, возвращаемое clang, неверно. Это ошибка.