Mengapa NaN - NaN == 0,0 dengan Intel C ++ Compiler?

300

Sudah diketahui umum bahwa NaN menyebar dalam aritmatika, tetapi saya tidak dapat menemukan demonstrasi, jadi saya menulis tes kecil:

#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;
}

Contoh ( menjalankan langsung di sini ) pada dasarnya menghasilkan apa yang saya harapkan (yang negatif sedikit aneh, tapi agak masuk akal):

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

MSVC 2015 menghasilkan sesuatu yang serupa. Namun, Intel C ++ 15 menghasilkan:

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

Secara khusus qNaN - qNaN == 0.0,.

Ini ... tidak mungkin benar, bukan? Apa yang dikatakan oleh standar yang relevan (ISO C, ISO C ++, IEEE 754), dan mengapa ada perbedaan perilaku di antara kompiler?

Imallett
sumber
18
Javascript dan Python (numpy) tidak memiliki perilaku ini. Nan-NaNadalah NaN. Perl dan Scala juga berperilaku serupa.
Paul
33
Mungkin Anda mengaktifkan optimisasi matematika yang tidak aman (setara dengan -ffast-mathpada gcc)?
Matteo Italia
5
@nm: Tidak benar. Lampiran F, yang merupakan opsional, tetapi normatif bila didukung, dan perlu untuk memiliki floating perilaku titik tertentu sama sekali , pada dasarnya menggabungkan IEEE 754 ke C.
R .. GitHub BERHENTI MEMBANTU ICE
5
Jika Anda ingin bertanya tentang standar IEEE 754, sebutkan di suatu tempat dalam pertanyaan.
n. 'kata ganti' m.
68
Saya yakin pertanyaan ini tentang JavaScript dari judul.
MikeTheLiar

Jawaban:

300

Default floating point penanganan di Intel C ++ compiler /fp:fast, yang menangani NaN's tidak aman (yang juga menghasilkan NaN == NaNmenjadi truemisalnya). Coba tentukan /fp:strictatau /fp:precisedan lihat apakah itu membantu.

Petr Abdulin
sumber
15
Saya hanya mencoba ini sendiri. Memang, menentukan secara tepat atau ketat memperbaiki masalah.
imallett
67
Saya ingin mendukung keputusan Intel untuk default ke /fp:fast: jika Anda menginginkan sesuatu yang aman , Anda mungkin sebaiknya menghindari NaN muncul di tempat pertama, dan umumnya tidak digunakan ==dengan angka floating-point. Mengandalkan semantik aneh yang diberikan IEEE754 ke NaN meminta masalah.
leftaroundabout
10
@ Leftaroundabout: Apa yang Anda temukan aneh tentang NaN, selain dari keputusan mengerikan IMHO untuk memiliki NaN! = NaN kembali benar?
supercat
21
NaNs memiliki kegunaan penting - mereka dapat mendeteksi situasi luar biasa tanpa memerlukan tes setelah setiap perhitungan. Tidak semua pengembang floating-point membutuhkannya tetapi jangan mengabaikannya.
Bruce Dawson
6
@supercat Karena penasaran, apakah Anda setuju dengan keputusan untuk NaN==NaNkembali false?
Kyle Strand
53

Ini . . tidak mungkin benar, kan? Pertanyaan saya: apa yang dikatakan oleh standar yang relevan (ISO C, ISO C ++, IEEE 754) tentang ini?

Petr Abdulin sudah menjawab mengapa kompiler memberikan 0.0jawaban.

Inilah yang dikatakan IEEE-754: 2008:

(6.2 Operasi dengan NaN) "[...] Untuk operasi dengan input NaN yang sunyi, selain operasi maksimum dan minimum, jika hasil floating-point akan dikirimkan hasilnya harus berupa NaN yang tenang yang harus menjadi salah satu masukan NaN. "

Jadi satu-satunya hasil yang valid untuk pengurangan dua operan NaN yang tenang adalah NaN yang tenang; hasil lainnya tidak valid.

Standar C mengatakan:

(C11, F.9.2 Transformasi ekspresi p1) "[...]

x - x → 0. 0 "Ekspresi x - x dan 0. 0 tidak setara jika x adalah NaN atau tidak terbatas"

(di mana di sini NaN menunjukkan NaN yang tenang sesuai F.2.1p1 "Spesifikasi ini tidak menentukan perilaku pensinyalan NaN. Biasanya menggunakan istilah NaN untuk menunjukkan NaN yang tenang")

ouah
sumber
20

Karena saya melihat jawaban yang mengganggu kepatuhan standar pada kompiler Intel, dan tidak ada orang lain yang menyebutkan ini, saya akan menunjukkan bahwa baik GCC dan Dentang memiliki mode di mana mereka melakukan sesuatu yang sangat mirip. Perilaku default mereka adalah sesuai dengan 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

- tetapi jika Anda meminta kecepatan dengan mengorbankan kebenaran, Anda mendapatkan apa yang Anda minta -

$ 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

Saya pikir itu sepenuhnya adil untuk mengkritik pilihan default ICC , tapi saya tidak akan membaca seluruh perang Unix kembali ke keputusan itu.

zwol
sumber
Perhatikan bahwa dengan -ffast-math, gcctidak mematuhi ISO 9899: 2011 sehubungan dengan aritmatika floating point lagi.
fuz
1
@ FuZxxl Ya, intinya adalah bahwa kedua kompiler memiliki mode floating-point tidak patuh, hanya saja icc default ke mode itu dan gcc tidak.
zwol
4
Hanya untuk membakar bahan bakar, saya sangat suka pilihan Intel untuk mengaktifkan matematika cepat secara default. Inti dari menggunakan pelampung adalah untuk mendapatkan throughput yang tinggi.
Navin