IEEE 754 मूल बातें
पहले आइईईई 754 संख्याओं की मूल बातें व्यवस्थित की समीक्षा करते हैं।
हम सिंगल प्रिसिजन (32-बिट) पर ध्यान केंद्रित करेंगे, लेकिन सब कुछ तुरंत ही दूसरे प्रिकृतियों के लिए सामान्यीकृत किया जा सकता है।
प्रारूप है:
- 1 बिट: साइन
- 8 बिट्स: प्रतिपादक
- 23 बिट्स: अंश
या अगर आपको तस्वीरें पसंद हैं:
स्रोत ।
संकेत सरल है: 0 सकारात्मक है, और 1 नकारात्मक है, कहानी का अंत है।
प्रतिपादक 8 बिट लंबा है, और इसलिए यह 0 से 255 तक है।
घातांक को पक्षपाती कहा जाता है, क्योंकि इसमें ऑफसेट होता है -127
, जैसे:
0 == special case: zero or subnormal, explained below
1 == 2 ^ -126
...
125 == 2 ^ -2
126 == 2 ^ -1
127 == 2 ^ 0
128 == 2 ^ 1
129 == 2 ^ 2
...
254 == 2 ^ 127
255 == special case: infinity and NaN
अग्रणी बिट सम्मेलन
(जो निम्न प्रकार का काल्पनिक काल्पनिक कथन है, वह किसी वास्तविक ऐतिहासिक शोध पर आधारित नहीं है।)
IEEE 754 को डिजाइन करते समय, इंजीनियरों ने देखा कि सभी अंकों को छोड़कर 0.0
, 1
पहले अंक के रूप में द्विआधारी में एक है। उदाहरण के लिए:
25.0 == (binary) 11001 == 1.1001 * 2^4
0.625 == (binary) 0.101 == 1.01 * 2^-1
दोनों उस कष्टप्रद 1.
भाग से शुरू करते हैं ।
इसलिए, यह बेकार होगा कि अंक एक सटीक बिट लगभग हर एक संख्या को ले जाए।
इस कारण से, उन्होंने "अग्रणी बिट सम्मेलन" बनाया:
हमेशा मान लें कि संख्या एक से शुरू होती है
लेकिन फिर कैसे निपटा जाए 0.0
? खैर, उन्होंने एक अपवाद बनाने का फैसला किया:
- यदि प्रतिपादक 0 है
- और अंश 0 है
- फिर संख्या प्लस या माइनस का प्रतिनिधित्व करती है
0.0
ताकि बाइट्स 00 00 00 00
भी प्रतिनिधित्व करें 0.0
, जो अच्छा लग रहा है।
यदि हम केवल इन नियमों पर विचार करते हैं, तो सबसे छोटा गैर-शून्य संख्या जिसे दर्शाया जा सकता है:
जो अग्रणी बिट सम्मेलन के कारण हेक्स अंश में कुछ इस तरह दिखता है:
1.000002 * 2 ^ (-127)
जहां अंत में .000002
22 शून्य हैं 1
।
हम नहीं ले सकते fraction = 0
, अन्यथा वह संख्या होगी 0.0
।
लेकिन फिर इंजीनियरों, जो भी एक गहरी सौंदर्य भावना थी, सोचा: यह बदसूरत नहीं है? कि हम सीधे 0.0
किसी ऐसी चीज़ से कूदें जो 2 की उचित शक्ति भी नहीं है? क्या हम किसी भी तरह छोटी संख्या का प्रतिनिधित्व नहीं कर सकते थे? (ठीक है, यह "बदसूरत" की तुलना में थोड़ा अधिक था: यह वास्तव में लोगों को उनकी संगणना के लिए बुरे परिणाम मिल रहे थे, "नीचे सबमूर्नल्स कैसे संगणना में सुधार करते हैं" देखें)।
अमानवीय संख्याएँ
इंजीनियरों ने अपने सिर को थोड़ी देर के लिए खरोंच दिया, और हमेशा की तरह एक और अच्छे विचार के साथ वापस आ गए। क्या होगा यदि हम एक नया नियम बनाते हैं:
यदि प्रतिपादक 0 है, तो:
- अग्रणी बिट 0 हो जाता है
- प्रतिपादक को -126 के लिए निर्धारित किया जाता है (नहीं -127 के रूप में अगर हमारे पास यह अपवाद नहीं था)
इस तरह की संख्याओं को उप-संख्या (या असमान संख्या जो पर्यायवाची है) कहा जाता है।
इस नियम का तात्पर्य यह है कि संख्या इस प्रकार है:
अभी भी है 0.0
, जो एक तरह से सुरुचिपूर्ण है क्योंकि इसका मतलब है कि ट्रैक रखने के लिए एक कम नियम।
तो 0.0
वास्तव में हमारी परिभाषा के अनुसार एक उप-संख्या है!
इस नए नियम के साथ, सबसे छोटा गैर-उप-असामान्य संख्या है:
- प्रतिपादक: 1 (0 सबनॉर्मल होगा)
- अंश: ०
जो दर्शाता है:
1.0 * 2 ^ (-126)
फिर, सबसे बड़ी उप-संख्या है:
- घातांक: ०
- अंश: 0x7FFFFF (23 बिट 1)
जो बराबर है:
0.FFFFFE * 2 ^ (-126)
जहां .FFFFFE
एक बार फिर से डॉट के दाईं ओर 23 बिट्स हैं।
यह सबसे छोटे गैर-उप-असामान्य संख्या के करीब है, जो समझ में आता है।
और सबसे छोटा गैर-शून्य उप-असामान्य संख्या है:
जो बराबर है:
0.000002 * 2 ^ (-126)
जो भी बहुत करीब लग रहा है 0.0
!
उस से छोटी संख्या का प्रतिनिधित्व करने के लिए किसी भी समझदार तरीके को खोजने में असमर्थ, इंजीनियर खुश थे, और बिल्ली के चित्रों को ऑनलाइन देखने के लिए वापस चले गए, या जो कुछ भी यह है कि उन्होंने इसके बजाय 70 के दशक में किया था।
जैसा कि आप देख सकते हैं, उप-संख्याएं सटीक और प्रतिनिधित्व लंबाई के बीच एक व्यापार बंद करती हैं।
सबसे चरम उदाहरण के रूप में, सबसे छोटा गैर-शून्य उप-असामान्य:
0.000002 * 2 ^ (-126)
अनिवार्य रूप से 32-बिट्स के बजाय एकल बिट की परिशुद्धता है। उदाहरण के लिए, यदि हम इसे दो से विभाजित करते हैं:
0.000002 * 2 ^ (-126) / 2
हम वास्तव में 0.0
बिल्कुल पहुंचते हैं!
VISUALIZATION
हम जो भी सीखते हैं उसके बारे में ज्यामितीय अंतर्ज्ञान होना हमेशा एक अच्छा विचार है, इसलिए यहां जाता है।
यदि हम प्रत्येक दिए गए घातांक के लिए एक लाइन पर IEEE 754 फ़्लोटिंग पॉइंट नंबर प्लॉट करते हैं, तो यह कुछ इस तरह दिखता है:
+---+-------+---------------+-------------------------------+
exponent |126| 127 | 128 | 129 |
+---+-------+---------------+-------------------------------+
| | | | |
v v v v v
-------------------------------------------------------------
floats ***** * * * * * * * * * * * *
-------------------------------------------------------------
^ ^ ^ ^ ^
| | | | |
0.5 1.0 2.0 4.0 8.0
उस से हम देख सकते हैं कि:
- प्रत्येक घातांक के लिए, प्रतिनिधित्व संख्याओं के बीच कोई ओवरलैप नहीं है
- प्रत्येक घातांक के लिए, हमारे पास संख्या 2 ^ 32 की संख्या है (यहाँ 4 द्वारा दर्शाया गया है
*
)
- प्रत्येक घातांक के भीतर, अंक समान रूप से दूरी पर होते हैं
- बड़े प्रतिपादक बड़ी श्रेणियों को कवर करते हैं, लेकिन अधिक फैलाव वाले बिंदुओं के साथ
अब, चलो 0 को घातांक करने के लिए सभी तरह से नीचे लाते हैं।
सबऑर्मल के बिना, यह काल्पनिक रूप से दिखेगा:
+---+---+-------+---------------+-------------------------------+
exponent | ? | 0 | 1 | 2 | 3 |
+---+---+-------+---------------+-------------------------------+
| | | | | |
v v v v v v
-----------------------------------------------------------------
floats * **** * * * * * * * * * * * *
-----------------------------------------------------------------
^ ^ ^ ^ ^ ^
| | | | | |
0 | 2^-126 2^-125 2^-124 2^-123
|
2^-127
सबनॉर्मल के साथ, यह इस तरह दिखता है:
+-------+-------+---------------+-------------------------------+
exponent | 0 | 1 | 2 | 3 |
+-------+-------+---------------+-------------------------------+
| | | | |
v v v v v
-----------------------------------------------------------------
floats * * * * * * * * * * * * * * * * *
-----------------------------------------------------------------
^ ^ ^ ^ ^ ^
| | | | | |
0 | 2^-126 2^-125 2^-124 2^-123
|
2^-127
दो ग्राफ की तुलना करके, हम देखते हैं कि:
सबऑर्मल 0
, प्रतिपादक की सीमा की लंबाई को दोगुना कर [2^-127, 2^-126)
देते हैं[0, 2^-126)
सबनॉर्मल रेंज में फ्लोट्स के बीच की जगह इसके लिए समान है [0, 2^-126)
।
रेंज [2^-127, 2^-126)
में आधे से अधिक अंक हैं जो कि बिना सबऑर्मल के होगा।
उनमें से आधे अंक सीमा के दूसरे भाग को भरने के लिए जाते हैं।
रेंज [0, 2^-127)
में सबमॉर्नल्स के साथ कुछ बिंदु होते हैं, लेकिन कोई भी बिना।
अंकों की यह कमी [0, 2^-127)
बहुत ही सुरुचिपूर्ण नहीं है, और सबमर्सल के अस्तित्व का मुख्य कारण है!
चूँकि अंक समान रूप से हैं:
- सीमा
[2^-128, 2^-127)
की तुलना में आधे अंक हैं [2^-127, 2^-126)
- [2^-129, 2^-128)
की तुलना में आधे अंक हैं[2^-128, 2^-127)
- और इसी तरह
इसका मतलब यह है जब हम कहते हैं कि सबनॉर्मल्स आकार और परिशुद्धता के बीच एक व्यापार है।
चल सी उदाहरण
अब अपने सिद्धांत को सत्यापित करने के लिए कुछ वास्तविक कोड के साथ खेलते हैं।
लगभग सभी वर्तमान और डेस्कटॉप मशीनों में, C float
एकल परिशुद्धता IEEE 754 फ़्लोटिंग पॉइंट नंबरों का प्रतिनिधित्व करता है।
यह विशेष रूप से मेरे Ubuntu 18.04 amd64 Lenovo P51 लैपटॉप के लिए मामला है।
उस धारणा के साथ, सभी दावे निम्नलिखित कार्यक्रम पर गुजरते हैं:
subnormal.c
#if __STDC_VERSION__ < 201112L
#error C11 required
#endif
#ifndef __STDC_IEC_559__
#error IEEE 754 not implemented
#endif
#include <assert.h>
#include <float.h> /* FLT_HAS_SUBNORM */
#include <inttypes.h>
#include <math.h> /* isnormal */
#include <stdlib.h>
#include <stdio.h>
#if FLT_HAS_SUBNORM != 1
#error float does not have subnormal numbers
#endif
typedef struct {
uint32_t sign, exponent, fraction;
} Float32;
Float32 float32_from_float(float f) {
uint32_t bytes;
Float32 float32;
bytes = *(uint32_t*)&f;
float32.fraction = bytes & 0x007FFFFF;
bytes >>= 23;
float32.exponent = bytes & 0x000000FF;
bytes >>= 8;
float32.sign = bytes & 0x000000001;
bytes >>= 1;
return float32;
}
float float_from_bytes(
uint32_t sign,
uint32_t exponent,
uint32_t fraction
) {
uint32_t bytes;
bytes = 0;
bytes |= sign;
bytes <<= 8;
bytes |= exponent;
bytes <<= 23;
bytes |= fraction;
return *(float*)&bytes;
}
int float32_equal(
float f,
uint32_t sign,
uint32_t exponent,
uint32_t fraction
) {
Float32 float32;
float32 = float32_from_float(f);
return
(float32.sign == sign) &&
(float32.exponent == exponent) &&
(float32.fraction == fraction)
;
}
void float32_print(float f) {
Float32 float32 = float32_from_float(f);
printf(
"%" PRIu32 " %" PRIu32 " %" PRIu32 "\n",
float32.sign, float32.exponent, float32.fraction
);
}
int main(void) {
assert(float32_equal(0.5f, 0, 126, 0));
assert(float32_equal(1.0f, 0, 127, 0));
assert(float32_equal(2.0f, 0, 128, 0));
assert(isnormal(0.5f));
assert(isnormal(1.0f));
assert(isnormal(2.0f));
assert(0.5f == 0x1.0p-1f);
assert(1.0f == 0x1.0p0f);
assert(2.0f == 0x1.0p1f);
assert(float32_equal(-0.5f, 1, 126, 0));
assert(float32_equal(-1.0f, 1, 127, 0));
assert(float32_equal(-2.0f, 1, 128, 0));
assert(isnormal(-0.5f));
assert(isnormal(-1.0f));
assert(isnormal(-2.0f));
assert(float32_equal( 0.0f, 0, 0, 0));
assert(float32_equal(-0.0f, 1, 0, 0));
assert(!isnormal( 0.0f));
assert(!isnormal(-0.0f));
assert(0.0f == -0.0f);
assert(FLT_MIN == 0x1.0p-126f);
assert(float32_equal(FLT_MIN, 0, 1, 0));
assert(isnormal(FLT_MIN));
float largest_subnormal = float_from_bytes(0, 0, 0x7FFFFF);
assert(largest_subnormal == 0x0.FFFFFEp-126f);
assert(largest_subnormal < FLT_MIN);
assert(!isnormal(largest_subnormal));
float smallest_subnormal = float_from_bytes(0, 0, 1);
assert(smallest_subnormal == 0x0.000002p-126f);
assert(0.0f < smallest_subnormal);
assert(!isnormal(smallest_subnormal));
return EXIT_SUCCESS;
}
गिटहब ऊपर ।
संकलित करें और चलाएं:
gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c
./subnormal.out
सी ++
सी के सभी एपीआई को उजागर करने के अलावा, सी ++ कुछ अतिरिक्त उप-असामान्य संबंधित कार्यक्षमता को भी उजागर करता है जो सी में आसानी से उपलब्ध नहीं है <limits>
, जैसे:
denorm_min
: प्रकार टी का न्यूनतम सकारात्मक उप-असामान्य मान लौटाता है
C ++ में पूरा API प्रत्येक फ्लोटिंग पॉइंट प्रकार के लिए टेम्प्लेट किया जाता है, और बहुत अच्छा होता है।
कार्यान्वयन
x86_64 और ARMv8 IEEE 754 को सीधे हार्डवेयर पर लागू करता है, जिसे C कोड अनुवाद करता है।
सबऑर्मलल्स कुछ कार्यान्वयनों में मानदंडों की तुलना में कम तेज़ हैं: 10x द्वारा 0.1f को 0 धीमी गति से प्रदर्शन में क्यों बदल जाता है? यह एआरएम मैनुअल में वर्णित है, इस उत्तर के "एआरएमवी 8 विवरण" अनुभाग देखें।
ARMv8 विवरण
एआरएम आर्किटेक्चर संदर्भ मैनुअल ARMv8 DDI 0487C.a मैनुअल A1.5.4 "फ्लश-टू-जीरो" एक विन्यास मोड का वर्णन करता है जहां प्रदर्शन में सुधार करने के लिए सबनॉर्मल को शून्य पर गोल किया जाता है:
फ्लो-पॉइंट प्रोसेसिंग का प्रदर्शन कम किया जा सकता है जब गणना में असमान संख्या और अंडरफ़्लो अपवाद शामिल होते हैं। कई एल्गोरिदम में, इस प्रदर्शन को पुनर्प्राप्त किया जा सकता है, बिना अंतिम परिणाम की सटीकता को प्रभावित किए, जीरो के साथ असामान्य ऑपरेंड्स और मध्यवर्ती परिणामों को बदलकर। इस अनुकूलन की अनुमति देने के लिए, एआरएम फ्लोटिंग-पॉइंट इम्प्लीमेंटेशन फ्लश-टू-जीरो मोड को विभिन्न फ्लोटिंग-पॉइंट प्रारूपों के लिए उपयोग करने की अनुमति देता है:
AArch64 के लिए:
यदि FPCR.FZ==1
, फिर फ्लश-टू-जीरो मोड का उपयोग सभी निर्देशों के लिए एकल-परिशुद्धता और डबल-परिशुद्धता इनपुट और आउटपुट के लिए किया जाता है।
यदि FPCR.FZ16==1
, फ़्लश-टू-जीरो मोड का उपयोग सभी अर्ध-परिशुद्धता इनपुट और फ़्लोटिंग-पॉइंट निर्देशों के आउटपुट के लिए किया जाता है, तो इसके अलावा: - अर्ध-परिशुद्धता और एकल-परिशुद्धता संख्याओं के बीच रूपांतरण। - अर्ध-परिशुद्धता और डबल-परिशुद्धता के बीच रूपांतरण संख्या।
A1.5.2 "फ्लोटिंग-पॉइंट मानक, और शब्दावली" टेबल A1-3 "फ्लोटिंग-पॉइंट शब्दावली" यह पुष्टि करता है कि सबनॉर्मल और डॉर्मॉर्मल समानार्थी हैं:
This manual IEEE 754-2008
------------------------- -------------
[...]
Denormal, or denormalized Subnormal
C5.2.7 "FPCR, फ़्लोटिंग-पॉइंट कंट्रोल रजिस्टर" बताता है कि जब भी फ़्लोटिंग पॉइंट ऑपरेशन का इनपुट उप-असामान्य होता है, तो ARMv8 वैकल्पिक रूप से अपवादों को कैसे बढ़ा सकता है या फ़्लैग बिट्स सेट कर सकता है:
FPCR.IDE, बिट [15] इनपुट असामान्य फ्लोटिंग-पॉइंट अपवाद ट्रैप सक्षम करें। संभावित मूल्य हैं:
0b0 चयनित न किए गए अपवाद को छोड़कर। यदि फ़्लोटिंग-पॉइंट अपवाद होता है, तो FPSR.IDC बिट 1 पर सेट है।
0b1 ट्रैप्ड अपवाद चयनित हैंडलिंग। यदि फ़्लोटिंग-पॉइंट अपवाद होता है, तो PE FPSR.IDC बिट को अपडेट नहीं करता है। ट्रैप हैंडलिंग सॉफ़्टवेयर यह तय कर सकता है कि FPSR.IDC बिट को 1 पर सेट करना है या नहीं।
D12.2.88 "MVFR1_EL1, AArch32 मीडिया और VFP फ़ीचर रजिस्टर 1" से पता चलता है कि तथ्य में पूरी तरह से वैकल्पिक समर्थन पूरी तरह से वैकल्पिक है, और समर्थन होने पर पता लगाने के लिए थोड़ा सा प्रदान करता है:
FPFtZ, बिट्स [3: 0]
शून्य मोड में फ्लश करें। इंगित करता है कि फ्लोटिंग-पॉइंट कार्यान्वयन केवल फ्लश-टू-जीरो ऑपरेशन के लिए समर्थन प्रदान करता है। परिभाषित मूल्य हैं:
0b0000 लागू नहीं है, या हार्डवेयर केवल फ्लश-टू-जीरो ऑपरेशन के लिए समर्थन करता है।
0b0001 हार्डवेयर पूर्ण संख्यात्मक अंकगणित का समर्थन करता है।
अन्य सभी मान आरक्षित हैं।
ARMv8-A में, अनुमत मान 0b0000 और 0b0001 हैं।
इससे पता चलता है कि जब सबऑर्मल लागू नहीं होते हैं, तो कार्यान्वयन केवल फ्लश-टू-शून्य पर वापस आ जाता है।
अनंत और NaN
जिज्ञासु? मैंने कुछ बातें लिखी हैं:
कैसे सबऑनॉर्मल गणना में सुधार करते हैं
TODO: आगे और अधिक ठीक से समझें कि कैसे कूदने से गणना के परिणाम बदतर हो जाते हैं / कैसे सबनॉर्मल गणना परिणामों में सुधार करते हैं।
वास्तविक इतिहास
फ्लोटिंग प्वाइंट की ओल्ड मैन के साथ एक साक्षात्कार के द्वारा चार्ल्स सेवरानस । (1998) एक छोटी असली दुनिया के साथ एक साक्षात्कार के रूप में ऐतिहासिक सिंहावलोकन है विलियम कहाँ टिप्पणी में जॉन कोलमैन द्वारा सुझाव दिया गया था।