সাইনড নাম্বার রিপ্রেজেন্টেশন — Two's Complement
এই পাঠে যা শিখবেন
- সাইন-ম্যাগনিটিউড পদ্ধতির সমস্যা — কেন এটি আধুনিক হার্ডওয়্যারে ব্যবহৃত হয় না
- Two's Complement-এর সঠিক নিয়ম — ইনভার্ট + ১ যোগ — এবং কেন এটি L09-এর সাবট্রাক্টর ট্রিকের ভিত্তি
- N-বিট two's complement রেঞ্জ, এবং কেন এটি অ্যাসিমেট্রিক (positive-এর চেয়ে negative একটি বেশি)
- ক্যারি-ইন-বনাম-ক্যারি-আউট নিয়মে ওভারফ্লো সঠিকভাবে সনাক্ত করা — কোড দিয়ে ভেরিফাই করে
১ · সমস্যা — ঋণাত্মক সংখ্যা বাইনারিতে কীভাবে?
L02-L03-এ যে বাইনারি শিখেছেন তা শুধু অ-ঋণাত্মক (unsigned) সংখ্যা প্রকাশ করতে পারে — 4-বিট বাইনারি দিয়ে সর্বোচ্চ 0000 থেকে 1111 (0 থেকে 15) প্রকাশ করা যায়, কিন্তু -5-এর মতো একটি সংখ্যা কীভাবে প্রকাশ করব? হার্ডওয়্যারের কাছে শুধু 0 আর 1 আছে — "ঋণাত্মক চিহ্ন" বলে আলাদা কোনো সিম্বল নেই, তাই এটি বিটের মধ্যেই এনকোড করতে হয়।
সবচেয়ে সরল-মনে-হওয়া সমাধান হলো সাইন-ম্যাগনিটিউডSign-Magnitudeসবচেয়ে বাম বিট শুধু চিহ্ন (0=ধনাত্মক, 1=ঋণাত্মক), বাকি বিট মান — সহজ কিন্তু সমস্যাযুক্ত। — সবচেয়ে বাম বিটকে শুধু একটি "চিহ্ন ফ্ল্যাগ" (0 = ধনাত্মক, 1 = ঋণাত্মক) হিসেবে সংরক্ষণ করা, বাকি বিটগুলো ম্যাগনিটিউড (পরম মান)। যেমন 4-বিটে +5 = 0101 আর -5 = 1101। বোঝা সহজ, কিন্তু এর দুটি বাস্তব সমস্যা আছে —
- শূন্যের দুটি প্রতিনিধিত্ব: +0 = 0000 আর -0 = 1000 — দুটোই "শূন্য" বোঝায় কিন্তু বিট-প্যাটার্ন আলাদা, যা তুলনা ও লজিক জটিল করে তোলে।
- জটিল আর্দমেটিক সার্কিট: যোগ-বিয়োগের সময় চিহ্ন আলাদা করে পরীক্ষা করতে হয়, ফলে সাধারণ অ্যাডার সরাসরি পুনর্ব্যবহার করা যায় না।
২ · Two's Complement — আধুনিক সার্বজনীন মান
এই সমস্যা দুটোই এড়াতে আধুনিক হার্ডওয়্যার ব্যবহার করে Two's ComplementTwo's Complementএকটি সংখ্যা ঋণাত্মক করতে সব বিট ইনভার্ট করে ১ যোগ করা হয় — শূন্যের একটিই প্রতিনিধিত্ব, সরল অ্যাডার সার্কিট পুনর্ব্যবহারযোগ্য। — নিয়মটি নিখুঁতভাবে সরল: একটি সংখ্যাকে ঋণাত্মক করতে হলে, তার সব বিট ইনভার্ট করো (0→1, 1→0), তারপর ফলাফলে ১ যোগ করো। এটি ঠিক L09-এর অ্যাডার/সাবট্রাক্টর ট্রিকের ভিত্তি — সেখানে বলা হয়েছিল B-কে ইনভার্ট করে Cin=1 দিলে "-B" পাওয়া যায় — এই পাঠেই সেই দাবিটার আসল কারণ প্রমাণিত হচ্ছে।
ওয়ার্কড উদাহরণ — 4-বিট two's complement-এ -5 বের করা যাক —
- শুরু করুন +5 দিয়ে:
0101 - সব বিট ইনভার্ট করুন:
1010 - ১ যোগ করুন:
1010 + 1 = 1011
যাচাই — 1011 সত্যিই -5 বোঝায় কিনা তা প্রতিটি বিটের স্থান-মান (place value) দিয়ে
পরীক্ষা করা যায়। Two's complement-এ সবচেয়ে বাম বিটের মান আর শুধু একটি "চিহ্ন" নয় — এটি একটি প্রকৃত ঋণাত্মক
স্থান-মান, ঠিক $-2^{N-1}$।
৩ · রেঞ্জ ও ওভারফ্লো
N-বিট two's complement দিয়ে প্রকাশযোগ্য রেঞ্জ —
লক্ষ্য করুন রেঞ্জটি অ্যাসিমেট্রিক — negative দিকে একটি সংখ্যা বেশি আছে ধনাত্মক দিকের চেয়ে (4-বিটে: -8 থেকে +7, অর্থাৎ -8 আছে কিন্তু +8 নেই)। কারণ, 0 নিজে একটি "ধনাত্মক-সাইড" স্লট ব্যবহার করে ফেলে — মোট $2^N$ টি বিট-প্যাটার্নের মধ্যে একটি 0-এর জন্য বরাদ্দ হওয়ায় ধনাত্মক সংখ্যার জন্য একটি কম বাকি থাকে।
ওভারফ্লোOverflowদুটি প্রকাশযোগ্য সংখ্যা যোগ করলে ফলাফল প্রকাশযোগ্য রেঞ্জের বাইরে চলে যাওয়া — হার্ডওয়্যারকে অবশ্যই এটি সনাক্ত করতে হয়।: দুটি প্রকাশযোগ্য সংখ্যা যোগ করলেও ফলাফল রেঞ্জের বাইরে চলে যেতে পারে। সনাক্ত করার নিয়মটি নিখুঁত ও precise — সাইন-বিটে (সবচেয়ে বাম বিট পজিশনে) ঢোকা ক্যারি (carry-in) আর সেই একই বিট পজিশন থেকে বের হওয়া ক্যারি (carry-out) যদি আলাদা হয়, তাহলে ওভারফ্লো ঘটেছে।
৪ · কোড দিয়ে ভেরিফাই — রাউন্ড-ট্রিপ ও ওভারফ্লো সনাক্তকরণ
নিচের কোড সেলে to_twos_complement ও from_twos_complement — দুটোই বাস্তব, ফ্রম-স্ক্র্যাচ
ফাংশন হিসেবে লেখা হলো (ইনভার্ট + ১ যোগ নিয়ম অনুসরণ করে), এবং edge case (সবচেয়ে ঋণাত্মক মান, শূন্য) সহ একাধিক
মান রাউন্ড-ট্রিপ করে যাচাই করা হলো। এরপর ওভারফ্লো-ডিটেকশন নিয়মটি একটি সত্যিকারের ওভারফ্লো-ঘটা কেসে (5+5, যা
4-বিট রেঞ্জ [-8,7]-এর বাইরে) এবং একটি স্বাভাবিক কেসে (5+(-3)) পরীক্ষা করা হলো।
# Two's Complement কনভার্সন -- বাস্তব বিট-স্ট্রিং নিয়ে কাজ করা সিমুলেশন, কোনো বাস্তব হার্ডওয়্যার অ্যাক্সেস নয়
def to_twos_complement(n, bits):
"""একটি সাইনড ইন্টিজার n-কে bits-বিট two's complement বিট-স্ট্রিং-এ রূপান্তর করে"""
if n >= 0:
b = bin(n)[2:].zfill(bits)
if len(b) > bits:
raise ValueError(f"{n} এই {bits} বিটে ধরবে না")
return b
else:
positive_bits = to_twos_complement(-n, bits) # প্রথমে ম্যাগনিটিউড
inverted = "".join("1" if c == "0" else "0" for c in positive_bits) # সব বিট ইনভার্ট
inverted_value = int(inverted, 2)
result_value = (inverted_value + 1) % (2 ** bits) # ১ যোগ
return bin(result_value)[2:].zfill(bits)
def from_twos_complement(bits_str):
"""একটি two's complement বিট-স্ট্রিং-কে সাইনড দশমিক ইন্টিজারে রূপান্তর করে"""
n = len(bits_str)
value = int(bits_str, 2)
if bits_str[0] == "1": # সাইন বিট সেট -- ঋণাত্মক স্থান-মান বিয়োগ করা লাগবে
value -= 2 ** n
return value
print("=== রাউন্ড-ট্রিপ যাচাই (4-বিট) ===")
for n in [5, -5, 0, 7, -8, 1, -1]:
bits = to_twos_complement(n, 4)
back = from_twos_complement(bits)
status = "OK" if back == n else "MISMATCH!"
print(f"{n:>3} -> {bits} -> {back:>3} {status}")
def add_with_sign_bit_carries(a_bits, b_bits):
"""সাধারণ বাইনারি যোগ, কিন্তু সাইন-বিট পজিশনের carry-in ও carry-out আলাদাভাবে ট্র্যাক করে"""
n = len(a_bits)
carry = 0
result = ["0"] * n
carry_into_sign_bit = None
for i in range(n - 1, -1, -1):
if i == 0:
carry_into_sign_bit = carry
a, b = int(a_bits[i]), int(b_bits[i])
s = a + b + carry
result[i] = str(s % 2)
carry = s // 2
carry_out_of_sign_bit = carry
return "".join(result), carry_into_sign_bit, carry_out_of_sign_bit
print()
print("=== ওভারফ্লো ডিটেকশন (4-বিট, রেঞ্জ -8..7) ===")
for x, y, label in [(5, 5, "5 + 5 (ওভারফ্লো হওয়া উচিত)"), (5, -3, "5 + (-3) (স্বাভাবিক)")]:
a, b = to_twos_complement(x, 4), to_twos_complement(y, 4)
result, cin, cout = add_with_sign_bit_carries(a, b)
overflow = cin != cout
print(f"{label}: {a} + {b} = {result} carry_in={cin} carry_out={cout} overflow={overflow}")
5 + 5-এর ক্ষেত্রে carry_in=1 কিন্তু carry_out=0 (আলাদা) — তাই overflow=True রিপোর্ট হয়,
যা সঠিক (10 চার-বিট signed রেঞ্জ [-8,7]-এর বাইরে)। অন্যদিকে 5 + (-3)-এ carry_in=1 এবং carry_out=1
(একই) — তাই overflow=False, যা সঠিক (ফলাফল 2, রেঞ্জের মধ্যেই)।
Two's complement-এর আসল সৌন্দর্য এখানেই — একই ইনভার্ট+১ নিয়ম শূন্যের একক প্রতিনিধিত্ব নিশ্চিত করে, আর একই সাধারণ অ্যাডার সার্কিট (L09) বিয়োগও চালাতে পারে বাড়তি কোনো হার্ডওয়্যার ছাড়াই। পরের পাঠে (L19) ঠিক এই বিট-প্যাটার্নগুলো L09-এর ripple-carry adder-এর মধ্য দিয়ে চালিয়ে সত্যিকারের সাইনড যোগ-বিয়োগ করা হবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ 4-বিট two's complement-এ -8 আছে কিন্তু +8 নেই কেন — এটা কি একটা ডিজাইন ভুল?
ভুল নয়, বরং গাণিতিকভাবে অনিবার্য একটি ফলাফল। 4 বিট দিয়ে মোট $2^4=16$টি ভিন্ন বিট-প্যাটার্ন তৈরি করা যায় — এই ১৬টির একটি অবশ্যই শূন্যের জন্য বরাদ্দ হবে (two's complement-এ শুধু একটি শূন্য আছে, যা এমনিতেই একটি সুবিধা)। বাকি ১৫টি ধনাত্মক ও ঋণাত্মক সংখ্যার মধ্যে ভাগ হতে হবে — যেহেতু ১৫ বিজোড় সংখ্যা, সমান ভাগ অসম্ভব, তাই একদিকে (ঐতিহাসিক কনভেনশন অনুযায়ী ঋণাত্মক দিকে) একটি বেশি পড়ে — ফলে -8 থেকে +7 (৮টি ঋণাত্মক, ৭টি ধনাত্মক, ১টি শূন্য)।
প্র ০২ সাইন-ম্যাগনিটিউড পদ্ধতিতে "শূন্যের দুটি প্রতিনিধিত্ব" আসলে ঠিক কী সমস্যা তৈরি করে?
হার্ডওয়্যারের একটি তুলনা (comparison) সার্কিট যদি শুধু বিট-প্যাটার্ন সরাসরি মেলায়, তাহলে +0 (0000) আর -0 (1000)-কে দুটো ভিন্ন সংখ্যা মনে করবে, যদিও গাণিতিকভাবে তারা সমান — এই বিশেষ কেসটি আলাদাভাবে হ্যান্ডেল করতে হয়, যা তুলনা ও যোগ-বিয়োগ সার্কিট উভয়কেই জটিল করে তোলে। Two's complement-এ শূন্যের মাত্র একটি প্রতিনিধিত্ব (0000) থাকায় এই বিশেষ-কেস হ্যান্ডলিং সম্পূর্ণ অপ্রয়োজনীয় হয়ে যায়।
প্র ০৩
উপরের কোডে from_twos_complement ফাংশনে সাইন-বিট সেট থাকলে 2**n বিয়োগ করা হয় কেন?
int(bits_str, 2) সব সময় বিট-স্ট্রিংটাকে একটা unsigned মান হিসেবে পড়ে — যেমন 4-বিট
"1011"-কে unsigned ধরলে এটা 11 (দশমিক)। কিন্তু two's complement-এ সবচেয়ে বাম বিটের প্রকৃত স্থান-মান
$+2^{N-1}$ নয়, বরং $-2^{N-1}$। unsigned রিডিং এই একটি বিটের মান ভুলভাবে ধনাত্মক ধরে ফেলে — তাই সেই বিটের
"ভুল ধনাত্মক অবদান" আর "সঠিক ঋণাত্মক অবদান"-এর মধ্যেকার পার্থক্য, অর্থাৎ ঠিক $2^N$, বিয়োগ করে দিলেই সঠিক
সাইনড মান পাওয়া যায় (11 - 16 = -5, যা সঠিক)।
অনুশীলন
-
হাতে-কলমে করুন: 4-বিট two's complement-এ -6-এর বিট-প্যাটার্ন বের করুন (ইনভার্ট+১ যোগ নিয়ম প্রয়োগ করে), তারপর স্থান-মান দিয়ে যাচাই করুন এটি আসলেই -6 বোঝায় কিনা।
+6 = 0110। সব বিট ইনভার্ট: 1001। ১ যোগ: 1001+1 = 1010। যাচাই: $1\times(-8)+0\times4+1\times2+0\times1 = -8+0+2+0 = -6$ — সঠিক।
-
পরীক্ষা করুন: উপরের কোড সেলে
to_twos_complement(-9, 4)কল করলে কী ঘটবে বলে মনে করেন — কেন?এটি একটি এরর দেবে, কারণ ফাংশনটি প্রথমে ম্যাগনিটিউড 9-কে
to_twos_complement(9, 4)দিয়ে বাইনারিতে রূপান্তর করার চেষ্টা করবে — কিন্তু 9-কে 4-বিটে (সর্বোচ্চ unsigned মান 15 হলেও, এই হেল্পার-কল আসলে ম্যাগনিটিউড হিসেবে ব্যবহৃত হচ্ছে যার জন্য 4 বিটে সর্বোচ্চ 15 প্রকাশযোগ্য, কিন্তু চূড়ান্তভাবে -9, 4-বিট two's complement-এর বৈধ রেঞ্জ -8 থেকে 7-এর বাইরে) — ফলে ফাংশনটিValueErrorতুলবে, ঠিক যেভাবে বাস্তব হার্ডওয়্যারও একটি অপ্রকাশযোগ্য মান এনকোড করতে পারে না।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ পরের পাঠে (L19) এই একই বিট-প্যাটার্নগুলো L09-এর ripple-carry adder দিয়ে চালিয়ে সত্যিকারের সাইনড যোগ-বিয়োগ দেখানো হবে।
- পাঠ ০৯ · রিপল ক্যারি অ্যাডার ও সাবট্রাক্টর সরাসরি ভিত্তি এই পাঠের ইনভার্ট+১ নিয়মটাই ছিল L09-এর অ্যাডার/সাবট্রাক্টর ট্রিকের পেছনের প্রকৃত কারণ — এখন সংযোগটি সম্পূর্ণ স্পষ্ট।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems ও Computer Architecture — সব এক জায়গায়।