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


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»)


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 обратно в это решение.


Обратите внимание, что с -ffast-math, gccбольше не соответствует ISO 9899: 2011 в отношении арифметики с плавающей запятой.
fuz

1
@FUZxxl Да, дело в том, что оба компилятора имеют несовместимый режим с плавающей точкой, просто icc по умолчанию использует этот режим, а gcc - нет.
15:06

4
Просто для того, чтобы бросить топливо в огонь, мне очень нравится выбор Intel по умолчанию включить быструю математику. Весь смысл использования поплавков - получить высокую пропускную способность.
Навин
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.