प्रायोगिक तौर पर qNaN और sNaN कैसे दिखते हैं?
आइए पहले जानें कि अगर हमारे पास sNaN या qNaN है तो कैसे पहचानें।
मैं C के बजाय इस उत्तर में C ++ का उपयोग करूंगा क्योंकि यह सुविधाजनक प्रदान करता है std::numeric_limits::quiet_NaN
और std::numeric_limits::signaling_NaN
जो मुझे C में आसानी से नहीं मिला।
मैं तब भी यह पता लगाने के लिए फ़ंक्शन नहीं कर सकता था कि कोई NaN sNaN या qNaN है, तो चलिए NaN कच्चे बाइट को प्रिंट करते हैं:
main.cpp
#include <cassert>
#include <cstring>
#include <cmath> // nanf, isnan
#include <iostream>
#include <limits> // std::numeric_limits
#pragma STDC FENV_ACCESS ON
void print_float(float f) {
std::uint32_t i;
std::memcpy(&i, &f, sizeof f);
std::cout << std::hex << i << std::endl;
}
int main() {
static_assert(std::numeric_limits<float>::has_quiet_NaN, "");
static_assert(std::numeric_limits<float>::has_signaling_NaN, "");
static_assert(std::numeric_limits<float>::has_infinity, "");
// Generate them.
float qnan = std::numeric_limits<float>::quiet_NaN();
float snan = std::numeric_limits<float>::signaling_NaN();
float inf = std::numeric_limits<float>::infinity();
float nan0 = std::nanf("0");
float nan1 = std::nanf("1");
float nan2 = std::nanf("2");
float div_0_0 = 0.0f / 0.0f;
float sqrt_negative = std::sqrt(-1.0f);
// Print their bytes.
std::cout << "qnan "; print_float(qnan);
std::cout << "snan "; print_float(snan);
std::cout << " inf "; print_float(inf);
std::cout << "-inf "; print_float(-inf);
std::cout << "nan0 "; print_float(nan0);
std::cout << "nan1 "; print_float(nan1);
std::cout << "nan2 "; print_float(nan2);
std::cout << " 0/0 "; print_float(div_0_0);
std::cout << "sqrt "; print_float(sqrt_negative);
// Assert if they are NaN or not.
assert(std::isnan(qnan));
assert(std::isnan(snan));
assert(!std::isnan(inf));
assert(!std::isnan(-inf));
assert(std::isnan(nan0));
assert(std::isnan(nan1));
assert(std::isnan(nan2));
assert(std::isnan(div_0_0));
assert(std::isnan(sqrt_negative));
}
संकलित करें और चलाएं:
g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out
मेरे x86_64 मशीन पर आउटपुट:
qnan 7fc00000
snan 7fa00000
inf 7f800000
-inf ff800000
nan0 7fc00000
nan1 7fc00001
nan2 7fc00002
0/0 ffc00000
sqrt ffc00000
हम QEMU उपयोगकर्ता मोड के साथ anarch64 पर कार्यक्रम को भी निष्पादित कर सकते हैं:
aarch64-linux-gnu-g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
qemu-aarch64 -L /usr/aarch64-linux-gnu/ main.out
और यह ठीक उसी आउटपुट का उत्पादन करता है, जिसमें सुझाव दिया गया है कि कई आर्क बारीकी से IEEE 754 को लागू करते हैं।
इस बिंदु पर, यदि आप IEEE 754 फ़्लोटिंग पॉइंट नंबरों की संरचना से परिचित नहीं हैं, तो एक नज़र डालें: सबऑनॉर्मल फ़्लोटिंग पॉइंट संख्या क्या है?
बाइनरी में कुछ मान ऊपर हैं:
31
|
| 30 23 22 0
| | | | |
-----+-+------+-+---------------------+
qnan 0 11111111 10000000000000000000000
snan 0 11111111 01000000000000000000000
inf 0 11111111 00000000000000000000000
-inf 1 11111111 00000000000000000000000
-----+-+------+-+---------------------+
| | | | |
| +------+ +---------------------+
| | |
| v v
| exponent fraction
|
v
sign
इस प्रयोग से हम मानते हैं कि:
qNaN और sNaN केवल 22: 1 का अर्थ है कि शांत का मतलब विभेदित प्रतीत होता है, और 0 का अर्थ है सिग्नलिंग
शिशु भी घातांक == 0xFF के साथ काफी समान हैं, लेकिन उनके पास अंश == 0 है।
इस कारण से, NaN को 21 से 1 सेट करना होगा, अन्यथा sNaN को सकारात्मक अनंतता से अलग करना संभव नहीं होगा!
nanf()
कई अलग-अलग NaN का उत्पादन करता है, इसलिए कई संभावित एनकोडिंग होने चाहिए:
7fc00000
7fc00001
7fc00002
चूंकि nan0
जैसा है std::numeric_limits<float>::quiet_NaN()
, हम वैसा ही करते हैं कि वे सभी अलग-अलग शांत NaN हैं।
C11 N1570 मानक प्रारूप पुष्टि की है कि nanf()
शांत Nans उत्पन्न करता है, क्योंकि nanf
आगे खेलने strtod
और 7.22.1.3 "strtod, strtof, और strtold कार्यों" का कहना है:
एक चरित्र अनुक्रम एनएएन या एनएएन (एन-चार-सीक्वेंस ऑप्ट) की व्याख्या एक शांत एनएन के रूप में की जाती है, यदि रिटर्न प्रकार में समर्थित है, और एक विषय अनुक्रम भाग की तरह है जिसमें अपेक्षित रूप नहीं है; एन-चार अनुक्रम का अर्थ कार्यान्वयन-परिभाषित है। 293)
यह सभी देखें:
मैनुअल में qNaNs और sNaNs कैसे दिखते हैं?
IEEE 754 2008 की अनुशंसा है कि (TODO अनिवार्य या वैकल्पिक?):
- घातांक == 0xFF और अंश के साथ कुछ भी! = 0 एक NaN है
- और यह कि उच्चतम अंश बिट sNaN से qNaN को अलग करता है
लेकिन यह कहना मुनासिब नहीं है कि कौन सा बिट एनएएन से अनंत को अलग करना पसंद करता है।
6.2.1 "द्विआधारी स्वरूपों में NaN एन्कोडिंग" कहते हैं:
जब वे संचालन के परिणाम होते हैं, तो यह उपखंड आगे NaNs के एन्कोडिंग्स को थोड़ा सा तार के रूप में निर्दिष्ट करता है। जब एन्कोड किया जाता है, तो सभी NaN में एक NaN के रूप में एन्कोडिंग की पहचान करने के लिए साइन बिट और बिट्स का एक पैटर्न होता है और जो इसकी तरह (sNaN बनाम qNaN) को निर्धारित करता है। शेष बिट्स, जो अनुगामी क्षेत्र में हैं, पेलोड को एनकोड करते हैं, जो नैदानिक जानकारी हो सकती है (ऊपर देखें)। 34
सभी बाइनरी NaN बिट स्ट्रिंग्स में पक्षपाती प्रतिपादक क्षेत्र E के सभी बिट्स 1 से सेट होते हैं (3.4 देखें)। एक शांत NaN बिट स्ट्रिंग अनुगामी क्षेत्र क्षेत्र T जा रहा है की पहली बिट (d1) के साथ एन्कोडेड होना चाहिए। एक संकेतन NaN बिट स्ट्रिंग अनुगामी महत्व क्षेत्र के पहले बिट के साथ एन्कोड किया जाना चाहिए 0. यदि पहला बिट अनुगामी महत्त्वपूर्ण क्षेत्र 0 है, अनुगामी महत्त्वपूर्ण क्षेत्र का कुछ और हिस्सा NaN को अनंत से अलग करने के लिए गैर-शून्य होना चाहिए। अभी वर्णित पसंदीदा एन्कोडिंग में, एक संकेतन NaN को D1 से 1 सेट करके शांत किया जाएगा, जिससे T के शेष बिट्स अपरिवर्तित रहेंगे। द्विआधारी प्रारूपों के लिए, पेलोड को पीछे के क्षेत्र में p significant 2 कम से कम महत्वपूर्ण बिट्स में एन्कोड किया गया है
इंटेल 64 और IA-32 आर्किटेक्चर सॉफ्टवेयर डेवलपर की मैनुअल - खंड 1 बेसिक वास्तुकला - 253665-056US सितंबर 2015 4.8.3.4 "Nans" पुष्टि की है कि 86 उच्चतम अंश थोड़ा करके NaN और स्नान भेद द्वारा आईईईई 754 इस प्रकार है:
IA-32 आर्किटेक्चर NaNs के दो वर्गों को परिभाषित करता है: शांत NaN (QNaNs) और NaN (SNaN) को सिग्नल करना। QNaN एक NaN है जिसमें सबसे महत्वपूर्ण अंश बिट सेट है SNNN एक NaN है जिसमें सबसे महत्वपूर्ण अंश बिट स्पष्ट है।
और ऐसा ही एआरएम आर्किटेक्चर रेफरेंस मैनुअल - ARMv8, ARMv8-A आर्किटेक्चर प्रोफाइल के लिए है - DDI 0487C.a A1.4.3 "एकल-सटीक फ़्लोटिंग-पॉइंट फॉर्मेट":
fraction != 0
: मान एक NaN है, और या तो एक शांत NaN या एक संकेतन NaN है। NaN के दो प्रकार उनके सबसे महत्वपूर्ण अंश बिट द्वारा प्रतिष्ठित हैं, बिट [22]:
bit[22] == 0
: NaN एक संकेतन NaN है। साइन बिट किसी भी मान ले सकता है, और शेष अंश बिट्स सभी शून्य को छोड़कर कोई भी मूल्य ले सकते हैं।
bit[22] == 1
: NaN एक शांत NaN है। साइन बिट और शेष अंश बिट्स किसी भी मूल्य ले सकते हैं।
QNanS और sNaN कैसे उत्पन्न होते हैं?
QNaN और sNaN के बीच एक बड़ा अंतर यह है कि:
- qNaN अजीब मूल्यों के साथ नियमित रूप से अंतर्निहित (सॉफ्टवेयर या हार्डवेयर) अंकगणितीय संचालन द्वारा उत्पन्न होता है
- sNaN बिल्ट-इन ऑपरेशंस द्वारा कभी उत्पन्न नहीं होता है, इसे केवल प्रोग्रामर द्वारा स्पष्ट रूप से जोड़ा जा सकता है, जैसे
std::numeric_limits::signaling_NaN
मैं उस के लिए स्पष्ट IEEE 754 या C11 उद्धरण नहीं ढूँढ सका, लेकिन न तो मुझे कोई अंतर्निहित ऑपरेशन मिल सकता है जो sNaNs;;
इंटेल मैनुअल स्पष्ट रूप से 4.8.3.4 "NaNs" पर इस सिद्धांत को बताता है:
SNaN आमतौर पर एक अपवाद हैंडलर को फंसाने या आह्वान करने के लिए उपयोग किया जाता है। उन्हें सॉफ्टवेयर द्वारा डाला जाना चाहिए; अर्थात्, प्रोसेसर कभी भी फ्लोटिंग-पॉइंट ऑपरेशन के परिणामस्वरूप एक SNaN उत्पन्न नहीं करता है।
यह हमारे उदाहरण से देखा जा सकता है जहाँ दोनों:
float div_0_0 = 0.0f / 0.0f;
float sqrt_negative = std::sqrt(-1.0f);
के रूप में बिल्कुल बिट्स का उत्पादन std::numeric_limits<float>::quiet_NaN()
।
उन दोनों ऑपरेशनों को एक एकल x86 असेंबली इंस्ट्रक्शन में संकलित किया गया है जो सीधे qNaN को हार्डवेयर में उत्पन्न करता है (TODO पुष्टि DB के साथ)।
QNaNs और sNaNs अलग-अलग क्या करते हैं?
अब जब हम जानते हैं कि qNaNs और sNaNs क्या दिखते हैं, और उन्हें कैसे हेरफेर करना है, तो हम आखिरकार कोशिश करते हैं और sNaNs अपनी बात करने और कुछ कार्यक्रमों को उड़ाने के लिए तैयार हैं!
तो आगे की हलचल के बिना:
blow_up.cpp
#include <cassert>
#include <cfenv>
#include <cmath> // isnan
#include <iostream>
#include <limits> // std::numeric_limits
#include <unistd.h>
#pragma STDC FENV_ACCESS ON
int main() {
float snan = std::numeric_limits<float>::signaling_NaN();
float qnan = std::numeric_limits<float>::quiet_NaN();
float f;
// No exceptions.
assert(std::fetestexcept(FE_ALL_EXCEPT) == 0);
// Still no exceptions because qNaN.
f = qnan + 1.0f;
assert(std::isnan(f));
if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID)
std::cout << "FE_ALL_EXCEPT qnan + 1.0f" << std::endl;
// Now we can get an exception because sNaN, but signals are disabled.
f = snan + 1.0f;
assert(std::isnan(f));
if (std::fetestexcept(FE_ALL_EXCEPT) == FE_INVALID)
std::cout << "FE_ALL_EXCEPT snan + 1.0f" << std::endl;
feclearexcept(FE_ALL_EXCEPT);
// And now we enable signals and blow up with SIGFPE! >:-)
feenableexcept(FE_INVALID);
f = qnan + 1.0f;
std::cout << "feenableexcept qnan + 1.0f" << std::endl;
f = snan + 1.0f;
std::cout << "feenableexcept snan + 1.0f" << std::endl;
}
संकलित करें, चलाएँ और बाहर निकलने की स्थिति प्राप्त करें:
g++ -ggdb3 -O0 -Wall -Wextra -pthread -std=c++11 -pedantic-errors -o blow_up.out blow_up.cpp -lm -lrt
./blow_up.out
echo $?
आउटपुट:
FE_ALL_EXCEPT snan + 1.0f
feenableexcept qnan + 1.0f
Floating point exception (core dumped)
136
ध्यान दें कि यह व्यवहार केवल -O0
GCC 8.2 में होता है : के साथ-O3
GCC पूर्व-गणना करता है और हमारे सभी sNaN परिचालनों का अनुकूलन करता है! मुझे यकीन नहीं है कि अगर इसे रोकने का एक मानक अनुपालन तरीका है।
तो हम इस उदाहरण से कटौती करते हैं:
snan + 1.0
कारण है FE_INVALID
, लेकिन qnan + 1.0
नहीं है
यदि यह सक्षम है तो लिनक्स केवल एक संकेत उत्पन्न करता है feenableexept
।
यह एक शानदार विस्तार है, मुझे किसी भी मानक में ऐसा करने का कोई तरीका नहीं मिला।
जब सिग्नल होता है, तो ऐसा इसलिए होता है क्योंकि सीपीयू हार्डवेयर खुद एक अपवाद उठाता है, जिसे लिनक्स कर्नेल ने संभाला और सिग्नल के माध्यम से एप्लिकेशन को सूचित किया।
इसका परिणाम यह है कि बैश प्रिंट Floating point exception (core dumped)
, और बाहर निकलने की स्थिति है 136
, जो सिग्नल से मेल खाती है136 - 128 == 8
, जो निम्नानुसार है:
man 7 signal
है SIGFPE
।
ध्यान दें कि SIGFPE
वही संकेत है जो हमें मिलता है यदि हम 0 से पूर्णांक को विभाजित करने का प्रयास करते हैं:
int main() {
int i = 1 / 0;
}
पूर्णांकों के लिए हालांकि:
- शून्य से कुछ भी विभाजित करना संकेत को बढ़ाता है, क्योंकि पूर्णांक में कोई अनंत प्रतिनिधित्व नहीं है
- संकेत यह डिफ़ॉल्ट रूप से होता है, बिना आवश्यकता के
feenableexcept
SIGFPE को कैसे संभालना है?
यदि आप केवल एक हैंडलर बनाते हैं जो सामान्य रूप से लौटता है, तो यह एक अनंत लूप की ओर जाता है, क्योंकि हैंडलर के वापस आने के बाद, विभाजन फिर से होता है! इसे GDB के साथ सत्यापित किया जा सकता है।
इसका एक ही तरीका है कि कहीं और इस्तेमाल किया जाए setjmp
और longjmp
जैसा कि दिखाया गया है: C हैंडल सिग्नल SIGFPE और निष्पादन जारी रखें
SNaNs के कुछ वास्तविक विश्व अनुप्रयोग क्या हैं?
बहुत ईमानदारी से, मैं अभी भी sNaNs के लिए एक सुपर उपयोगी उपयोग के मामले को नहीं समझ पाया हूं, यह पूछा गया है: NaN को संकेत देने की उपयोगिता?
sNaNs विशेष रूप से बेकार महसूस करते हैं क्योंकि हम प्रारंभिक अमान्य संचालन ( 0.0f/0.0f
) का पता लगा सकते हैं जो qNaNs के साथ उत्पन्न feenableexcept
होता है: ऐसा प्रतीत होता है कि snan
बस अधिक संचालन के लिए त्रुटियों को उठाता है जो qnan
इसके लिए नहीं उठाता है, जैसे (qnan + 1.0f
)।
उदाहरण के लिए:
main.c
#define _GNU_SOURCE
#include <fenv.h>
#include <stdio.h>
int main(int argc, char **argv) {
(void)argv;
float f0 = 0.0;
if (argc == 1) {
feenableexcept(FE_INVALID);
}
float f1 = 0.0 / f0;
printf("f1 %f\n", f1);
feenableexcept(FE_INVALID);
float f2 = f1 + 1.0;
printf("f2 %f\n", f2);
}
संकलन:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -lm
फिर:
./main.out
देता है:
Floating point exception (core dumped)
तथा:
./main.out 1
देता है:
f1 -nan
f2 -nan
इसे भी देखें: C ++ में NaN का पता कैसे लगाएं
सिग्नल फ्लैग क्या हैं और उन्हें कैसे हेरफेर किया जाता है?
सब कुछ सीपीयू हार्डवेयर में लागू किया गया है।
झंडे कुछ रजिस्टर में रहते हैं, और यदि ऐसा कुछ होता है, तो अपवाद / संकेत उठाया जाना चाहिए।
वे रजिस्टर अधिकांश आर्क से उपयोगकर्तालैंड से सुलभ हैं ।
ग्लिब 2.29 कोड का यह हिस्सा वास्तव में समझने में बहुत आसान है!
उदाहरण के लिए, fetestexcept
x86_86 के लिए sysdeps / x86_64 / fpu / ftestexcept.c पर लागू किया जाता है :
#include <fenv.h>
int
fetestexcept (int excepts)
{
int temp;
unsigned int mxscr;
/* Get current exceptions. */
__asm__ ("fnstsw %0\n"
"stmxcsr %1" : "=m" (*&temp), "=m" (*&mxscr));
return (temp | mxscr) & excepts & FE_ALL_EXCEPT;
}
libm_hidden_def (fetestexcept)
इसलिए हम तुरंत देखते हैं कि निर्देशों का उपयोग है stmxcsr
"स्टोर MXCSR रजिस्टर स्टेट" के लिए है।
और sysdeps / x86_64 / fpu / feenablxcpt.cfeenableexcept
पर लागू किया गया है :
#include <fenv.h>
int
feenableexcept (int excepts)
{
unsigned short int new_exc, old_exc;
unsigned int new;
excepts &= FE_ALL_EXCEPT;
/* Get the current control word of the x87 FPU. */
__asm__ ("fstcw %0" : "=m" (*&new_exc));
old_exc = (~new_exc) & FE_ALL_EXCEPT;
new_exc &= ~excepts;
__asm__ ("fldcw %0" : : "m" (*&new_exc));
/* And now the same for the SSE MXCSR register. */
__asm__ ("stmxcsr %0" : "=m" (*&new));
/* The SSE exception masks are shifted by 7 bits. */
new &= ~(excepts << 7);
__asm__ ("ldmxcsr %0" : : "m" (*&new));
return old_exc;
}
QNaN बनाम sNaN के बारे में C मानक क्या कहता है?
C11 N1570 मानक प्रारूप स्पष्ट रूप से कहा गया है कि मानक F.2.1 "Infinities, पर हस्ताक्षर किए शून्य, और Nans" में उन दोनों के बीच भेदभाव नहीं करता:
1 यह विनिर्देश संकेतन NaN के व्यवहार को परिभाषित नहीं करता है। यह आमतौर पर शांत NaN को निरूपित करने के लिए NaN शब्द का उपयोग करता है। नान और इन्फिनिटी मैक्रोज़ और नान <math.h>
IEC 60559 NaN और शिशुओं के लिए पदनाम प्रदान करते हैं।
उबंटू 18.10, जीसीसी 8.2 में परीक्षण किया गया। गिटहब अपस्ट्रीम: