सामग्री पर जाएँ
Topos Uranos

सारांश

प्रोसेसर के लिए पूर्णांक बिटों की एक कतार है, और C++ ऐसे संक्रियक देती है जो इस कतार पर स्तंभ-दर-स्तंभ काम करते हैं: &, |, ^ और ~, तथा शिफ़्ट << और >>। यह पाठ सिखाता है कि इन्हें हाथ से कैसे निकालें, मास्क की तकनीक से एक ही पूर्णांक में कई फ़्लैग कैसे रखें, और उन शिफ़्टों को कैसे पहचानें जिन्हें मानक परिभाषित नहीं करता। फिर यह समझाता है कि 2 ^ 3 का मान 8 नहीं, 1 क्यों है, और बिट संक्रियकों को कोष्ठक क्यों चाहिए। दूसरा आधा भाग गणितीय लाइब्रेरी प्रस्तुत करता है: <cmath> के फ़ंक्शन, <numbers> के स्थिरांक, <numeric> से महत्तम समापवर्तक और लघुत्तम समापवर्त्य, और <bit> के बिट फ़ंक्शन, एक ऐसे सिद्धांत के साथ जो किसी भी भाषा में लागू होता है: जो लाइब्रेरी सही और परखा हुआ देती है, उसे दोबारा नहीं लिखा जाता।

अधिगम उद्देश्य

  1. छोटे पूर्णांकों पर &, |, ^, ~ और शिफ़्टों का परिणाम हाथ से निकालना।
  2. परिभाषित शिफ़्टों (C++20 से ऋणात्मक संख्याओं की शिफ़्ट भी) को उन शिफ़्टों से अलग पहचानना जो अपरिभाषित व्यवहार हैं, और पहली तरह की शिफ़्टों का परिणाम पहले से बताना।
  3. मास्क से किसी बिट को जाँचना, सेट करना, हटाना और पलटना।
  4. समझाना कि 2 ^ 3 का मान 8 नहीं, 1 क्यों है, और स्थिति के अनुसार std::pow और शिफ़्ट में से चुनना।
  5. सूत्रों में <cmath> के फ़ंक्शन (std::sqrt, std::pow, std::abs, std::hypot, त्रिकोणमितीय फ़ंक्शन) और <numbers> के स्थिरांक, जैसे std::numbers::pi, इस्तेमाल करना।

बिट संक्रियक

पाठ पूर्णांक और उनका निरूपण ने दिखाया कि int बिटों की एक कतार है, इस पाठ्यक्रम के सिस्टमों में 32 बिट की, और उसका मान उन स्थानों के भारों का योग है जहाँ 1 है। फिर भी अब तक प्रोग्राम ने पूर्णांक को केवल संख्या की तरह बरता है: उसे जोड़ा, गुणा किया और भाग दिया। लेकिन उसी कतार को दूसरी तरह भी पढ़ा जा सकता है, “हाँ” या “नहीं” वाले 32 उत्तरों के संग्रह की तरह, और इस पढ़ने के लिए C++ में चार संक्रियक हैं जो बिटों पर एक-एक करके काम करते हैं, स्थानों के बीच हासिल के बिना।

बिटों की गिनती दाईं ओर से, 0 से शुरू करके, की जाती है: बिट kk वह है जिसका भार 2k2^k है। उदाहरण के लिए, 1212 में, जो द्विआधारी में 1100 लिखा जाता है, बिट 2 और 3 सेट हैं, और बिट 0 और 1 हटे हुए हैं।

संक्रियक ये हैं। बिटवार AND, a & b, हर उस स्थान पर 1 रखता है जहाँ a और b दोनों में 1 है। बिटवार OR, a | b, वहाँ 1 रखता है जहाँ दोनों में से कम से कम एक में 1 है। बिटवार XOR, a ^ b, वहाँ 1 रखता है जहाँ दोनों में से ठीक एक में 1 है, यानी जहाँ बिट अलग-अलग हैं। बिटवार NOT, ~a, का एक ही संकार्य होता है और वह हर बिट को उलट देता है। पहले तीन की सारणियाँ, बिटों के एक जोड़े pp और qq के लिए, संयोजन, वियोजन और अनन्य वियोजन की सारणियाँ हैं:

pqp&qp∣qp^q11110100110101100000\begin{array}{cc|ccc} p & q & p \mathbin{\&} q & p \mid q & p \mathbin{\hat{}} q \\ \hline 1 & 1 & 1 & 1 & 0 \\ 1 & 0 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 \end{array}

जिसने पाठ्यक्रम “गणितीय तर्कशास्त्र I” पढ़ा है, वह यहाँ प्रस्तावात्मक संयोजकों को पहचान लेगा, जिनमें 1 सत्य और 0 असत्य है: बिट संक्रियक और कुछ नहीं, बल्कि एक संयोजक है जो सभी स्तंभों पर एक साथ लगाया जाता है। इससे हाथ से निकालने की विधि निकलती है: दोनों संख्याएँ द्विआधारी में, एक के नीचे दूसरी, दाईं ओर से संरेखित करके लिखी जाती हैं, और हर स्तंभ पर सारणी लगाई जाती है, पड़ोसी स्तंभों की परवाह किए बिना।

