ফ্লোটিং পয়েন্ট রিপ্রেজেন্টেশন — IEEE 754
এই পাঠে যা শিখবেন
- কেন ইন্টিজার রিপ্রেজেন্টেশন ভগ্নাংশ ও চরম মান প্রকাশে অপর্যাপ্ত, এবং ফ্লোটিং পয়েন্ট কীভাবে সমাধান দেয়
- IEEE 754 সিঙ্গেল-প্রিসিশন ফরম্যাটের ঠিক বিট-লেআউট — সাইন, এক্সপোনেন্ট (বায়াসসহ), ম্যান্টিসা (ইমপ্লিসিট লিডিং ১-সহ)
- দশমিক 5.75-কে হাতে-কলমে IEEE 754 বিট প্যাটার্নে রূপান্তর, ও কোড দিয়ে verification
- ফ্লোটিং পয়েন্টের নির্ভুলতার সীমাবদ্ধতা এবং এর বাস্তব প্রোগ্রামিং প্রভাব
১ · কেন ফ্লোটিং পয়েন্ট দরকার
L18-এ আমরা দেখেছি Two's Complementনেগেটিভ ইন্টিজার প্রকাশ করার আধুনিক স্ট্যান্ডার্ড পদ্ধতি — ইনভার্ট-অল-বিটস-প্লাস-ওয়ান। দিয়ে কীভাবে নেগেটিভ ইন্টিজার প্রকাশ করা যায় — কিন্তু ইন্টিজার (সাইনড হোক বা আনসাইনড) কখনোই ভগ্নাংশ (যেমন 5.75) প্রকাশ করতে পারে না, আর N বিট দিয়ে শুধুই একটি নির্দিষ্ট, সীমিত রেঞ্জের পূর্ণসংখ্যা প্রকাশ করা যায় — খুব বড় (যেমন ১০৩০) বা খুব ছোট (যেমন ১০-৩০) মান প্রকাশ করতে হলে অবাস্তব রকম বেশি বিট লাগবে।
সমাধানটি স্কুলে শেখা বৈজ্ঞানিক নোটেশন (scientific notation)-এর মতোই — যেমন ৫৭০০০০ কে 5.7 × 10⁵ লিখলে কম জায়গায় বড় মান প্রকাশ করা যায়। ফ্লোটিং পয়েন্ট ঠিক এই ধারণাটিই বাইনারিতে প্রয়োগ করে — একটি সংখ্যাকে sign × mantissa × 2^exponent আকারে ভেঙে প্রকাশ করে, যেখানে exponent পরিবর্তন করে একই সংখ্যক বিট দিয়েই অনেক বড় বা অনেক ছোট মান প্রকাশ করা সম্ভব হয়।
২ · IEEE 754 সিঙ্গেল-প্রিসিশন বিট-লেআউট (৩২-বিট)
IEEE 754IEEE 754ফ্লোটিং পয়েন্ট সংখ্যা কীভাবে বিটে এনকোড হবে তার আন্তর্জাতিক স্ট্যান্ডার্ড — প্রায় সব আধুনিক হার্ডওয়্যার ও প্রোগ্রামিং ভাষা এই স্ট্যান্ডার্ড অনুসরণ করে। হলো প্রায় সমস্ত আধুনিক হার্ডওয়্যারের ফ্লোটিং পয়েন্ট স্ট্যান্ডার্ড। এর ৩২-বিট "সিঙ্গেল-প্রিসিশন" ফরম্যাট ঠিক তিনটি ফিল্ডে ভাগ করা থাকে —
- সাইন বিট (১ বিট, বিট ৩১): 0 = পজিটিভ, 1 = নেগেটিভ।
- এক্সপোনেন্ট (৮ বিট, বিট ৩০-২৩): একটি বায়াসসংরক্ষিত এক্সপোনেন্ট মান থেকে একটি স্থির সংখ্যা (১২৭) বিয়োগ করলেই আসল (true) এক্সপোনেন্ট পাওয়া যায় — নেগেটিভ এক্সপোনেন্টও আনসাইনড বিট প্যাটার্নে প্রকাশ করার কৌশল। (bias) ১২৭ যোগ করে সংরক্ষিত হয় — অর্থাৎ সংরক্ষিত মান থেকে ১২৭ বিয়োগ করলেই আসল এক্সপোনেন্ট পাওয়া যায়।
- ম্যান্টিসা / ফ্র্যাকশন (২৩ বিট, বিট ২২-০): নরমালাইজড ফর্মে সবসময় একটি ইমপ্লিসিট (না-লেখা) লিডিং ১ ধরে নেওয়া হয় — অর্থাৎ ম্যান্টিসা আসলে 1.fraction (বাইনারিতে) প্রকাশ করে, যেখানে শুধু fraction অংশটাই এই ২৩ বিটে সংরক্ষিত হয়।
$$\text{value} = (-1)^{sign} \times (1.mantissa)_2 \times 2^{(exponent_{stored} - 127)}$$
এখানে mantissa অংশটি বাইনারি ভগ্নাংশ হিসেবে পড়তে হবে (প্রতিটি বিট পজিশন i-এর ভ্যালু 2^-(i+1)), আর
exponentstored হলো সেই ৮-বিট ফিল্ডে সরাসরি সংরক্ষিত (বায়াসসহ) মান।
৩ · হাতে-কলমে এনকোড করা — 5.75 উদাহরণ
চলুন 5.75-কে ধাপে ধাপে IEEE 754 বিট প্যাটার্নে রূপান্তর করি —
- বাইনারিতে রূপান্তর: 5 = 101₂, আর 0.75 = 0.5 + 0.25 = 0.11₂। তাই 5.75 = 101.11₂।
- নরমালাইজেশন: বাইনারি পয়েন্ট সরিয়ে ফর্ম 1.xxxx × 2^k বানাতে হবে — 101.11₂ = 1.0111₂ × 2². অর্থাৎ true exponent = 2।
- সাইন বিট: সংখ্যাটি পজিটিভ, তাই sign = 0।
- এক্সপোনেন্ট সংরক্ষণ (বায়াসসহ): stored exponent = true exponent + 127 = 2 + 127 = 129, যা বাইনারিতে 10000001।
- ম্যান্টিসা: নরমালাইজড ফর্মের ইমপ্লিসিট 1 বাদ দিয়ে বাকি অংশ 0111 — এটি ২৩ বিট পর্যন্ত ডানদিকে শূন্য দিয়ে প্যাড করলে হয় 01110000000000000000000।
সম্পূর্ণ ৩২-বিট প্যাটার্ন: 0 10000001 01110000000000000000000। নিচের কোড সেলে এই হাতে-কলমে করা
হিসাবটি Python-এ বাস্তবায়ন করে, এবং সবচেয়ে গুরুত্বপূর্ণভাবে — Python-এর নিজস্ব struct মডিউল যে
বিট প্যাটার্ন হার্ডওয়্যার-স্ট্যান্ডার্ড অনুযায়ী উৎপন্ন করে, তার সাথে মিলিয়ে যাচাই করা হবে।
import struct
def decode_ieee754(sign_bit, exponent_bits, mantissa_bits):
"""তিনটি বিট-ফিল্ড থেকে আসল দশমিক মান বের করে -- ঠিক উপরের সূত্র অনুযায়ী"""
exponent_stored = 0
for b in exponent_bits:
exponent_stored = (exponent_stored << 1) | b
mantissa_val = 0.0
for i, b in enumerate(mantissa_bits):
mantissa_val += b * (2 ** -(i + 1))
true_exponent = exponent_stored - 127
sign = (-1) ** sign_bit
return sign * (1.0 + mantissa_val) * (2 ** true_exponent)
def encode_ieee754(value):
"""একটি float-কে (sign_bit, exponent_bits[8], mantissa_bits[23]) এ ভাঙে"""
sign_bit = 1 if value < 0 else 0
v = abs(value)
if v == 0:
return 0, [0] * 8, [0] * 23
exponent = 0
while v >= 2.0:
v /= 2.0
exponent += 1
while v < 1.0:
v *= 2.0
exponent -= 1
frac = v - 1.0 # ইমপ্লিসিট লিডিং ১ বাদ দেওয়া হলো
mantissa_bits = []
for _ in range(23):
frac *= 2
bit = int(frac)
mantissa_bits.append(bit)
frac -= bit
stored_exponent = exponent + 127
exponent_bits = [(stored_exponent >> (7 - i)) & 1 for i in range(8)]
return sign_bit, exponent_bits, mantissa_bits
# --- ধাপ ১: হাতে-কলমে হিসাব করা 5.75-এর বিট প্যাটার্ন কোডে বসিয়ে যাচাই ---
hand_sign = 0
hand_exponent = [1, 0, 0, 0, 0, 0, 0, 1] # 129
hand_mantissa = [int(c) for c in "01110000000000000000000"]
print("হাতে-কলমে বিট প্যাটার্ন দিয়ে decode:", decode_ieee754(hand_sign, hand_exponent, hand_mantissa))
# --- ধাপ ২: এনকোডার দিয়ে 5.75 সরাসরি এনকোড করা ---
sign, exponent_bits, mantissa_bits = encode_ieee754(5.75)
print("encode_ieee754(5.75) sign :", sign)
print("encode_ieee754(5.75) exponent :", "".join(map(str, exponent_bits)), "=", int("".join(map(str, exponent_bits)), 2))
print("encode_ieee754(5.75) mantissa :", "".join(map(str, mantissa_bits)))
assert (sign, exponent_bits, mantissa_bits) == (hand_sign, hand_exponent, hand_mantissa)
print("এনকোডার আর হাতে-কলমে করা হিসাব হুবহু মিলেছে ✓")
# --- ধাপ ৩: encode -> decode রাউন্ড-ট্রিপ ---
print("encode -> decode রাউন্ড-ট্রিপ ফলাফল:", decode_ieee754(sign, exponent_bits, mantissa_bits))
# --- ধাপ ৪: Python-এর নিজস্ব struct মডিউল দিয়ে প্রকৃত হার্ডওয়্যার-স্ট্যান্ডার্ড বিট প্যাটার্নের সাথে যাচাই ---
packed = struct.pack('>f', 5.75)
raw_bits = struct.unpack('>I', packed)[0]
struct_pattern = format(raw_bits, '032b')
my_pattern = f"{sign}{''.join(map(str, exponent_bits))}{''.join(map(str, mantissa_bits))}"
print("আমাদের এনকোডারের প্যাটার্ন :", my_pattern)
print("struct.pack এর প্যাটার্ন :", struct_pattern)
print("দুটো হুবহু মিলেছে কিনা :", my_pattern == struct_pattern)
struct মডিউল (যা সরাসরি
প্রকৃত হার্ডওয়্যার-স্ট্যান্ডার্ড IEEE 754 বিট প্যাটার্ন উৎপন্ন করে) তার সাথেও হুবহু মিলেছে — এটাই এই ধরনের
বিট-লেভেল এনকোডিং যাচাই করার সবচেয়ে নির্ভরযোগ্য উপায়, কারণ হাতে-কলমে করা হিসাবে ভুল হওয়ার সম্ভাবনা বেশি।
৪ · নির্ভুলতার সীমাবদ্ধতা
একটি গুরুত্বপূর্ণ ও বাস্তব সমস্যা — IEEE 754 (বা আসলে যেকোনো সসীম-বিট বাইনারি ফ্লোটিং পয়েন্ট ফরম্যাট) প্রতিটি দশমিক ভগ্নাংশ নিখুঁতভাবে প্রকাশ করতে পারে না। যেমন 0.1 বাইনারিতে একটি পুনরাবৃত্ত (repeating) ভগ্নাংশ (0.000110011001100...₂, চিরকাল চলতে থাকে) — ঠিক যেমন দশমিকে 1/3 = 0.3333... চিরকাল চলতে থাকে। যেহেতু ম্যান্টিসায় মাত্র ২৩ বিট জায়গা আছে, এই পুনরাবৃত্তি কোথাও কেটে (truncate/round) ফেলতে হয় — ফলে সংরক্ষিত মান আসল 0.1 থেকে সামান্য ভিন্ন হয়ে যায়।
এটাই বাস্তব প্রোগ্রামিং-এ একটি সুপরিচিত সমস্যার মূল কারণ — 0.1 + 0.2 == 0.3 এক্সপ্রেশনটি অনেক
ভাষায় (Python-সহ) False রিটার্ন করে, কারণ 0.1, 0.2 এবং 0.3 — কোনোটিই বাইনারি ফ্লোটিং পয়েন্টে
নিখুঁতভাবে সংরক্ষিত হয় না, তাই যোগফলে সামান্য রাউন্ডিং ত্রুটি জমা হয়।
IEEE 754 একটি সুনির্দিষ্ট, স্ট্যান্ডার্ডাইজড বিট-লেআউট (sign + বায়াসড exponent + ইমপ্লিসিট-লিডিং-১ mantissa)
দিয়ে ভগ্নাংশ ও চরম মান প্রকাশ করে — কিন্তু এই প্রকাশ সসীম বিট ব্যবহার করে বলে প্রতিটি দশমিক ভগ্নাংশ নিখুঁতভাবে
ধরতে পারে না। বিট-লেভেল এনকোডিং করার সময় সবসময় struct-এর মতো নির্ভরযোগ্য টুল দিয়ে ক্রস-চেক করা
উচিত — হাতে-কলমে হিসাবে ভুল হওয়া সহজ।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ ম্যান্টিসায় "ইমপ্লিসিট লিডিং ১" ধরে নেওয়া হয় কেন — কেন সরাসরি পুরো মান ২৩ বিটেই লিখে ফেলা হয় না?
একটি সংখ্যাকে নরমালাইজড ফর্মে (1.xxxx × 2^k) লিখলে, বাইনারিতে লিডিং বিটটি (দশমিকের মতো নয়) সবসময়ই ১ হবে — কারণ বাইনারিতে একমাত্র নন-জিরো ডিজিট হলো ১, তাই নরমালাইজেশনের সংজ্ঞা অনুযায়ী লিডিং বিট বাধ্যতামূলকভাবে ১। যেহেতু এই বিটের মান সবসময় জানা, এটি আলাদাভাবে সংরক্ষণ করার দরকার নেই — ফলে ম্যান্টিসার পুরো ২৩ বিটই অতিরিক্ত প্রিসিশনের জন্য ব্যবহার করা যায়, একই সংখ্যক বিটে বেশি নির্ভুলতা পাওয়া যায়।
প্র ০২ এক্সপোনেন্টে বায়াস (১২৭) ব্যবহার করা হয় কেন — L18-এর মতো সরাসরি সাইনড (two's complement) এক্সপোনেন্ট ব্যবহার করলে সমস্যা কী হতো?
বায়াসড এক্সপোনেন্ট ব্যবহারের একটি বাস্তব সুবিধা হলো — এটি দুটি ফ্লোটিং পয়েন্ট সংখ্যার তুলনা সহজ করে। যদি এক্সপোনেন্ট বায়াসড (সবসময় নন-নেগেটিভ আনসাইনড সংখ্যা হিসেবে) সংরক্ষিত থাকে, তাহলে দুটি পজিটিভ ফ্লোটের পুরো ৩২-বিট প্যাটার্নকে সরাসরি সাধারণ আনসাইনড ইন্টিজার হিসেবে তুলনা করলেই সঠিক ফলাফল পাওয়া যায় (বড় বিট প্যাটার্ন মানেই বড় মান) — যদি এক্সপোনেন্ট সরাসরি two's complement হতো, এই সরল তুলনা আর কাজ করত না, কারণ two's complement-এ নেগেটিভ মানের বিট প্যাটার্ন সংখ্যাগতভাবে "বড়" দেখায় (leftmost bit 1)।
প্র ০৩ 0.1 বাইনারিতে নিখুঁতভাবে প্রকাশ করা যায় না কেন — অথচ 0.5 বা 0.25 ঠিকই নিখুঁতভাবে প্রকাশ হয়?
0.5 = 2⁻¹ আর 0.25 = 2⁻² — এগুলো ঠিক ২-এর ঋণাত্মক ঘাত (power of 2), তাই বাইনারিতে সসীম সংখ্যক বিটেই নিখুঁতভাবে প্রকাশ করা যায় (0.1₂ ও 0.01₂)। কিন্তু 0.1 = 1/10 — আর 10-এর সাথে 2-এর কোনো সরল ঘাত-সম্পর্ক নেই, ঠিক যেমন দশমিকে 1/3 নিখুঁতভাবে লেখা যায় না (0.333... চিরকাল চলে) কারণ 3, 10-এর কোনো ঘাত নয়। বাইনারিতে 1/10 এর বাইনারি প্রকাশও একইভাবে চিরকাল পুনরাবৃত্ত হতে থাকে (0.0001100110011...₂), যা ২৩-বিট ম্যান্টিসায় ধরার সময় বাধ্যতামূলকভাবে কোথাও কেটে ফেলতে হয়।
অনুশীলন
-
হাতে-কলমে করুন: −0.5 কে IEEE 754 সিঙ্গেল-প্রিসিশন ফরম্যাটে হাতে-কলমে এনকোড করুন (sign, stored exponent, mantissa — এই পাঠের 5.75 উদাহরণের ধাপ অনুসরণ করে), তারপর উপরের কোড সেলে
encode_ieee754(-0.5)চালিয়ে আপনার হাতের হিসাবের সাথে মেলান।−0.5 এর ম্যাগনিটিউড 0.5 = 0.1₂ = 1.0₂ × 2⁻¹, তাই sign = 1 (নেগেটিভ), true exponent = −1, stored exponent = −1 + 127 = 126 = 01111110₂, আর normalized mantissa-এর ইমপ্লিসিট ১ বাদ দিলে ফ্র্যাকশন অংশ 0 — তাই mantissa = 00000000000000000000000 (সব শূন্য)। সম্পূর্ণ প্যাটার্ন:
1 01111110 00000000000000000000000।encode_ieee754(-0.5)চালালে এটাই পাওয়ার কথা, এবংstruct.pack('>f', -0.5)-এর সাথে মিলিয়েও যাচাই করা যায়। -
চিন্তা করুন: Python-এ
0.1 + 0.2 == 0.3রান করলে ফলাফল কী আসবে বলে মনে হয়, এবং কেন? বাস্তব প্রোগ্রামে ফ্লোটিং-পয়েন্ট সংখ্যা তুলনা করার সময় এই সমস্যা এড়াতে কী কৌশল ব্যবহার করা যেতে পারে (এখনো কোড না চালিয়ে শুধু যুক্তি দিয়ে ভাবুন)?ফলাফল
Falseআসবে — কারণ 0.1 ও 0.2, দুটোই বাইনারি ফ্লোটিং পয়েন্টে নিখুঁতভাবে প্রকাশযোগ্য নয় (এই পাঠের ৪র্থ সেকশনে ব্যাখ্যা করা হয়েছে), তাই দুটোরই সংরক্ষিত মান আসল মান থেকে সামান্য ভিন্ন — এদের যোগফলও তাই ঠিক 0.3-এর সংরক্ষিত বিট প্যাটার্নের সমান হয় না। বাস্তব প্রোগ্রামে সরাসরি==দিয়ে তুলনা না করে, দুটো মানের পার্থক্য একটি ছোট থ্রেশহোল্ড (epsilon, যেমন 1e-9)-এর চেয়ে কম কিনা তা পরীক্ষা করা হয় — যেমনabs(a - b) < 1e-9।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ এটি M4 (কম্পিউটার আর্দমেটিক)-এর শেষ পাঠ — পরের মডিউল M5 থেকে ইনস্ট্রাকশন সেট আর্কিটেকচার শুরু হচ্ছে।
- L18 · সাইনড নাম্বার রিপ্রেজেন্টেশন — Two's Complement সম্পর্কিত পাঠ ফ্লোটিং পয়েন্টের সাইন বিট ও এক্সপোনেন্ট বায়াসের ধারণা সরাসরি এই পাঠের সাইনড-নাম্বার রিপ্রেজেন্টেশনের উপর দাঁড়িয়ে আছে।
- L23 · ISA কী — RISC বনাম CISC পরবর্তী পাঠ M4 শেষ হলো — এবার M5-এ দেখব সফটওয়্যার ও হার্ডওয়্যারের মধ্যকার চুক্তি, ইনস্ট্রাকশন সেট আর্কিটেকচার, কীভাবে সংজ্ঞায়িত হয়।