ट्रैक
/
x86-64 Assembly
x86-64 Assembly
/
अभ्यास
/
इन्वेंटरी प्रबंधन
इन्वेंटरी प्रबंधन

इन्वेंटरी प्रबंधन

सीखने का अभ्यास

परिचय

पूर्णांक

द्विआधारी संकेतन

पूर्णांक एक ऐसी अवधारणा है जो पूर्ण संख्याओं को दर्शाती है, जैसे 4, -2, 0 या 64532।

किसी पूर्णांक को बाइटों के क्रम के रूप में दर्शाने के लिए द्विआधारी संकेतन का उपयोग किया जाता है। इस संकेतन में क्रम का हर बिट दो की एक अलग घात दर्शाता है। बिट का इंडेक्स दाएँ से बाएँ बढ़ने पर उसकी वैल्यू भी बढ़ती जाती है।

अचिह्नित संख्याएँ

अगर कोई संख्या केवल ऋणेतर हो सकती है, तो उसे अचिह्नित संख्या कहा जाता है।

अचिह्नित संख्या को दर्शाने के लिए उसके क्रम में जो बिट सेट हैं, उन सबसे जुड़ी दो की घातों को जोड़ दिया जाता है।

एक रजिस्टर में दर्शाए जा सकने वाले ऋणेतर पूर्णांकों की सीमा 0 (कोई बिट सेट नहीं) से लेकर 2⁶⁴ - 1 (सभी 64 बिट सेट होने का योग) तक होती है।

किसी अचिह्नित संख्या को बड़े आकार में बदलने के लिए उसके सभी ऊपरी बिट 0 से भर दिए जाते हैं, ताकि कोई नया बिट वैल्यू में न जुड़े। इसे शून्य विस्तार कहा जाता है।

movzx इंस्ट्रक्शन (z शून्य के लिए) किसी 8-बिट या 16-बिट स्रोत ऑपरेंड का शून्य विस्तार करके उसे बड़े गंतव्य ऑपरेंड में बदल देता है। 32-बिट स्रोत ऑपरेंड का शून्य विस्तार साधारण mov से हमेशा गंतव्य ऑपरेंड के सभी 64 बिटों तक हो जाता है।

चिह्नित संख्याएँ

अगर कोई पूर्णांक धनात्मक या ऋणात्मक दोनों वैल्यू ले सकता है, तो उसे चिह्नित संख्या कहा जाता है।

ऋणात्मक संख्याओं को दर्शाने के लिए x86-64 दो का पूरक निरूपण इस्तेमाल करता है।

दो के पूरक में भी चिह्नित संख्याओं को उनके सेट बिटों से जुड़ी दो की घातों के योग के रूप में दर्शाया जाता है। लेकिन अगर सबसे ऊपरी बिट सेट हो, तो उसे बाकी बिटों में जोड़ने के बजाय घटाया जाता है।

यह बिट बाकी सबके योग से भी बड़ी वैल्यू दर्शाता है, इसलिए व्यवहार में इसका मतलब है कि जिस संख्या में यह बिट सेट हो, वह हमेशा ऋणात्मक होती है। इस खास बिट को चिह्न बिट कहा जाता है।

चिह्नित संख्या को बड़े आकार में बदलने का मतलब है हर नए ऊपरी बिट में चिह्न बिट की एक प्रति भर देना, ताकि वैल्यू ज्यों की त्यों बनी रहे। इसे चिह्न विस्तार कहा जाता है।

movsx इंस्ट्रक्शन (s चिह्न के लिए) किसी 8-बिट या 16-बिट स्रोत ऑपरेंड का चिह्न विस्तार करके उसे बड़े गंतव्य ऑपरेंड में बदल देता है। movsx का ही एक रूप movsxd यही काम 32-बिट स्रोत ऑपरेंड से 64-बिट गंतव्य ऑपरेंड के लिए करता है।

किसी संख्या का चिह्न बदलने के लिए neg इंस्ट्रक्शन का उपयोग किया जा सकता है।

Caution

असेंबली में यह बताने का कोई तरीका नहीं है कि बाइटों का कोई क्रम किसी चिह्नित संख्या को दर्शाता है या अचिह्नित संख्या को। इन बाइटों को अर्थ देना प्रोग्रामर की ज़िम्मेदारी है।

इस काम में कमेंट लिखना बहुत मददगार हो सकता है।

इमीडिएट

पिछली किसी अवधारणा में बताया गया था कि 4 या -15 जैसी अचर संख्या कई इंस्ट्रक्शनों में स्रोत ऑपरेंड के रूप में इस्तेमाल की जा सकती है। ऐसी संख्याओं को इमीडिएट कहा जाता है।

इमीडिएट किसी रजिस्टर या मेमोरी में नहीं रहता। वह इंस्ट्रक्शन के अंदर ही एनकोड किया जाता है। ज़्यादातर इंस्ट्रक्शनों में इसके लिए रखी जगह सिर्फ 32 बिट चौड़ी होती है, चाहे गंतव्य ऑपरेंड कितना भी बड़ा हो।

जब गंतव्य ऑपरेंड 64 बिट चौड़ा होता है, तो उन 32 बिटों का चिह्न विस्तार करके उसे भर दिया जाता है। ऑपरेंड का ऊपरी आधा हिस्सा पूरी तरह इमीडिएट के सबसे ऊपरी बिट की प्रतियों से भर दिया जाता है। इसलिए इस तरह सिर्फ 32-बिट चिह्नित पूर्णांक की सीमा में आने वाली संख्या ही लिखी जा सकती है:

add rax, -1          ; the immediate is sign-extended, so all 64 bits of rax are affected
add rax, 2147483647  ; the largest immediate an instruction like this accepts

उस सीमा से बाहर की संख्या इमीडिएट के रूप में इस्तेमाल नहीं की जा सकती। इस नियम का अपवाद mov है, जो तब पूरा 64-बिट इमीडिएट ले सकता है जब गंतव्य ऑपरेंड एक रजिस्टर हो। अगर 64-बिट इमीडिएट की ज़रूरत हो, तो पहले mov से उसे किसी रजिस्टर में लोड कीजिए, फिर उस रजिस्टर का इस्तेमाल कीजिए:

mov rax, 3435973837           ; this works, mov can take a 64-bit immediate
mov rdx, 18446744073709551615 ; the largest immediate mov accepts
sub rdx, rax

ध्यान दीजिए कि एक ऋणात्मक इमीडिएट और वही बिट निरूपण रखने वाली अचिह्नित संख्या समान होती हैं, और दोनों असेंबल होकर बिल्कुल एक ही वैल्यू बनाती हैं:

mov rax, -1                   ; rax = 18446744073709551615
mov rax, 18446744073709551615 ; rax = -1

योग

दो संख्याओं का जोड़ add इंस्ट्रक्शन से निकाला जा सकता है।

एक ऑपरेंड वाला inc इंस्ट्रक्शन भी है, जो अपने ऑपरेंड की वैल्यू में 1 जोड़ देता है:

inc rax ; rax = rax + 1

दो पूर्णांकों का योग अचिह्नित और चिह्नित, दोनों तरह की संख्याओं के लिए एक ही तरह से काम करता है।

घटाव

दो पूर्णांकों का घटाव sub इंस्ट्रक्शन से किया जाता है।

एक ऑपरेंड वाला dec इंस्ट्रक्शन भी है, जो अपने ऑपरेंड की वैल्यू से 1 घटा देता है:

dec rax ; rax = rax - 1

दो पूर्णांकों का घटाव भी अचिह्नित और चिह्नित, दोनों तरह की संख्याओं के लिए एक ही तरह से काम करता है।

गुणा

x86-64 में दो संख्याओं का गुणा करने के लिए दो अलग इंस्ट्रक्शन हैं। सामान्यतः अचिह्नित गुणा में mul इंस्ट्रक्शन इस्तेमाल होता है, और चिह्नित गुणा में imul।

mul इंस्ट्रक्शन नीचे दिया गया एक-ऑपरेंड रूप लेता है, जिसमें src स्रोत ऑपरेंड है:

mul src

imul इंस्ट्रक्शन एक-ऑपरेंड, दो-ऑपरेंड या तीन-ऑपरेंड रूप ले सकता है:

imul src
imul dest, src
imul dest, src1, src2
एक-ऑपरेंड गुणा

एक-ऑपरेंड रूप में गुणा करने के लिए दो रजिस्टर अपने आप इस्तेमाल होते हैं: rax और rdx। अगर गुणा में दो 64-बिट संख्याएँ शामिल हों, तो नतीजे के निचले 64 बिट rax में और ऊपरी 64 बिट rdx में होंगे।

इसे आमतौर पर rdx:rax कहा जाता है, जो बताता है कि दोनों रजिस्टर साथ मिलकर काम करते हैं:

mul rcx ; rax = lower 64 bits of rax * rcx
        ; rdx = upper 64 bits of rax * rcx

बाकी ऑपरेंड आकारों के साथ भी ऐसा ही होता है। जैसे अगर दो 32-बिट संख्याओं का गुणा हो रहा हो, तो eax और edx इस्तेमाल होंगे।

अपवाद दो बाइटों के बीच के गुणा का है।

इस स्थिति में dl:al के बजाय ax इस्तेमाल होता है। ax के निचले हिस्से (al) में गुणनफल के निचले 8 बिट जाएँगे, और ऊपरी हिस्से (ah) में ऊपरी 8 बिट।

Caution

गुणा में अपने आप इस्तेमाल होने वाले रजिस्टरों, जैसे rax और rdx, में हमेशा नया मान लिख दिया जाता है। अगर इन रजिस्टरों की वैल्यू बाद में चाहिए हो, तो इन्हें काम से पहले सहेज लेना चाहिए।

दो-ऑपरेंड गुणा

imul के दो-ऑपरेंड रूप में गंतव्य ऑपरेंड स्पष्ट रूप से दिया जाता है और यह सामान्य सिंटैक्स का पालन करता है। इसमें rdx इस्तेमाल नहीं होता। इसके बजाय नतीजे को गंतव्य ऑपरेंड में फिट होने लायक छोटा कर दिया जाता है।

imul r8, r9 ; r8 = lower 64 bits of r8 * r9
तीन-ऑपरेंड गुणा

imul के तीन-ऑपरेंड रूप में दो स्रोत ऑपरेंड होते हैं, जिनमें दूसरा हमेशा एक इमीडिएट (अचर संख्या) होता है। दोनों स्रोत ऑपरेंडों का गुणा किया जाता है और नतीजे को छोटा करके गंतव्य ऑपरेंड में रख दिया जाता है:

imul r8, r9, 100 ; r8 = lower 64 bits of r9 * 100

ध्यान दीजिए कि गुणा में गंतव्य ऑपरेंड इस्तेमाल नहीं होता। वह सिर्फ नतीजा लेता है।

ओवरफ्लो को संभालना

दो-ऑपरेंड और तीन-ऑपरेंड, दोनों गुणा नतीजे को गंतव्य ऑपरेंड के आकार में फिट होने लायक छोटा कर देते हैं। एक-ऑपरेंड गुणा पूरी सीमा बनाए रखता है, लेकिन वह आमतौर पर दो रजिस्टरों, rdx और rax, में बाँटा जाता है।

इसलिए कभी-कभी यह काम आता है कि गुणा से पहले ऑपरेंडों को चौड़ा कर लिया जाए, ताकि पूरा गुणनफल एक ही रजिस्टर में समा जाए। अचिह्नित ऑपरेंड का शून्य विस्तार किया जाता है, और चिह्नित ऑपरेंड का चिह्न विस्तार:

movzx eax, di ; di and si hold unsigned 16-bit numbers
movzx ecx, si
mul ecx       ; the 32-bit product fits in eax, and edx is cleared

भाग

गुणा की तरह भाग के लिए भी दो इंस्ट्रक्शन हैं। अचिह्नित भाग में div इंस्ट्रक्शन इस्तेमाल होता है, और चिह्नित भाग में idiv।

दोनों इंस्ट्रक्शन सिर्फ एक ऑपरेंड के साथ काम करते हैं:

div src
idiv src

16-बिट, 32-बिट और 64-बिट भाग में क्रमशः dx:ax, edx:eax और rdx:rax को भाज्य माना जाता है। इन स्थितियों में दोनों रजिस्टर साथ मिलकर एक 2N-बिट वैल्यू बनाते हैं, जहाँ N उस काम का आकार है (16-बिट, 32-बिट या 64-बिट)। फिर इस वैल्यू को स्रोत ऑपरेंड से भाग दिया जाता है। काम के आकार के हिसाब से भागफल ax, eax या rax में लिखा जाता है, और शेष dx, edx या rdx में।

बाइटों के बीच का भाग खास है: इसमें dl:al के बजाय ax इस्तेमाल होता है। ax के निचले 8 बिट (al) में भाग का भागफल जाएगा, और ऊपरी 8 बिट (ah) में शेष।

ध्यान दीजिए कि भाग से पहले भाज्य के सभी बिट ठीक से सेट होने चाहिए। rdx में सेट कोई भी बिट (या 8-बिट भाग में ah में सेट बिट) उस वैल्यू में जुड़ता है जिसका भाग किया जा रहा है।

अचिह्नित भाग में, जब जिस वैल्यू का भाग करना है वह निचले आधे हिस्से में समा जाए, तो ऊपरी आधा हिस्सा शून्य कर देना चाहिए। इन बिटों को शून्य करने वाला कोई भी इंस्ट्रक्शन चलेगा। जैसे 32-बिट भाग में mov edx, 0 ऊपरी बिटों को शून्य कर देता है।

चिह्नित भाग में इसके बजाय वैल्यू का चिह्न विस्तार करना चाहिए। इस काम को अपने आप करने वाले इंस्ट्रक्शन भी हैं: cbw, cwd, cdq और cqo। पहला al के चिह्न के हिसाब से ah के बिट सेट करता है। बाकी तीन क्रमशः ax से dx, eax से edx और rax से rdx तक चिह्न विस्तार करते हैं।

Caution

भाग में अपने आप इस्तेमाल होने वाले रजिस्टरों, जैसे rax और rdx, में हमेशा नया मान लिख दिया जाता है। अगर इन रजिस्टरों की वैल्यू बाद में चाहिए हो, तो इन्हें भाग से पहले सहेज लेना चाहिए।

निर्देश

एक स्थानीय दुकान अपना माल एक बड़े गोदाम में ले जा रही है। आपको सारा सामान पैक करके ले जाने के लिए काम पर रखा गया है।

आपके पास चार काम हैं, और सबका संबंध ढुलाई संभालने से है।

Note

इस कॉन्सेप्ट में जिन इंस्ट्रक्शनों का उल्लेख है, वे ये हैं:

इंस्ट्रक्शन विवरण
add a, b a = a + b
inc a a = a + 1
sub a, b a = a - b
dec a a = a - 1
imul a rdx:rax = a * rax (साइन्ड)
imul a, b a = a * b (साइन्ड, ट्रंकेटेड)
imul a, b, c a = b * c (साइन्ड, ट्रंकेटेड)
mul a rdx:rax = a * rax (अनसाइन्ड)
div a rax = भागफल, rdx = rdx:rax / a का शेषफल (अनसाइन्ड)
idiv a rax = भागफल, rdx = rdx:rax / a का शेषफल (साइन्ड)
movzx a, b a = b, अतिरिक्त बिट्स में 0 जोड़कर
movsx a, b a = b, अगर b < 0 हो तो अतिरिक्त बिट्स में 1 जोड़कर, वरना 0
Note

याद रखिए कि ऑपरेंड का नाम बदलकर आप एक ही रजिस्टर को अलग-अलग साइज़ में इस्तेमाल कर सकते हैं। उदाहरण के लिए: rax (64-बिट), eax (32-बिट), ax (16-बिट), al (8-बिट)।

पूरी टेबल के लिए आप पिछले कॉन्सेप्ट को देख सकते हैं।

1. हर डिब्बे का वज़न निकालिए

वस्तुएँ ऐसे डिब्बों में पैक की जा रही हैं जिन पर उनका वज़न लिखा होना चाहिए। आसपास कोई तराज़ू नहीं है, लेकिन अच्छी बात है कि आप जानते हैं कि हरेक वस्तु का औसत वज़न कितना होता है।

चीज़ों को बेहतर ढंग से व्यवस्थित करने के लिए, एक डिब्बे में सिर्फ दो अलग-अलग उत्पादों की वस्तुएँ ही रखी जाती हैं।

एक फंक्शन get_box_weight बनाइए जो किसी डिब्बे का कुल वज़न g में लौटाता है। यह फंक्शन इस क्रम में ये पैरामीटर लेता है:

  • डिब्बे में पहले उत्पाद की वस्तुओं की संख्या
  • पहले उत्पाद की हर वस्तु का वज़न, g में
  • डिब्बे में दूसरे उत्पाद की वस्तुओं की संख्या
  • दूसरे उत्पाद की हर वस्तु का वज़न, g में

मान लीजिए कि खाली डिब्बे का वज़न 500 g होता है। सॉल्यूशन फाइल के सबसे ऊपर एक कॉन्स्टेंट WEIGHT_OF_EMPTY_BOX दिया गया है।

उदाहरण:

get_box_weight(30, 40, 50, 20);
// => 2700

सभी आर्गुमेंट 16-बिट गैर-ऋणात्मक पूर्णांक हैं, और रिटर्न वैल्यू 32-बिट गैर-ऋणात्मक पूर्णांक है।

2. ट्रक में कितने डिब्बे समा सकते हैं, इसकी गणना कीजिए

डिब्बों को एक के ऊपर एक रखकर ट्रक से नए गोदाम में ले जाया जा रहा है। लेकिन ट्रक के अंदर ऊँचाई की जगह सीमित है।

एक फंक्शन max_number_of_boxes बनाइए जो लौटाता है कि ट्रक के अंदर एक तय ऊँचाई के कितने डिब्बे एक के ऊपर एक रखे जा सकते हैं।

यह फंक्शन पैरामीटर के रूप में डिब्बे की ऊँचाई cm में लेता है। मान लीजिए कि ट्रक के अंदर की ऊँचाई 300 cm है। सॉल्यूशन फाइल के सबसे ऊपर एक कॉन्स्टेंट TRUCK_HEIGHT दिया गया है।

उदाहरण:

max_number_of_boxes(30);
// => 10

आर्गुमेंट और रिटर्न वैल्यू 8-बिट गैर-ऋणात्मक पूर्णांक हैं। डिब्बे की ऊँचाई हमेशा कम से कम 2 होती है, इसलिए परिणाम 8 बिट में आ जाता है।

3. जाँचिए कि सभी उत्पादों का हिसाब पूरा है या नहीं

नए गोदाम में एक चेकलिस्ट है, जिसमें हर उत्पाद की उन वस्तुओं की संख्या लिखी है जिनका हिसाब अभी बाकी है। वहाँ पहुँचने वाले हर नए डिब्बे के लिए आपको डिब्बे में मौजूद हर उत्पाद के लिए चेकलिस्ट की नई वैल्यू निकालनी है।

