Ketika sebuah operasi menghasilkan NaN yang tenang, tidak ada indikasi bahwa ada sesuatu yang tidak biasa hingga program memeriksa hasilnya dan melihat NaN. Artinya, komputasi berlanjut tanpa sinyal apa pun dari floating point unit (FPU) atau pustaka jika floating-point diimplementasikan dalam perangkat lunak. NaN pensinyalan akan menghasilkan sinyal, biasanya dalam bentuk pengecualian dari FPU. Apakah pengecualian dilempar tergantung pada status FPU.
C ++ 11 menambahkan beberapa kontrol bahasa pada lingkungan floating-point dan menyediakan cara standar untuk membuat dan menguji NaN . Namun, apakah kontrol diimplementasikan tidak terstandarisasi dengan baik dan pengecualian floating-point biasanya tidak ditangkap dengan cara yang sama seperti pengecualian C ++ standar.
Dalam sistem POSIX / Unix, pengecualian floating point biasanya ditangkap dengan menggunakan handler untuk SIGFPE .
Bagaimana qNaNs dan sNaNs terlihat secara eksperimental?
Pertama-tama, mari pelajari cara mengidentifikasi apakah kita memiliki sNaN atau qNaN.
Saya akan menggunakan C ++ dalam jawaban ini daripada C karena ia menawarkan kemudahan
std::numeric_limits::quiet_NaN
danstd::numeric_limits::signaling_NaN
yang tidak dapat saya temukan di C dengan nyaman.Namun saya tidak dapat menemukan fungsi untuk mengklasifikasikan jika NaN adalah sNaN atau qNaN, jadi mari kita mencetak byte mentah NaN:
main.cpp
Kompilasi dan jalankan:
keluaran pada mesin x86_64 saya:
Kami juga dapat menjalankan program di aarch64 dengan mode pengguna QEMU:
dan menghasilkan keluaran yang sama persis, menunjukkan bahwa beberapa arch mengimplementasikan IEEE 754 dengan cermat.
Pada titik ini, jika Anda tidak terbiasa dengan struktur bilangan floating point IEEE 754, lihatlah: Apa itu bilangan floating point subnormal?
Dalam biner beberapa nilai di atas adalah:
Dari percobaan ini kami mengamati bahwa:
qNaN dan sNaN tampaknya hanya dibedakan dengan bit 22: 1 berarti tenang, dan 0 berarti pensinyalan
infinitas juga sangat mirip dengan eksponen == 0xFF, tetapi memiliki pecahan == 0.
Karena alasan ini, NaN harus menyetel bit 21 ke 1, jika tidak maka tidak mungkin membedakan sNaN dari tak terhingga positif!
nanf()
menghasilkan beberapa NaN berbeda, jadi harus ada beberapa kemungkinan pengkodean:Karena
nan0
sama denganstd::numeric_limits<float>::quiet_NaN()
, kami menyimpulkan bahwa mereka semua adalah NaN pendiam yang berbeda.The C11 N1570 rancangan standar menegaskan bahwa
nanf()
menghasilkan NaN tenang, karenananf
ke depan untukstrtod
dan 7.22.1.3 "The strtod, strtof, dan fungsi strtold" mengatakan:Lihat juga:
Bagaimana qNaNs dan sNaNs terlihat di manual?
IEEE 754 2008 merekomendasikan bahwa (TODO wajib atau opsional?):
tetapi tampaknya tidak mengatakan bit mana yang lebih disukai untuk membedakan tak terhingga dari NaN.
6.2.1 "Pengodean NaN dalam format biner" mengatakan:
The Intel 64 dan IA-32 Arsitektur Software Developer Manual - Volume 1 Arsitektur Dasar - 253665-056US September 2015 4.8.3.4 "NaN" menegaskan bahwa x86 berikut IEEE 754 dengan membedakan NaN dan Snan oleh fraksi bit tertinggi:
begitu pula dengan Manual Referensi Arsitektur ARM - ARMv8, untuk profil arsitektur ARMv8-A - DDI 0487C.a A1.4.3 "Format titik-mengambang presisi tunggal":
fraction != 0
: Nilainya adalah NaN, dan merupakan NaN tenang atau NaN pensinyalan. Dua jenis NaN dibedakan berdasarkan bit fraksi paling signifikannya, bit [22]:bit[22] == 0
: NaN adalah NaN pensinyalan. Bit tanda dapat mengambil nilai apa pun, dan bit pecahan yang tersisa dapat mengambil nilai apa pun kecuali semua nol.bit[22] == 1
: NaN adalah NaN yang tenang. Bit tanda dan bit pecahan yang tersisa dapat mengambil nilai apa pun.Bagaimana qNanS dan sNaNs dibuat?
Satu perbedaan utama antara qNaNs dan sNaNs adalah:
std::numeric_limits::signaling_NaN
Saya tidak dapat menemukan kutipan IEEE 754 atau C11 yang jelas untuk itu, tetapi saya juga tidak dapat menemukan operasi bawaan yang menghasilkan sNaNs ;-)
Manual Intel dengan jelas menyatakan prinsip ini namun pada 4.8.3.4 "NaNs":
Ini dapat dilihat dari contoh kami di mana keduanya:
menghasilkan bit yang sama persis dengan
std::numeric_limits<float>::quiet_NaN()
.Kedua operasi tersebut dikompilasi ke instruksi perakitan x86 tunggal yang menghasilkan qNaN langsung di perangkat keras (TODO konfirmasi dengan GDB).
Apa yang dilakukan qNaNs dan sNaNs secara berbeda?
Sekarang kita tahu seperti apa qNaN dan sNaNs, dan bagaimana memanipulasinya, akhirnya kita siap untuk mencoba dan membuat sNaN melakukan tugasnya dan meledakkan beberapa program!
Jadi tanpa basa-basi lagi:
blow_up.cpp
Kompilasi, jalankan dan dapatkan status keluar:
Keluaran:
Perhatikan bahwa perilaku ini hanya terjadi dengan
-O0
di GCC 8.2: dengan-O3
, GCC menghitung sebelumnya dan mengoptimalkan semua operasi sNaN kami! Saya tidak yakin apakah ada cara patuh standar untuk mencegahnya.Jadi kami menyimpulkan dari contoh ini bahwa:
snan + 1.0
penyebabFE_INVALID
, tetapiqnan + 1.0
tidakLinux hanya menghasilkan sinyal jika diaktifkan dengan
feenableexept
.Ini adalah ekstensi glibc, saya tidak dapat menemukan cara untuk melakukannya dalam standar apa pun.
Ketika sinyal terjadi, itu karena perangkat keras CPU itu sendiri menimbulkan pengecualian, yang ditangani oleh kernel Linux dan menginformasikan aplikasi melalui sinyal.
Hasilnya adalah bash mencetak
Floating point exception (core dumped)
, dan status keluarnya adalah136
, yang sesuai dengan sinyal136 - 128 == 8
, yang menurut:adalah
SIGFPE
.Perhatikan bahwa
SIGFPE
adalah sinyal yang sama yang kita dapatkan jika kita mencoba membagi integer dengan 0:meskipun untuk bilangan bulat:
feenableexcept
Bagaimana cara menangani SIGFPE?
Jika Anda baru saja membuat sebuah penangan yang mengembalikan secara normal, ini mengarah ke pengulangan tak terbatas, karena setelah penangan kembali, pembagian terjadi lagi! Ini dapat diverifikasi dengan GDB.
Satu-satunya cara adalah menggunakan
setjmp
danlongjmp
melompat ke tempat lain seperti yang ditunjukkan di: C menangani sinyal SIGFPE dan melanjutkan eksekusiApa sajakah aplikasi sNaN di dunia nyata?
Sejujurnya, saya masih belum memahami kasus penggunaan yang sangat berguna untuk sNaN, hal ini telah ditanyakan di: Kegunaan pensinyalan NaN?
sNaNs terasa sangat tidak berguna karena kita dapat mendeteksi operasi awal yang tidak valid (
0.0f/0.0f
) yang menghasilkan qNaN denganfeenableexcept
: tampaknyasnan
hanya menimbulkan kesalahan untuk lebih banyak operasi yangqnan
tidak memunculkan, misalnya (qnan + 1.0f
).Misalnya:
main.c
menyusun:
kemudian:
memberikan:
dan:
memberikan:
Lihat juga: Cara melacak NaN di C ++
Apa sajakah tanda-tanda itu dan bagaimana cara memanipulasinya?
Semuanya diimplementasikan di perangkat keras CPU.
Flag-flag tersebut hidup di beberapa register, dan begitu juga dengan bit yang mengatakan jika sebuah pengecualian / sinyal harus dinaikkan.
Register tersebut dapat diakses dari userland dari kebanyakan arch.
Bagian dari kode glibc 2.29 ini sebenarnya sangat mudah dimengerti!
Misalnya,
fetestexcept
diimplementasikan untuk x86_86 di sysdeps / x86_64 / fpu / ftestexcept.c :jadi kita langsung bisa melihat bahwa petunjuk pemakaiannya adalah
stmxcsr
kependekan dari "Store MXCSR Register State".Dan
feenableexcept
diimplementasikan di sysdeps / x86_64 / fpu / feenablxcpt.c :Apa yang dikatakan standar C tentang qNaN vs sNaN?
The C11 N1570 rancangan standar tegas mengatakan bahwa standar tidak membedakan antara mereka pada F.2.1 "infinities, nol ditandatangani, dan NaN":
Diuji di Ubuntu 18.10, GCC 8.2. GitHub upstream:
sumber