64-बिट सिस्टम पर i128 का रहस्य: Rust विशाल संख्याओं के साथ कैसे काम करता है

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

परिचय: छोटी दुनिया में बड़ी संख्याएँ

\n\n

कल्पना करें: आप एक क्रिप्टोग्राफ़िक एल्गोरिदम लिख रहे हैं, भौतिक प्रक्रियाओं का अनुकरण कर रहे हैं, या बस नैनोमीटर में निकटतम आकाशगंगा की दूरी की गणना कर रहे हैं। और अचानक आपको एहसास होता है — मानक i64 (-9 क्विंटिलियन से 9 क्विंटिलियन तक) आपके लिए बेहद अपर्याप्त है। Rust में एक जीवन रक्षक है — i128 प्रकार। लेकिन यह एक प्रोसेसर पर कैसे काम करता है जो शारीरिक रूप से 128-बिट संख्याओं के साथ काम नहीं कर सकता? क्या यह जादू है?

\n\n

स्पॉयलर: कोई जादू नहीं है, शानदार इंजीनियरिंग है। इस लेख में, हम यह समझेंगे कि कैसे Rust (या अधिक सटीक रूप से, हुड के नीचे LLVM) 64-बिट आर्किटेक्चर पर i128 का अनुकरण करता है। आप जानेंगे कि यह उतना धीमा क्यों नहीं है जितना लगता है, और इन विशाल संख्याओं के साथ कुशल कोड कैसे लिखें। यह शुष्क सिद्धांत नहीं है — हम प्रश्न से लेकर उदाहरणों पर वास्तविक अनुप्रयोग तक का सफर तय करेंगे।

\n\n

1. हार्डवेयर वास्तविकता: 128 बिट एक चुनौती क्यों है

\n\n

आधुनिक 64-बिट प्रोसेसर (x86-64, ARM64) में 64 बिट आकार के सामान्य प्रयोजन वाले रजिस्टर होते हैं। यह उनका "मूल" शब्द आकार है। 128-बिट संख्या के साथ काम करने के लिए, प्रोसेसर को दो 64-बिट हिस्सों पर कई ऑपरेशन करने होंगे: निचला (low) और ऊपरी (high)।

\n\n

Rust में, i128 प्रकार (और इसका अहस्ताक्षरित भाई u128) एक हार्डवेयर प्रकार नहीं है, बल्कि एक एग्रीगेट प्रकार है। इसे मेमोरी में दो आसन्न 64-बिट शब्दों के रूप में संग्रहीत किया जाता है। कंपाइलर (LLVM के माध्यम से) कोड उत्पन्न करता है जो इन शब्दों को एक इकाई के रूप में संसाधित करता है।

\n\n

एक सरल उदाहरण:

\n\n
fn main() {\n    let big: i128 = 123456789012345678901234567890;\n    println!("{}", big);\n}
\n\n

असेंबली स्तर पर, यह दो 64-बिट रजिस्टरों (जैसे, rax और rdx) के साथ काम करने वाले निर्देशों के अनुक्रम में बदल जाएगा। LLVM ऐसे ऑपरेशनों को बहुत कुशलता से "जोड़ना" जानता है।

\n\n

2. जोड़ की शारीरिक रचना: Rust i128 को कैसे जोड़ता है

\n\n

आइए सबसे बुनियादी ऑपरेशन देखें — दो i128 को जोड़ना। आइए एक सरल फ़ंक्शन लिखें और cargo-show-asm या Compiler Explorer की मदद से हुड के नीचे देखें।

\n\n
fn add_big(a: i128, b: i128) -> i128 {\n    a + b\n}
\n\n

इसे x86-64 के लिए संकलित करें (बिना अनुकूलन के, ताकि यह अधिक स्पष्ट हो)। LLVM लगभग इस तरह एक स्यूडोकोड उत्पन्न करेगा:

\n\n
    \n
  1. a के निचले 64 बिट को रजिस्टर rax में लोड करें, ऊपरी को rdx में।
  2. \n
  3. b के निचले 64 बिट को rbx में लोड करें, ऊपरी को rcx में।
  4. \n
  5. निचले भागों को जोड़ें: add rax, rbx — परिणाम rax में, कैरी फ्लैग (CF) सेट होता है यदि अतिप्रवाह होता है।
  6. \n
  7. कैरी के साथ ऊपरी भागों को जोड़ें: adc rdx, rcx — adc (add with carry) 1 जोड़ता है यदि CF फ्लैग सेट था।
  8. \n
  9. परिणाम: निचले 64 बिट rax में, ऊपरी rdx में।
  10. \n
\n\n

यही अनुकरण है। एक के बजाय दो निर्देश। गुणा और भाग के लिए कोड अधिक जटिल होगा, लेकिन सिद्धांत वही है — 64-बिट ऑपरेशनों में विभाजन।

\n\n

3. प्रदर्शन के बारे में क्या? मिथक और वास्तविकता

\n\n

बहुत से लोग सोचते हैं कि i128 i64 से दोगुना धीमा है। यह पूरी तरह सच नहीं है। हाँ, प्रत्येक ऑपरेशन एक के बजाय 2-3 निर्देशों में बदल जाता है। लेकिन आधुनिक प्रोसेसर में होते हैं:

\n\n
    \n
  • पाइपलाइन और सुपरस्केलरिटी — प्रति क्लॉक कई निर्देश निष्पादित कर सकते हैं।
  • \n
  • शाखा भविष्यवाणी — तुलनाओं के लिए विशेष रूप से महत्वपूर्ण।
  • \n
  • बड़े कैश — मेमोरी के साथ काम प्रभावित नहीं होता, क्योंकि i128 केवल 16 बाइट लेता है।
  • \n
\n\n

व्यावहारिक परीक्षण (Rust, लूप में सरल अंकगणित):

\n\n
use std::time::Instant;\n\nfn main() {\n    let n = 100_000_000;\n    \n    // i64\n    let start = Instant::now();\n    let m                

ब्लॉग

पुस्तक अनुशंसाएँ