Чому 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
Я просто пробував це сам. Дійсно, конкретизуючи або чітко, або чітко виправляє проблему.
imallett

67
Я хотів би схвалити рішення Intel за замовчуванням /fp:fast: якщо ви хочете чогось безпечного , вам, мабуть, краще у першу чергу уникати появи NN, і взагалі не користуватися ==номерами з плаваючою комою. Опираючись на дивну семантику, яку IEEE754 призначає NaN, це задає проблеми.
близько

10
@leftaroundabout: Що ти дивишся щодо NaN, окрім жахливого рішення IMHO мати NaN! = NaN return true?
supercat

21
NaNs мають важливе використання - вони можуть виявляти виняткові ситуації, не вимагаючи тестів після кожного розрахунку. Не кожен розробник з плаваючою комою потребує їх, але не відхиляйте їх.
Брюс Доусон

6
@supercat З цікавості, чи погоджуєтесь ви з рішенням NaN==NaNповернутись false?
Кайл Странд

53

Це. . . не може бути прав, правда? Моє запитання: що про це говорять відповідні стандарти (ISO C, ISO C ++, IEEE 754)?

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

Ось що говорить IEEE-754: 2008:

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

Таким чином, єдиним правильним результатом віднімання двох тихих операндів NaN є тихий NaN; будь-який інший результат недійсний.

Стандарт C говорить:

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

x - x → 0. 0 "Вирази x - x і 0. 0 не еквівалентні, якщо x - NaN або нескінченна"

(де тут NaN позначає спокійний NaN згідно з F.2.1p1 "Ця специфікація не визначає поведінку сигналізації NaN. Зазвичай він використовує термін NaN для позначення тихих NaNs")


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 - ні.
zwol

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