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

300

Хорошо известно, что 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 C ++ 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 C ++, IEEE 754), и почему существует разница в поведении между компиляторами?

imallett
источник
18
Javascript и Python (numpy) не имеют такого поведения. Nan-NaNесть NaN. Perl и Scala также ведут себя одинаково.
Пол
33
Может быть, вы включили небезопасные математические оптимизации (эквивалент -ffast-mathgcc)?
Маттео Италия
5
@nm: не правда. Приложение F, которое является необязательным, но нормативным, когда оно поддерживается, и которое необходимо для определения поведения с плавающей запятой вообще , в основном включает IEEE 754 в C.
R .. GitHub STOP HELPING ICE
5
Если вы хотите спросить о стандарте IEEE 754, укажите это где-нибудь в вопросе.
нет. местоимения м.
68
Я был уверен, что этот вопрос был о JavaScript из заголовка.
MikeTheLiar

Ответы:

300

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

Петр Абдулин
источник
15
Я просто пробовал это сам. Действительно, указание точного или строгого решает проблему.
Ималлетт
67
Я хотел бы одобрить решение Intel по умолчанию /fp:fast: если вы хотите что-то безопасное , вам, вероятно, лучше избегать появления NaN, и вообще не использовать ==с числами с плавающей запятой. Полагаясь на странную семантику, которую IEEE754 назначает NaN, возникает проблема.
оставлено около
10
@leftaroundabout: Что вы находите странным в NaN, кроме ужасного решения IMHO, что NaN! = NaN вернет истину?
суперкат
21
У NaN есть важное применение - они могут обнаруживать исключительные ситуации, не требуя тестов после каждого расчета. Не каждый разработчик с плавающей точкой нуждается в них, но не отклоняет их.
Брюс Доусон
6
@supercat Из любопытства согласны ли вы с решением о NaN==NaNвозвращении false?
Кайл Стрэнд
53

Это . , , не может быть правым, верно? Мой вопрос: что об этом говорят соответствующие стандарты (ISO C, ISO C ++, 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»)

ouah
источник
20

Поскольку я вижу ответ, оспаривающий соответствие стандартам компилятора 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 обратно в это решение.

zwol
источник
Обратите внимание, что с -ffast-math, gccбольше не соответствует ISO 9899: 2011 в отношении арифметики с плавающей запятой.
fuz
1
@FUZxxl Да, дело в том, что оба компилятора имеют несовместимый режим с плавающей точкой, просто icc по умолчанию использует этот режим, а gcc - нет.
15:06
4
Просто для того, чтобы бросить топливо в огонь, мне очень нравится выбор Intel по умолчанию включить быструю математику. Весь смысл использования поплавков - получить высокую пропускную способность.
Навин