एक फंक्शन items_to_be_moved बनाइए जो लौटाता है कि किसी दिए गए उत्पाद की कितनी वस्तुएँ अभी नए गोदाम तक ले जानी बाकी हैं। यह फंक्शन इस क्रम में ये पैरामीटर लेता है:

  • किसी उत्पाद की उन वस्तुओं की संख्या जिनका हिसाब अभी बाकी है
  • एक डिब्बे में उस उत्पाद की वस्तुओं की संख्या

उदाहरण:

items_to_be_moved(76532, 120);
// => 76412

आर्गुमेंट 32-बिट गैर-ऋणात्मक पूर्णांक हैं। रिटर्न वैल्यू 32-बिट पूर्णांक है। प्रक्रिया में कोई एरर होने पर यह संभव है कि परिणाम एक ऋणात्मक संख्या हो।

4. अपना भुगतान निकालिए

आपका भुगतान इस पर निर्भर करता है कि कितने डिब्बे ले जाए गए और ट्रक के कितने चक्कर लगे। हर डिब्बे के लिए आपको 5 डॉलर मिलेंगे और हर चक्कर के लिए 220 डॉलर। सॉल्यूशन फाइल के सबसे ऊपर कॉन्स्टेंट PAY_PER_BOX और PAY_PER_TRUCK_TRIP दिए गए हैं।

ध्यान दीजिए कि शुरुआती खर्च निकालने के लिए आपको इस भुगतान का कुछ हिस्सा पहले ही मिल चुका हो सकता है, और यह पहले मिला भुगतान अंतिम भुगतान से घटाया जाना चाहिए। इसके अलावा, कुछ उत्पाद बीमा के दायरे में नहीं आते, और उनमें से टूटी या गुम हुई किसी भी वस्तु की कीमत भी आपके भुगतान से घटा दी जाएगी। अगर आप ध्यान नहीं रखेंगे, तो यह भी हो सकता है कि आप पर पैसा बाकी हो जाए!

इसका मतलब है कि जो शुद्ध राशि आपको मिलनी है, या जो आपको चुकानी है, वह यह है:

net = boxes * PAY_PER_BOX + trips * PAY_PER_TRUCK_TRIP - up_front - broken_items * item_value

यह भुगतान, या कर्ज़, आपके और आपके काम पर रखे कुछ कामगारों के बीच बराबर बाँटा जाएगा। जो पैसा या कर्ज़ बच जाए, वह आपका है। उदाहरण के लिए, अगर शुद्ध राशि 100 है और उसे 6 लोगों (आप और 5 कामगार) में बाँटा जा रहा है, तो आपको 20 मिलेंगे (100/(5 + 1) = 16 और बाकी बचे 4)।

एक फंक्शन calculate_payment बनाइए जो लौटाता है कि अंत में आपको कितना मिलना चाहिए, या आपको कितना चुकाना चाहिए। यह फंक्शन इस क्रम में ये पैरामीटर लेता है:

  • आपको पहले से कितना मिल चुका है, 64-बिट गैर-ऋणात्मक पूर्णांक के रूप में
  • ले जाए गए डिब्बों की कुल संख्या, 32-बिट गैर-ऋणात्मक पूर्णांक के रूप में
  • ट्रक के लगाए गए चक्करों की संख्या, 32-बिट गैर-ऋणात्मक पूर्णांक के रूप में
  • टूटी या गुम हुई वस्तुओं की संख्या, 32-बिट गैर-ऋणात्मक पूर्णांक के रूप में
  • खोई हुई हर वस्तु की कीमत, 64-बिट गैर-ऋणात्मक पूर्णांक के रूप में
  • आपके साथ भुगतान या कर्ज़ बाँटने वाले कामगारों की संख्या, 8-बिट धनात्मक पूर्णांक के रूप में

उदाहरण:

calculate_payment(2000, 1000, 5, 21, 2, 1);
// => 2029

रिटर्न वैल्यू 64-बिट पूर्णांक है।

GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
x86-64 Assembly Exercism

इन्वेंटरी प्रबंधन शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।