उदाहरणबारह और दस, स्तंभ-दर-स्तंभ

संख्याओं 12=12 = 1100 और 10=10 = 1010 का गुण यह है कि उनके चार स्तंभ दो बिटों के चारों संयोगों से होकर गुज़रते हैं; इसीलिए उनसे तीनों सारणियाँ एक साथ देखी जा सकती हैं।

प्रमाण

  1. आँकड़े: 1100 12 है और 1010 10 है। स्तंभों में, बाएँ से दाएँ, जोड़े (1,1)(1, 1), (1,0)(1, 0), (0,1)(0, 1) और (0,0)(0, 0) हैं।

  2. 12 & 10 का पहला स्तंभ: दोनों बिट 1 हैं, और AND 1 देता है।

  3. दूसरा स्तंभ: 1 और 0। AND के 0 देने के लिए एक 0 काफ़ी है।

  4. तीसरा स्तंभ: 0 और 1; AND फिर 0 देता है।

  5. चौथा स्तंभ: 0 और 0 से 0 मिलता है। परिणाम की पंक्ति, 1000, 8 है: 12 & 10 का मान 8 है।

  6. OR हर उस स्तंभ को सेट करता है जिसमें कम से कम एक 1 हो: 1110, यानी 12 | 10 का मान 14 है।

  7. XOR केवल उन स्तंभों को सेट करता है जिनके बिट अलग-अलग हैं: 0110, और 12 ^ 10 का मान 6 है।

नीचे दिया गया प्रोग्राम चित्र की पुष्टि करता है। फ़ॉर्मैट फ़ील्ड {:04b}, जो पूर्णांकों वाले पाठ में प्रस्तुत किया गया था, किसी मान को द्विआधारी में चार अंकों के साथ लिखता है, खाली अंकों को शून्यों से भरकर।

C++ प्रोग्राम
#include <print>​int main(){    const int a{0b1100};    const int b{0b1010};    std::println("a     = {:04b} = {}", a, a);    std::println("b     = {:04b} = {}", b, b);    std::println("a & b = {:04b} = {}", a & b, a & b);    std::println("a | b = {:04b} = {}", a | b, a | b);    std::println("a ^ b = {:04b} = {}", a ^ b, a ^ b);    std::println("~a    = {}", ~a);}
आउटपुट
a     = 1100 = 12b     = 1010 = 10a & b = 1000 = 8a | b = 1110 = 14a ^ b = 0110 = 6~a    = -13

आख़िरी पंक्ति को समझाना ज़रूरी है। NOT a के सभी 32 बिट उलटता है, केवल लिखे गए चार नहीं: बाईं ओर के 28 शून्य एक बन जाते हैं, और 1 से शुरू होने वाला int दो के पूरक में ऋणात्मक होता है। अब, दो के पूरक में किसी संख्या का ऋणात्मक उसके बिट उलटकर 1 जोड़ने से मिलता है, यानी −x=∼x+1-x = {\sim}x + 1; इसलिए ∼x=−x−1{\sim}x = -x - 1, और ~12 का मान −13-13 है। अतः चिह्नयुक्त पूर्णांक पर बिटवार NOT ऐसी संख्या देता है जो संख्या के रूप में शायद ही काम की हो: वह बिट पैटर्न के रूप में काम की होती है, और इसीलिए बिटों पर काम प्राथमिकता से चिह्नरहित प्रकारों में किया जाता है, जैसा मास्क वाले अनुभाग में दिखेगा।

छोटे प्रकारों के बारे में एक चेतावनी। संक्रिया से पहले C++ किसी संकरे पूर्णांक प्रकार, जैसे std::uint8_t, के हर संकार्य को int में बदल देती है; यह वही प्रमोशन है जिसका वर्णन पाठ अंकगणितीय संक्रियाएँ ने अंकगणितीय संक्रियाओं के लिए किया था, और यह यहाँ भी लागू होता है। इससे निकलता है कि ~flags, जब flags का प्रकार std::uint8_t और मान 5 हो, बाइट 11111010 नहीं, बल्कि int −6-6 है, जिसके 32 बिटों में बाईं ओर जोड़े गए 24 एक शामिल हैं; केवल उसे फिर से किसी std::uint8_t में रखने पर वह आठ बिट तक काटा जाता है और अपेक्षित बाइट, 250, वापस मिलता है।

बिट संक्रियकों को पाठ संबंध और तर्क के तार्किक संक्रियकों से नहीं मिलाना चाहिए। 6 && 3 true है: दोनों संकार्य शून्य से भिन्न हैं, और परिणाम bool है। इसके विपरीत, 6 & 3 एक पूर्णांक है, वह जो उन स्तंभों से बनता है जहाँ दोनों में 1 है। इसके अलावा, && की गणना शॉर्ट-सर्किट होती है, जबकि & हमेशा दोनों संकार्यों की गणना करता है।