পাঠ ২১ · ৫৭-এর মধ্যে · মডিউল ৪
Home / Courses / Computer Architecture & Digital Logic / বাইনারি ডিভিশন

বাইনারি ডিভিশন

Binary division
৮ মিনিট পড়া মধ্যম-উচ্চ · Advanced Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • Restoring Division অ্যালগরিদমের ঠিক-নিয়ম — শিফট, বিয়োগ, রিস্টোর-বা-কিপ সিদ্ধান্ত
  • $13 \div 3$-এর প্রতিটি বিট-পজিশনের সিদ্ধান্ত কোডের প্রকৃত আউটপুট দিয়ে ধাপে ধাপে ট্রেস করা
  • quotient × divisor + remainder = dividend দিয়ে ফলাফল পুনর্গঠন করে যাচাই করা
  • division-by-zero কেস আগেভাগেই স্পষ্টভাবে চেক করে রিজেক্ট করা — কেন এটি বাধ্যতামূলক

১ · Restoring Division — হাতে-করা লম্বা ভাগের সার্কিট সংস্করণ

M4-এর এতদিনের প্রতিটি পাঠে (L18-L20) আমরা যোগ, বিয়োগ ও গুণ দেখেছি — এই শেষ পাঠে ভাগ। ক্লাসিক সার্কিট-লেভেল পদ্ধতিটির নাম Restoring DivisionRestoring Divisionহাতে-করা লম্বা ভাগের বাইনারি সংস্করণ — প্রতিটি বিট পজিশনে শিফট, বিয়োগ এবং প্রয়োজনে বিয়োগ "রিস্টোর" (আনডু) করার ধাপ নিয়ে গঠিত। — এটি অনেকটা হাতে-করা লম্বা ভাগের মতোই, শুধু বিট-বাই-বিট কাজ করে।

অ্যালগরিদম রূপরেখা — একটি রেমেইন্ডার রেজিস্টার (শুরুতে ০) ও একটি ডিভিডেন্ড রেজিস্টার রাখা হয়; প্রতিটি বিট পজিশনের জন্য (সবচেয়ে গুরুত্বপূর্ণ বিট থেকে সবচেয়ে কম গুরুত্বপূর্ণ বিট পর্যন্ত) —

  1. রেমেইন্ডার:ডিভিডেন্ড একত্রে (combined) এক বিট বামে শিফট করো।
  2. রেমেইন্ডার অংশ থেকে ডিভাইজার বিয়োগ করো।
  3. ফলাফল ঋণাত্মক হলে — "রিস্টোর" করো (বিয়োগ আনডু করে ডিভাইজার আবার যোগ করে দাও) এবং বর্তমান quotient বিট = ০ সেট করো; ফলাফল অ-ঋণাত্মক হলে — বিয়োগের ফলাফলই রাখো এবং quotient বিট = ১ সেট করো।

সব বিট পজিশন প্রসেস হওয়ার পর, quotient রেজিস্টারে উত্তর (ভাগফল) আর রেমেইন্ডার রেজিস্টারে চূড়ান্ত ভাগশেষ থাকে।

২ · ওয়ার্কড উদাহরণ — $13 \div 3$, প্রত্যাশিত quotient$=4$, remainder$=1$

যাচাই ($3\times4+1=13$) আগে থেকেই নিশ্চিত করা যায়, কিন্তু অ্যালগরিদম সত্যিই সঠিকভাবে এই উত্তরে পৌঁছায় কিনা তা নিচের কোড সেলে প্রতিটি বিট-পজিশনের শিফট/বিয়োগ/রিস্টোর-বা-কিপ সিদ্ধান্ত প্রিন্ট করে দেখানো হচ্ছে — L19-এর subtractor লজিক পুনর্ব্যবহার করে।

Python
# Restoring Division -- বিট-লেভেল সিমুলেশন, L19-এর subtractor লজিক পুনর্ব্যবহার করে
# (toy বিট-স্ট্রিং রেজিস্টার সিমুলেশন -- কোনো বাস্তব CPU ইনস্ট্রাকশন নয়)

def full_adder(a, b, cin):
    s = a ^ b ^ cin
    cout = (a & b) | (cin & (a ^ b))
    return s, cout

def ripple_carry_adder(a_bits, b_bits, cin=0):
    n = len(a_bits)
    result = ["0"] * n
    carry = cin
    for i in range(n - 1, -1, -1):
        a, b = int(a_bits[i]), int(b_bits[i])
        s, carry = full_adder(a, b, carry)
        result[i] = str(s)
    return "".join(result), carry

def subtract_bits(a_bits, b_bits):
    """L19-এর adder_subtractor(subtract_mode=True) -- b ইনভার্ট করে cin=1 দেয়"""
    inverted_b = "".join("1" if c == "0" else "0" for c in b_bits)
    return ripple_carry_adder(a_bits, inverted_b, cin=1)

