Float Exposed
(float.exposed)- यह लेख समझाता है कि floating point(float) मान memory में कैसे store और represent किए जाते हैं
- फोकस इस बात पर है कि मानों के hexadecimal और decimal रूप से वास्तविक संख्यात्मक मान में कैसे रूपांतरण किया जाता है
- Sign, Exponent, Significand क्षेत्रों की परिभाषा और उनकी-उनकी भूमिकाओं की व्याख्या की गई है
- इसमें यह दिखाने वाले उदाहरण शामिल हैं कि कोई विशेष float मान ठीक किस binary और decimal मान को दर्शाता है
- represent किए जा सकने वाले मानों के बीच अंतर (Delta) की गणना का भी उल्लेख है
floating point मानों की storage संरचना का विश्लेषण
- "halfb float float double" जैसे विभिन्न floating point formats मौजूद हैं
- प्रत्येक मान को memory में store किए गए मान के रूप में Raw Hexadecimal Integer Value (hexadecimal integer value) और Raw Decimal Integer Value (decimal integer value) की तरह देखा जा सकता है
- hexadecimal data को Hexadecimal Form ("%a") में दिखाया जाता है, जो वास्तविक floating point notation से जुड़ता है
- प्रत्येक मान की स्थिति को Significand–Exponent Range (significand–exponent range में स्थिति) के रूप में दर्शाया जाता है
binary और decimal मानों की व्याख्या कैसे करें
- floating point संख्या को Base-2 (binary evaluation expression) में इस तरह व्यक्त किया जा सकता है:
- (−12)02×102(100010012 − 011111112)×1.011111110010100000000002
→ यह binary expression के माध्यम से संख्यात्मक मूल्यांकन है
- (−12)02×102(100010012 − 011111112)×1.011111110010100000000002
- Base-10 (decimal evaluation expression) में यह इस रूप में होती है:
- 1×210×1.4967041015625
→ इसे 2 की 10वीं घात और fractional भाग के गुणनफल के रूप में व्यक्त किया गया है
- 1×210×1.4967041015625
- रूपांतरण के समय का सटीक decimal मान भी दिखाया जाता है:
- 1.532625×103 जैसे रूप में प्रस्तुत किया गया है
निकटवर्ती मानों से दूरी (Delta) की गणना
- represent किए जा सकने वाले मानों के बीच Delta (अंतराल) का महत्वपूर्ण अर्थ होता है
- अगले (Next) या पिछले (Previous) representable value तक की दूरी (Delta to Next/Previous Representable Value) अलग-अलग दी जाती है
- उदाहरण: ±1.220703125×10-4
- यह अंतर floating point मान की effective digits/precision से संबंधित है
सारांश
- floating point की memory representation और binary, decimal रूपांतरण के सिद्धांत
- sign, exponent, significand संरचना की व्याख्या
- representation range और निकटवर्ती मानों के बीच अंतराल की जानकारी भी साथ में व्यवस्थित की गई है
1 टिप्पणियां
Hacker News की राय
इस विषय पर यह सबसे बढ़िया व्याख्या है: https://fabiensanglard.net/floating_point_visually_explained/ Hacker News पढ़ना शुरू करते समय मैं इस लेख से टकराया था, और इसी तरह की सामग्री प्लेटफ़ॉर्म पर बनी रहे, यह चाहने की प्रेरणा मुझे इससे मिली: https://news.ycombinator.com/item?id=29368529
मुझे लगता है कि यह शायद गणित की तरफ थोड़ा ज़्यादा झुका हुआ है, इसलिए वह व्याख्या इतनी आसान नहीं लगी अगर आपको floating point की सच में बहुत सरल व्याख्या चाहिए: यह स्केल से स्वतंत्र होकर लगभग एक-जितनी बिट-प्रिसीजन देता है यानी, चाहे संख्या 1 से बहुत छोटी हो, 1 के आसपास हो, या बहुत बड़ी हो, आप अग्रणी बिट्स में लगभग समान स्तर की सटीकता की उम्मीद कर सकते हैं यही इसका मूल गुण है, लेकिन इसे सहज रूप से समझना आसान नहीं है
यह हाल की TM research team की ब्लॉग पोस्ट के संदर्भ में भी काफ़ी अच्छी तरह मेल खाता है https://news.ycombinator.com/item?id=45200925
मैंने इसे इससे बेहतर समझाया हुआ कभी नहीं देखा, साझा करने के लिए सच में धन्यवाद
जिन समस्याओं पर मैंने काफ़ी समय तक सोचा था, उनमें से एक थी: 'float value को सबसे छोटे लेकिन स्पष्ट decimal string के रूप में कैसे व्यक्त किया जाए' उदाहरण के लिए, single-precision float में किसी float को uniquely identify करने के लिए अधिकतम 9 अंकों की decimal precision चाहिए होती है इसलिए
%.9gजैसा printf pattern इस्तेमाल करना पड़ता है लेकिन तब 0.1 जैसी संख्या0.100000001जैसे बदसूरत रूप में प्रिंट होती है इसलिए आम तौर पर इसे 6 अंकों तक round करके दिखाया जाता है, और%.6gइस्तेमाल करने पर 6 अंकों तक दी गई decimal input values संग्रहीत मान के समान रूप में प्रिंट हो सकती हैं लेकिन calculation के नतीजों के लिए यह round-trip safe नहीं रहता खासकर जब float values की सटीक तुलना करनी हो (जैसे data change detect करना), तब यह अहम हो जाता है मेरे दिमाग में यह विचार आया था कि पहले 6 अंकों में प्रिंट करें, फिर parse करने पर अगर वही binary value वापस मिले तो उसे रखें, नहीं तो 7, 8, 9 अंकों तक दोहराते हुए सबसे छोटी decimal representation ढूँढें मेरा algorithm कुछ ऐसा थामैं जानना चाहता था कि printf/scanf को बार-बार चलाए बिना सबसे छोटी representation निकालने का कोई और अधिक efficient तरीका है क्या
यह समस्या वास्तव में महत्वपूर्ण है इसे इस रूप में देखा जा सकता है कि किसी दिए गए float के लिए "normalized" string बनानी है इसलिए Dragon4, Grisu3, Ryu, Dragonbox जैसे कई efficient algorithm मौजूद हैं Google की double-conversion library पहले दो को implement करती है
printf/scanf loop के बिना इससे बेहतर तरीका मौजूद है सिर्फ
printf("%f", ...)से नहीं हो जाएगा float से string में बदलने का असली algorithm काफ़ी जटिल है हाल का एक अच्छा algorithm है https://github.com/ulfjack/ryu मुझे पता है कि इससे भी अधिक efficient कोई तरीका हाल में आया है, लेकिन उसका नाम याद नहीं हैनकारात्मक टिप्पणियों की ज़्यादा चिंता करने की ज़रूरत नहीं है; भले यह सर्वोत्तम तरीका न हो, लेकिन (अगर bug न हो) ज़्यादातर मामलों में काफ़ी ठीक काम करता है सच कहूँ तो मेरा भी ऐसा ही अनुभव रहा है: एक बार मैं Euler rotation (5°, 5°, 0) के बाद वही vector देने वाले vector को ढूँढना चाहता था, इसलिए random तरीके से vector को थोड़ा-थोड़ा हिलाकर देखता था कि क्या वह reference vector के और करीब जा रहा है मैंने लाखों iteration चलाए, और Python में कुछ ही सेकंड में जवाब मिल गया library-level code के लिए यह inefficient होता, लेकिन मेरे use case के लिए मैं इससे बहुत संतुष्ट था
std::numeric_limits<float>::max_digits10देखना उपयोगी हो सकता है https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10.htmlयह बेकार है, और
sscanf()का तो कभी इस्तेमाल नहीं करना चाहिए unsigned integer में convert करके serialize/restore करें, तो बिना information loss के reversibility मिल जाती हैअगर और छोटा representation चाहिए, तो ऐसा heuristic इस्तेमाल करें जो original precision की guarantee बनाए रखे (जैसे idempotence)
FP के बारे में मेरी पसंदीदा टिप्स में से एक यह है कि float comparison को लगभग integer comparison की तरह इस्तेमाल किया जा सकता है
a > bजाँचना हो तोa,bको signed integer की तरह interpret करके बस compare कर दीजिए यह तरीका (लगभग) काम करता है यानी, अगला बड़ा float value वही है जो उसके bit pattern को integer मानकर उसमें 1 जोड़ने पर मिलता है उदाहरण के लिए, अगर आप 0.0 float से शुरू करें और integer addition से उसमें 1 जोड़ें, तो वही अगला float value होगा (denormal, सबसे छोटा gap वाला मान)nextafterभी इसी सिद्धांत पर implement होता है जब आपको समझ आ जाता है कि float values का order integer comparison order जैसा है, तो यह बहुत अधिक स्वाभाविक लगता है बेशक कुछ exceptions हैं: NaN, infinity, negative zero वगैरह अलग तरह से व्यवहार करते हैं कुछ उपयोगी बातें निकलती हैं, लेकिन सब कुछ नहींयह बात सटीक रूप से सही नहीं है positive numbers या positive-negative comparisons के लिए यह ठीक है, लेकिन negative numbers के बीच तुलना अलग होती है standard floating point (
float) sign-magnitude format में होता है, जबकि आधुनिक signed integers two's complement में होते हैं negative values के लिए दोनों में magnitude comparison की दिशा उलट जाती है अगर आप float को int की तरह 1-1 बढ़ाएँ, तो आम तौर पर आप उसी sign के भीतर 'मात्रा में बड़े' मान की तरफ जाते हैं यानी positive ऊपर जाते हैं, और negative अधिक छोटे negative की तरफ नीचे जाते हैं integer में तो आप हमेशा ऊपर जाते हैं, या overflow में फँसते हैं अधिक सटीक रूप में कहें तो यह sign-magnitude integer comparison जैसा है और हाँ, ऊपर बताए गए caveat अब भी लागू रहते हैंसंदर्भ के लिए, Rust standard library में NaN सहित comparable total-order floating-point comparison algorithm इस तरह है (IEEE 751 recommendation)
पूरा algorithm देखें
इस विषय से मेरा सामना OMSCS के game AI course में हुआ था, जहाँ game objects की position को floating point से व्यक्त करते समय किन बातों का ध्यान रखना चाहिए, यह बताया गया था origin या reference point से दूर जाने पर float को बड़े values store करने पड़ते हैं, इसलिए precision घटती जाती है और यह जोखिम पैदा करता है
यह दिलचस्प है कि यही चीज़ Minecraft की mythic Far Lands जैसी बन गई यानी, दुनिया के origin से जितना दूर जाते हैं, terrain generation और physics धीरे-धीरे अजीब होने लगते हैं, और बहुत अधिक दूर जाने पर पूरी तरह टूट जाते हैं इसमें थोड़ा occult जैसा एहसास भी है, मानो वास्तविकता के नियम धीरे-धीरे बिखर रहे हों और यह सब float precision की सीमा की वजह से होता है
जब float में 0 से 1 के बीच की बहुत-सी संख्याएँ जोड़ी जाती हैं, तो उन्हें एक-एक करके जोड़ने की तुलना में दो-दो के जोड़े बनाकर फिर जोड़ने का तरीका (pairing) कहीं अधिक सटीक होता है यह float accumulation error के गंभीर असर का एक अच्छा उदाहरण है वास्तव में ऐसे float error को नज़रअंदाज़ करने से समस्याएँ हुई हैं Donald Knuth की "The Art of Computer Programming" में float के बुनियादी सत्य, जैसे
a + (b + c) ≠ (a + b) + c, समझाए गए हैं वास्तविक दुनिया में भी ऐसी ग़लतियों से समस्याएँ हुई हैं; Patriot missile system में समय का accumulation float में किया गया, और धीरे-धीरे error इतना बढ़ा कि सिस्टम target से पूरी तरह चूक गया और restart की ज़रूरत पड़ी हर 24 घंटे पर reboot करना पड़ता था, और अंततः system software में सुधार किया गया float errors की वजह से बड़े structures गिर जाने जैसी घटनाएँ भी हुईं (क्योंकि thickness values बहुत पतली calculate हो गई थीं)पहले boundary conditions तय करनी चाहिए ताकि पता चले कितनी precision चाहिए तब minimum/maximum distance भी पहले से निकाले जा सकते हैं अगर world बहुत बड़ा हो, तो उसे sectors में बाँटना चाहिए या global/local coordinates अलग-अलग रखने चाहिए (जैसे No Man's Sky) game आखिरकार एक तरह की stagecraft ही है Double-Precision ज़्यादातर practical स्थितियों में काफ़ी होती है अहम बात यह याद रखना है कि बहुत छोटे और बहुत बड़े values को साथ में जोड़ने से बचें
Kerbal Space Program ने सिर्फ 32bit float के साथ पूरे solar system को संभालने के लिए काफ़ी चतुर engineering का इस्तेमाल किया था इस पर कई articles और videos हैं, और वे सच में recommend करने लायक हैं
यह visualization मज़ेदार है, और दिलचस्प बात यह है कि इसका visual style उस CIDR range calculator जैसा लगता है जिसे मैंने कभी network ranges समझाने के लिए बनाया था ऐसी visualizations बहुत उपयोगी होती हैं
पहले मैं float representation को समझने के लिए https://www.h-schmidt.net/FloatConverter/IEEE754.html इस्तेमाल करता था इस साइट की अच्छी बात यह है कि यह conversion error भी दिखाती है, लेकिन यह double precision support नहीं करती
अभी तक इस comment thread में किसी ने साझा नहीं किया, लेकिन float से जुड़ी मेरी सबसे पसंदीदा साइट है https://0.30000000000000004.com/
32bit float में "सबसे दिलचस्प integer" का ख़िताब 16777217 को जाता है (64-bit में 9007199254740992) testing के दौरान ऐसे edge case याद रखना मज़ेदार होता है
64-bit float के लिए 9007199254740991, JavaScript में
Number.MAX_SAFE_INTEGERहै यह मान even नहीं है, और अगला मान 9007199254740992 अपने आप में अभी भी safe है, लेकिन 9007199254740993 जैसे स्पष्ट रूप से unsafe values round होकर अलग पहचान खो देते हैं64-bit float में यह असल में ठीक ±9,007,199,254,740,993.0 है :-) संदर्भ के लिए, ऐसे values उस सबसे बड़े integer के ठीक अगले मान को दिखाते हैं जिसे float 'सटीक रूप से' represent कर सकता है उदाहरण के लिए, 32-bit float में ±16,777,216.0 के बाद represent होने वाला अगला मान ±16,777,218.0 है ±16,777,217.0 represent नहीं किया जा सकता, इसलिए इसे आम तौर पर zero की दिशा वगैरह में round कर दिया जाता है precision limits और rounding के ये मुद्दे अक्सर नज़रअंदाज़ हो जाते हैं
IEEE754 का अस्तित्व अच्छी बात है, लेकिन IEEE754 पूर्ण नहीं है, और मेरा मानना है कि posit जैसे number formats (अगर hardware support न मानें) उससे बेहतर हो सकते हैं bignum rational उससे भी अधिक शक्तिशाली है, लेकिन सबसे धीमा भी है
हाल में GPU में आए कई तरह के fp8 formats का support मिल जाए तो सच में बहुत बढ़िया होगा