Solidity signed integers एक स्मार्ट कॉन्ट्रैक्ट में नकारात्मक संख्याओं (negative numbers) का उपयोग करने की अनुमति देते हैं। यह लेख बताता है कि EVM स्तर पर उनका उपयोग कैसे किया जाता है। यह मानकर चला गया है कि आपको EVM और बाइनरी संख्याओं (binary numbers) की बुनियादी समझ है।
Two’s Complement की व्याख्या
Solidity और EVM signed integers के लिए Two’s Complement representation का उपयोग करते हैं
हर डेटा प्रकार की तरह, Solidity अभी भी signed integers को दर्शाने के लिए 32-byte शब्दों (words) का उपयोग करता है। EVM में इस प्रकार का कोई अर्थपूर्ण संकेतक (semantic indicator) नहीं है, ठीक वैसे ही जैसे इसका कोई संकेतक नहीं है कि एक 32-byte स्लॉट वास्तव में एक boolean, एक address या 160-bit संख्या है। संकलन के समय (compilation time) मान को नकारात्मक (negative) माना जाता है।
क्योंकि आप “type(int256).max” के साथ इंटीजर का अधिकतम मान प्राप्त कर सकते हैं या न्यूनतम (minimum) प्राप्त करने के लिए .min फ़ील्ड का उपयोग कर सकते हैं। कोई संख्या सकारात्मक है या नकारात्मक, इसके संकेतक के लिए एक अतिरिक्त बिट (extra bit) की आवश्यकता होती है, इसलिए यह unsigned संस्करण की तुलना में केवल एक बिट कम तक की संख्याओं को संग्रहीत कर सकता है।
One’s complement का अर्थ है कि एक uint256 एक uint255 बन जाता है, जिसमें सबसे बाईं ओर का बिट (leftmost bit) यह दर्शाता है कि यह सकारात्मक है या नकारात्मक। यदि EVM one’s complement का उपयोग करता, तो इसका अर्थ यह होता कि type(int256).max == absoluteValue(type(int256.min)) लेकिन ऐसा नहीं है। Two’s Complement नकारात्मक संख्या का अधिकतम परिमाण (maximum magnitude) सकारात्मक संख्या के अधिकतम परिमाण से एक अधिक होता है। उदाहरण के लिए, int8 के लिए अधिकतम सकारात्मक संख्या 127 है, लेकिन int8 के लिए अधिकतम परिमाण वाली नकारात्मक संख्या -128 है।
Two’s complement अंकगणित (arithmetic) के पैटर्न और उदाहरण।
बहुत सारे गणितीय प्रमाणों में जाने के बजाय, आइए कुछ वास्तविक उदाहरणों का उपयोग करें (इसका उद्देश्य Two’s Complement अंकगणित के लिए उदाहरण द्वारा प्रमाण प्रस्तुत करना नहीं है, इच्छुक पाठकों के लिए Two’s Complement पर बहुत सारा साहित्य उपलब्ध है)।
उदाहरणों को अधिक पठनीय बनाने के लिए आइए int8 का उपयोग करें। निम्नलिखित बाइनरी (binary) में है, हेक्स (hex) में नहीं।
int8(0) == 0000 0000
type(int8).max == 0111 1111
type(int8).min == 1000 000
+1 और -1 के निरूपण (representations) को देखना शिक्षाप्रद है:
int8(1) == 0000 0001
int8(-1) == 1111 1111
आइए Two’s complement में नीचे की ओर गिनती करें ताकि एक पैटर्न स्पष्ट हो सके:
int8(-2) == 1111 1110
int8(-3) == 1111 1101
int8(-4) == 1111 1100
int8(-5) == 1111 1011
आप मोटे तौर पर Two’s complement नकारात्मक संख्याओं को “उल्टी गिनती” (counting down) के रूप में सोच सकते हैं।
यहाँ Two’s complement की दिलचस्प विशेषता है। -2 + -2 बराबर -4 होना चाहिए, और Two’s complement में जोड़ने और ओवरफ़्लो (overflow) की अनुमति देने से यह संभव हो जाता है। यहाँ Python में Two’s complement निरूपण का उपयोग करके -2 को स्वयं में जोड़ा गया है:
>>> (int(b'11111110', 2) + int(b'11111110', 2) ) % 256
252
>>> bin(252)
'0b11111100'
यह ऊपर दिए गए अपेक्षित पैटर्न से मेल खाता है।
क्या होगा यदि हम -2 में +4 जोड़ें? हमें +2 प्राप्त होना चाहिए। आइए इसे कार्य करते हुए देखें:
>>> # -2 + 4
>>> (int(b'11111110', 2) + int(b'00000100', 2)) % 256
>>> 2
यह केवल तभी काम करता है जब दोनों संख्याएँ Two’s complement निरूपण में हों। Solidity आपको unsigned और signed integers को एक साथ जोड़ने की अनुमति नहीं देता है क्योंकि यह अस्पष्ट होता है कि आपका इरादा क्या है।
Two’s Complement गुणन (multiplication) के साथ भी काम करता है। -2 और -2 का अपेक्षित परिणाम +4 है, और पाठक को इसे सत्यापित करने के लिए पहले दिए गए कोड को कॉपी करने के लिए प्रोत्साहित किया जाता है।
यह सभी अंकगणितीय संचालन (arithmetic operations) के लिए काम नहीं करता है।
Two’s Complement को जोड़ (addition), घटाव (subtraction), गुणन (multiplication), या यहाँ तक कि left bitshift (<<) में बदलाव की आवश्यकता नहीं होती है। ये EVM op codes ADD, SUB, MUL और SHL के अनुरूप हैं। हम इस ट्यूटोरियल के बाद के भाग में चर्चा करेंगे कि shift left अभी भी Two’s complement में क्यों काम करता है।
हालाँकि, गुणन (multiplication), मोडुलो (modulo), राइट शिफ्ट (right shift), और एक बड़े signed integer में कास्टिंग (casting) को हस्ताक्षरित विधियों (signed methods) का उपयोग करके नहीं किया जा सकता है और इसके लिए उनके स्वयं के op codes की आवश्यकता होती है। इसी तरह, पारंपरिक तुलना ऑपरेटर (comparison operators) काम नहीं करेंगे, क्योंकि नकारात्मक संख्याएँ सकारात्मक संख्याओं की तुलना में बड़ी “प्रतीत” होती हैं।
Signed arithmetic के लिए Ethereum op codes
sdiv
Gas cost: 5
SDIV, या signed division, हस्ताक्षरित संख्याओं (signed numbers) को विभाजित करने के लिए है। इस opcode का उपयोग निम्नलिखित जैसे कोड में पर्दे के पीछे (behind the scenes) किया जाता है।
function divide(int256 a, int256 b) public pure returns (int256 quotient)
{
quotient = a / b;
}
smod
Gas cost: 5
चूँकि Two’s complement अंकगणित को div के लिए अपने स्वयं के opcode की आवश्यकता होती है, इसलिए इसमें कोई आश्चर्य नहीं है कि यही बात मोडुलो (शेषफल) लेने पर भी लागू होती है।
function divide(int256 a, int256 b) public pure returns (int256 remainder)
{
remainder = a % b;
}
slt and sgt
Gas cost: 3
Signed numbers के परिमाण (magnitude) की तुलना करने के लिए, हमें पहले यह निर्धारित करने की आवश्यकता है कि यह सकारात्मक है या नकारात्मक, फिर परिमाण की तुलना करें। ये op codes उस ऑपरेशन को एक चरण में करते हैं।
Unsigned समकक्षों की तरह, जहाँ संभव हो >= और <= से बचना और इसके बजाय strict inequality ऑपरेटरों का उपयोग करना अधिक gas कुशल (gas efficient) है।
sar - signed arithmetic shift right
Gas cost: 3
SAR एक बहुत ही कम उपयोग किया जाने वाला op code है, लेकिन यह इस solidity कोड के संकलन परिणाम (compilation result) पर दिखाई देगा। ध्यान दें कि x एक integer है और y एक unsigned integer है।
contract SarExample {
function main(int256 x, uint256 y) public pure returns (int256 res) {
res = x >> y;
}
}
हम इसे कैसे समझें? साधारण unsigned संख्याओं के संदर्भ में, बिट्स को एक से दाईं ओर शिफ्ट (shift right) करने का प्रभाव दो से विभाजित करने जैसा होता है, दो से शिफ्ट करने का प्रभाव चार से विभाजित करने जैसा होता है, आदि।
uint256 x = 8 >> 2; // x = 2
uint256 y = 4 >> 1; // y = 2
यदि आप एक signed integer के साथ ऐसा करते हैं, तो यह घटना सुरक्षित रहती है।
int256 x = -8 >> 2; // x = -2
int256 y = -4 >> 1; // y = -2
कोई SAL (signed arithmetic left shift) op code क्यों नहीं है? निम्नलिखित उदाहरण में क्या होना चाहिए?
int256 x = -8 << 2; // x = -32
int256 y = -4 << 1; // y = -8
हमने क्रमशः 4 और 2 से गुणा किया। Two’s Complement में, बाईं ओर शिफ्ट (shifting left) करने से संख्या अपेक्षानुसार सुरक्षित रहती है।
इसके पीछे (Under the hood), एक नियमित SHL (shift left) opcode का उपयोग किया गया था। Arithmetic left shift के लिए किसी विशेष स्थिति (special case) की आवश्यकता नहीं है। यह समझने में अटपटा लग सकता है, क्योंकि सबसे दाईं ओर के बिट्स (rightmost bits) के शून्य होने से संख्या बड़ी हो जाती है। लेकिन याद रखें, Two’s Complement में अधिकतम नकारात्मक मान तब होता है जब सबसे बाईं ओर का बिट (leftmost bit) एक होता है, और अन्य सभी बिट्स शून्य होते हैं।
signextend evm
Gas cost: 5
256 बिट्स से छोटे signed integer में शुरुआत में शून्य (leading zeros) होंगे। हालाँकि, Two’s Complement नकारात्मक संख्याएँ हमेशा एक (1) पर सेट सबसे बाईं ओर के बिट के साथ शुरू होती हैं। इसलिए, यदि एक Two’s Complement इंटीजर को एक बड़े प्रकार में अपकास्ट (upcasted) किया जाता है, तो मान नकारात्मक से सकारात्मक में बदल जाएगा क्योंकि सबसे बाईं ओर के बिट्स शून्य होंगे। Signextend इस संक्रमण (transition) को सहजता से संभालता है।
signextend solidity
आप Solidity में सीधे signextend का उपयोग नहीं कर सकते हैं, लेकिन जब एक छोटे इंटीजर को बड़े इंटीजर में कास्ट (cast) किया जाता है तो इसका उपयोग पर्दे के पीछे किया जाता है। निम्नलिखित कोड में int8 को int256 में कास्ट करने के लिए इसके संकलित बाइटकोड (compiled bytecode) में signextend opcode शामिल है।
contract SignExtendExample {
function main(int8 x) public pure returns (int256 res) {
res = x;
}
}
इस बिंदु पर यह स्पष्ट होना चाहिए कि बड़े इंटीजर्स को छोटे इंटीजर्स में कास्ट नहीं किया जा सकता है।
और जानें
हमारे विशेषज्ञ solidity प्रशिक्षण पाठ्यक्रम में और अधिक उन्नत विषय (advanced topics) जानें।
मूल रूप से 11 अप्रैल, 2023 को प्रकाशित