def restoring_division(dividend, divisor, bits):
    """Restoring Division -- প্রতিটি বিট-পজিশনের সিদ্ধান্ত ট্রেস করে quotient, remainder রিটার্ন করে"""
    if divisor == 0:
        raise ZeroDivisionError("divisor শূন্য -- অ্যালগরিদম চালানোর আগেই রিজেক্ট করা হলো")
    if dividend >= 2 ** bits or divisor >= 2 ** bits:
        raise ValueError(f"অপারেন্ড {bits}-বিটে ধরবে না")

    remainder = "0" * (bits + 1)                     # সাইন-সহ কাজের জন্য ১ বিট বাড়তি
    quotient = bin(dividend)[2:].zfill(bits)
    divisor_bits = "0" + bin(divisor)[2:].zfill(bits)  # remainder-এর সমান প্রস্থে

    trace = []
    for step in range(1, bits + 1):
        combined = (remainder + quotient)[1:] + "0"    # remainder:quotient একত্রে বামে শিফট
        remainder = combined[:bits + 1]
        quotient = combined[bits + 1:]

        sub_result, _ = subtract_bits(remainder, divisor_bits)
        is_negative = sub_result[0] == "1"              # সাইন বিট চেক

        if is_negative:
            decision = "ঋণাত্মক -> রিস্টোর (বিয়োগ বাতিল), quotient bit = 0"
            quotient_bit = "0"
            # remainder অপরিবর্তিত থাকে (রিস্টোর)
        else:
            decision = "অ-ঋণাত্মক -> ফলাফল রাখা হলো, quotient bit = 1"
            quotient_bit = "1"
            remainder = sub_result

        quotient = quotient[:-1] + quotient_bit
        trace.append((step, remainder, quotient, sub_result, decision))

    return int(quotient, 2), int(remainder, 2), trace

quotient, remainder, trace = restoring_division(13, 3, 4)

print("13 ÷ 3, 4-বিট Restoring Division")
print("-" * 60)
for step, rem, quo, sub, decision in trace:
    print(f"ধাপ {step}: বিয়োগের ফলাফল={sub}  {decision}")
    print(f"        নতুন remainder={rem}  quotient(এখন পর্যন্ত)={quo}")

