it-swarm.com.ru

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

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

297
imallett

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

297
Petr Abdulin

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

52
ouah

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

19
zwol