Почему NaN - NaN == 0.0 с компилятором Intel C ++?

302

Хорошо известно, что NaN распространяются в арифметике, но я не смог найти никаких демонстраций, поэтому написал небольшой тест:

#include <limits>
#include <cstdio>

int main(int argc, char* argv[]) {
    float qNaN = std::numeric_limits<float>::quiet_NaN();

    float neg = -qNaN;

    float sub1 = 6.0f - qNaN;
    float sub2 = qNaN - 6.0f;
    float sub3 = qNaN - qNaN;

    float add1 = 6.0f + qNaN;
    float add2 = qNaN + qNaN;

    float div1 = 6.0f / qNaN;
    float div2 = qNaN / 6.0f;
    float div3 = qNaN / qNaN;

    float mul1 = 6.0f * qNaN;
    float mul2 = qNaN * qNaN;

    printf(
        "neg: %f\nsub: %f %f %f\nadd: %f %f\ndiv: %f %f %f\nmul: %f %f\n",
        neg, sub1,sub2,sub3, add1,add2, div1,div2,div3, mul1,mul2
    );

    return 0;
}

Пример (работает в прямом эфире здесь) дает в основном то, что я ожидаю (отрицательный немного странный, но это имеет смысл)

neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

MSVC 2015 создает нечто подобное. Однако Intel С++ 15 производит:

neg: -nan(ind)
sub: nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

В частности, qNaN - qNaN == 0.0.

Это... не может быть правильным, не так ли? Что об этом говорят соответствующие стандарты (ISO C, ISO С++, IEEE 754) и почему существует разница в поведении между компиляторами?

  • 18
    Javascript и Python (numpy) не имеют такого поведения. Nan-NaN - это NaN . Perl и Scala также ведут себя одинаково.
  • 33
    Может быть, вы включили небезопасные математические оптимизации (эквивалент -ffast-math на gcc)?
Показать ещё 25 комментариев
Теги:
floating-point
ieee-754
icc

3 ответа

294

Обработка с плавающей запятой по умолчанию в компиляторе Intel С++ составляет /fp:fast, которая обрабатывает NaN unsafely (что также приводит к тому, что NaN == NaN является true). Попробуйте указать /fp:strict или /fp:precise и посмотрите, поможет ли это.

  • 15
    Я просто пробовал это сам. Действительно, указание точного или строгого исправляет проблему.
  • 65
    Я хотел бы одобрить решение Intel по умолчанию использовать /fp:fast : если вы хотите что-то безопасное , вам, вероятно, лучше избегать появления NaN, и обычно не используйте == с числами с плавающей запятой. Полагаясь на странную семантику, которую IEEE754 назначает NaN, возникают проблемы.
Показать ещё 37 комментариев
52

Это., не может быть прав, не так ли? Мой вопрос: что говорят об этом соответствующие стандарты (ISO C, ISO С++, IEEE 754)?

Петр Абдулин уже ответил, почему компилятор дает ответ 0.0.

Вот что говорит IEEE-754: 2008:

(6.2 Операции с NaN) "[...] Для операции с тихими входами NaN, отличными от максимальных и минимальных операций, если результат с плавающей запятой должен быть доставлен, результат должен быть тихим NaN, который должен быть один из входных NaNs."

Таким образом, единственным допустимым результатом для вычитания двух тихих операндов NaN является тихое NaN; любой другой результат недействителен.

В стандарте C говорится:

(C11, F.9.2 Преобразования выражения p1) "[...]

x - x → 0. 0" Выражения x - x и 0. 0 не эквивалентны, если x является NaN или бесконечное "

(где здесь NaN обозначает тихое NaN согласно F.2.1p1 "Эта спецификация не определяет поведение сигнальных NaNs. Обычно он использует термин NaN для обозначения тихих NaN" )

17

Поскольку я вижу ответ, оспаривающий соответствие стандартов компилятору Intel, и никто другой не упомянул об этом, я укажу, что и GCC, и Clang имеют режим, в котором они делают что-то очень похожее. Их поведение по умолчанию IEEE-совместимо -

$ g++ -O2 test.cc && ./a.out 
neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

$ clang++ -O2 test.cc && ./a.out 
neg: -nan
sub: -nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

- но если вы попросите скорость за счет правильности, вы получите то, что вы просите -

$ g++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: nan nan 0.000000
add: nan nan
div: nan nan 1.000000
mul: nan nan

$ clang++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: -nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

Я считаю, что вполне справедливо критиковать выбор ICC по умолчанию, но я бы не стал читать все войны Unix в этом решении.

  • 0
    Обратите внимание, что с -ffast-math gcc больше не соответствует ISO 9899: 2011 в отношении арифметики с плавающей запятой.
  • 1
    @FUZxxl Да, дело в том, что оба компилятора имеют несовместимый режим с плавающей запятой, просто icc использует этот режим по умолчанию, а gcc - нет.
Показать ещё 1 комментарий
Сообщество Overcoder
Наверх
Меню