print()
print(f"চূড়ান্ত ভাগফল (quotient) = {quotient}")
print(f"চূড়ান্ত ভাগশেষ (remainder) = {remainder}")
print(f"পুনর্গঠন-যাচাই: {quotient} x 3 + {remainder} = {quotient * 3 + remainder}  (মূল dividend 13 হওয়া উচিত)")
assert quotient * 3 + remainder == 13
assert (quotient, remainder) == (13 // 3, 13 % 3)
print("cross-check (Python // এবং %):", 13 // 3, 13 % 3, "-- MATCH")

print()
print("=== division-by-zero হ্যান্ডলিং ===")
try:
    restoring_division(13, 0, 4)
except ZeroDivisionError as e:
    print(f"সঠিকভাবে রিজেক্ট হলো -> ZeroDivisionError: {e}")

    
কোডের আউটপুটে ধাপ ১-এ বিয়োগের ফলাফলের সাইন-বিট ১ (ঋণাত্মক) থাকে, তাই সেই ধাপে রিস্টোর হয় ও quotient bit=০ বসে — কিন্তু ধাপ ২-এ বিয়োগের ফলাফল অ-ঋণাত্মক, তাই সেটি রাখা হয় ও quotient bit=১ বসে। চারটি ধাপ শেষে quotient বিট-বাই-বিট মিলে দাঁড়ায় $0100_2 = 4$, আর remainder দাঁড়ায় $0001_2=1$ — ঠিক যা প্রত্যাশিত ছিল, এবং $4\times3+1=13$ পুনর্গঠন-চেকও পাস করেছে।
মূল কথা · Key takeaway

ভাগ হলো M4-এর চারটি অ্যারিথমেটিক অপারেশনের (যোগ, বিয়োগ, গুণ, ভাগ) মধ্যে সবচেয়ে ধীর ও জটিল — প্রতিটি বিট পজিশনে একটি সম্পূর্ণ বিয়োগ (এবং সম্ভবত রিস্টোর) লাগে, বিপরীতে Booth's Algorithm (L20) কখনো কখনো ধাপ এড়িয়ে যেতে পারত। আর division-by-zero-এর মতো অবৈধ ইনপুট আগেভাগেই স্পষ্টভাবে চেক করে প্রত্যাখ্যান করাটা নিছক ভালো অভ্যাস নয় — এটি বাস্তব হার্ডওয়্যারেও একটি বাধ্যতামূলক ফল্ট-ডিটেকশন সার্কিট, যা M7/L36-এর এক্সসেপশন হ্যান্ডলিং-এর সাথে সরাসরি সংযুক্ত।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ "রিস্টোর" ধাপে ঠিক কী "পুনরুদ্ধার" (restore) করা হয়, আর কেন সেটা এই অ্যালগরিদমের নামেই আছে?

যখন remainder থেকে divisor বিয়োগ করলে ফলাফল ঋণাত্মক হয়ে যায়, তার মানে divisor বর্তমান remainder-এর চেয়ে বড় — অর্থাৎ এই বিট পজিশনে ভাগ "যায় না" (quotient bit=০)। কিন্তু বিয়োগটি ইতিমধ্যে সঞ্চালিত হয়ে গেছে, তাই remainder-কে তার বিয়োগের-আগের মূল মানে ফিরিয়ে আনতে হয় — এটাই "রিস্টোর" (বিয়োগ বাতিল করে আগের অবস্থায় ফেরা)। ঠিক এই "বিয়োগ করো, প্রয়োজনে আবার ফিরিয়ে আনো" আচরণের কারণেই অ্যালগরিদমের নাম "Restoring Division"।

প্র ০২ কোড সেলে remainder-কে bits + 1 বিট প্রস্থে রাখা হয়েছে (dividend/quotient-এর bits-এর বদলে) — কেন একটি অতিরিক্ত বিট দরকার?

বিয়োগের ফলাফল ঋণাত্মক কিনা তা পরীক্ষা করতে হলে, একটি সাইন বিট থাকা দরকার যাতে সেই ফলাফলের সবচেয়ে বাম বিট পড়েই "ঋণাত্মক না অ-ঋণাত্মক" তা নির্ধারণ করা যায়। যদি remainder ঠিক bits-বিট প্রস্থেই রাখা হতো (কোনো বাড়তি সাইন বিট ছাড়া), তাহলে বিয়োগের ফলাফল overflow/wrap করে একটি ভুল বিট-প্যাটার্নে পরিণত হতে পারত, এবং সাইন সঠিকভাবে পড়া সম্ভব হতো না — একটি বাড়তি বিট এই সমস্যা সম্পূর্ণভাবে এড়িয়ে যায়।

প্র ০৩ উপরের কোডে restoring_division(13, 0, 4) কল করলে কেন ZeroDivisionError ওঠে, অ্যালগরিদম চালিয়েই দেখার বদলে?

divisor=০ হলে গাণিতিকভাবে ভাগ সংজ্ঞায়িত নয় — অ্যালগরিদম চালালে বিয়োগ ধাপগুলো কখনোই ঋণাত্মক ফলাফল দেবে না (remainder থেকে ০ বিয়োগ করলে remainder-ই থেকে যায়), ফলে quotient bit সবসময় ১ বসবে এবং একটি অর্থহীন, বিভ্রান্তিকর ফলাফল তৈরি হবে — কোনো ভুল সংকেত ছাড়াই। এই কারণে ফাংশনটি অ্যালগরিদম শুরু করার আগেই স্পষ্টভাবে divisor == 0 চেক করে এরর তোলে — বাস্তব CPU হার্ডওয়্যারেও ঠিক এই কারণেই ভাগ-শূন্য একটি হার্ডওয়্যার ফল্ট/এক্সসেপশন হিসেবে ধরা হয়, নীরবে ভুল ফলাফল দেওয়ার বদলে।

অনুশীলন

  1. অনুমান করুন, তারপর যাচাই করুন: $14 \div 3$-এর quotient ও remainder কত হবে বলে মনে করেন? কোড সেলে restoring_division(14, 3, 4) কল করে নিজে যাচাই করুন এবং পুনর্গঠন-চেক ($quotient\times3+remainder=14$) করুন।

    $14 = 4\times3+2$, তাই quotient$=4$, remainder$=2$ হওয়া উচিত। পুনর্গঠন-চেক: $4\times3+2=14$ — সঠিক। Python-এর 14 // 3 == 4 ও 14 % 3 == 2-এর সাথেও এটি মিলবে।

  2. চিন্তা করুন: যদি dividend, divisor-এর চেয়ে ছোট হয় (যেমন $2 \div 5$), তাহলে 4-বিট restoring division-এ quotient ও remainder কী হবে বলে মনে করেন — কেন?

    quotient$=0$, remainder$=2$ হবে (কারণ $2 = 0\times5+2$)। অ্যালগরিদমের প্রতিটি বিট-পজিশনেই বিয়োগের ফলাফল ঋণাত্মক হবে (যেহেতু divisor সবসময় remainder-এর চেয়ে বড় থাকবে), তাই প্রতিটি ধাপেই রিস্টোর ঘটবে ও প্রতিটি quotient bit ০ বসবে — চূড়ান্ত quotient $0000_2=0$, আর মূল dividend-টিই অপরিবর্তিত remainder হিসেবে থেকে যাবে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
বাইনারি মাল্টিপ্লিকেশন — Booth's Algorithm