Bit Inspector CLI
Bit Inspector CLI
যেকোনো মান (integer বা float, decimal/hex/binary ইনপুট) নিয়ে তার raw bytes, পূর্ণ binary, two's complement আর IEEE 754 breakdown — সব একসাথে দেখানো একটা CLI টুল। একই bit pattern, ভিন্ন interpretation — এই মডিউলের মূল প্রশ্নটাই হাতে-কলমে দেখা।
কেন এই প্রজেক্ট
Signed Integers আর IEEE 754 লেসনে আমরা শিখেছি bit pattern কীভাবে সংখ্যায় রূপ নেয় — কিন্তু সেটা কাগজে-কলমে। এই প্রজেক্টে সেই একই নিয়মগুলো একটা প্রোগ্রামে বসাতে হবে, আর তখনই আসল প্রশ্নগুলো সামনে আসে: memory-তে byte-গুলো কোন ক্রমে থাকে (endianness)? overflow হলে হার্ডওয়্যার আসলে কী করে? একটা 32-bit pattern-কে int হিসেবে পড়লে এক জিনিস, float হিসেবে পড়লে সম্পূর্ণ অন্য জিনিস কেন বোঝায়?
এই টুলটা আসলে এই মডিউলের driving question-এরই উত্তর: “একটা bit pattern দেখে আমি কীভাবে জানব সেটা কী অর্থ বহন করছে?” — উত্তর হলো, জানা যায় না, শুধু ধরে নেওয়া যায়। এই প্রজেক্ট সেই ধরে-নেওয়াটাকে explicit করে দেখাবে।
লক্ষ্য
এই রকম কিছু চালাতে পারা:
$ python bitinspect.py 65
input : '65' → parsed as integer 65
bit width : 32
binary : 00000000 00000000 00000000 01000001
raw bytes (little-endian, native=little): 41 00 00 00
unsigned : 65
signed (2's complement): 65
--- একই 32-bit pattern-কে float32 হিসেবে reinterpret করলে ---
sign : 0 (ধনাত্মক)
exponent : 00000000 (raw=0, biased-127=-127)
mantissa : 00000000000000001000001 (raw=65)
stored value: 9.108245e-44 (একটা subnormal — প্রায় শূন্য, কিন্তু আক্ষরিক অর্থে শূন্য নয়)
$ python bitinspect.py 300 --bits 8
input : '300' → parsed as integer 300
bit width : 8
binary : 00101100
raw bytes (little-endian): 2c
unsigned : 44
signed (2's complement): 44
⚠ overflow : 300 8-bit-এ ধরে না, wrap হয়ে 44 হয়ে গেছে (হুবহু হার্ডওয়্যার wraparound)
$ python bitinspect.py 3.14
input : '3.14' → parsed as float 3.14
bit width : 32 (single-precision)
binary : 01000000 01001000 11110101 11000011
raw bytes (little-endian): c3 f5 48 40
sign : 0 (ধনাত্মক)
exponent : 10000000 (raw=128, biased-127=1)
mantissa : 10010001111010111000011 (raw=4781507)
stored value : 3.140000104904175
intended (double) : 3.14
precision error : ≈1.049e-07 (single precision-এ মাত্র ~৭টা দশমিক অঙ্ক নির্ভরযোগ্য)
ধাপে ধাপে
১. ইনপুট পার্স করা
ব্যবহারকারী decimal, hex, binary, বা float — যেকোনো ফরম্যাটে ইনপুট দিতে পারবে।
def parse_input(raw: str):
"""ইনপুট স্ট্রিং থেকে (value, is_float) বের করা।"""
s = raw.strip()
lower = s.lower()
if lower.startswith('0x') or lower.startswith('-0x'):
return int(s, 16), False
if lower.startswith('0b') or lower.startswith('-0b'):
return int(s, 2), False
if '.' in s or 'e' in lower or lower in ('inf', '-inf', 'nan'):
return float(s), True
return int(s), False
লক্ষ্য করুন — int(s, 16) আর int(s, 2) নিজেরাই 0x/0b prefix আর - চিহ্ন বুঝে নেয়, তাই আলাদা করে parse করার দরকার নেই।
২. Bit pattern আর two’s complement wraparound
Python-এর int arbitrary-precision — তাই hardware-এর মতো একটা নির্দিষ্ট bit-width-এ wraparound হওয়াটা নিজেই simulate করতে হবে।
def analyze_int(value: int, bits: int):
lo_signed = -(1 \<\< (bits - 1))
hi_unsigned = (1 \<\< bits) - 1
overflowed = value \< lo_signed or value > hi_unsigned
mask = (1 \<\< bits) - 1
pattern = value & mask # unsigned bit pattern, wraparound সহ
sign_bit = 1 \<\< (bits - 1)
signed_value = pattern - (1 \<\< bits) if pattern & sign_bit else pattern
return pattern, signed_value, overflowed
value & mask লাইনটাই আসল কাজ — এটা Python-কে বাধ্য করছে হার্ডওয়্যারের মতো আচরণ করতে। 300 & 0xFF = 44 ঠিক তেমনই wraparound যেমন একটা 8-bit CPU register-এ ঘটত। এটাই Signed Integers লেসনের overflow আলোচনার প্রত্যক্ষ প্রমাণ।
৩. Raw bytes আর endianness
import sys
def to_raw_bytes(pattern: int, bits: int, endian: str) -> bytes:
return pattern.to_bytes(bits // 8, byteorder=endian)
def format_binary(pattern: int, bits: int) -> str:
raw_bits = format(pattern, f'0{bits}b')
groups = [raw_bits[i:i + 8] for i in range(0, bits, 8)]
return ' '.join(groups)
binary আউটপুট সবসময় most-significant-bit আগে দেখায় (সংখ্যা যেভাবে লেখা হয়, ঠিক সেভাবে) — কিন্তু raw bytes মেমরিতে যেভাবে সাজানো থাকে সেভাবে, যেটা endianness-এর উপর নির্ভর করে। এই দুইটার ক্রম যে আলাদা হতে পারে — সেটাই এন্ডিয়ানেস লেসনের মূল কথা। --endian flag দিয়ে little আর big দুইভাবেই দেখান, এবং sys.byteorder দিয়ে মেশিনের নিজের native order জানিয়ে দিন।
৪. IEEE 754 breakdown
import struct
def decode_ieee754_32(bits_pattern: int):
sign = (bits_pattern >> 31) & 1
exponent = (bits_pattern >> 23) & 0xFF
mantissa = bits_pattern & 0x7FFFFF
return sign, exponent, mantissa
def reconstruct_value(sign: int, exponent: int, mantissa: int) -> float:
if exponent == 0 and mantissa == 0:
return -0.0 if sign else 0.0
if exponent == 0: # subnormal
frac = mantissa / (1 \<\< 23)
return ((-1) ** sign) * frac * (2.0 ** -126)
if exponent == 0xFF:
if mantissa == 0:
return float('-inf') if sign else float('inf')
return float('nan')
frac = 1 + mantissa / (1 \<\< 23) # normal — implicit leading 1
return ((-1) ** sign) * frac * (2.0 ** (exponent - 127))
এখন হার্ডওয়্যারের decode-এর সাথে নিজের ম্যানুয়াল reconstruction মিলিয়ে দেখা যায়, struct মডিউল ব্যবহার করে:
def print_float_breakdown(bits_pattern: int, original: float = None):
sign, exponent, mantissa = decode_ieee754_32(bits_pattern)
stored = struct.unpack('\<f', bits_pattern.to_bytes(4, 'little'))[0]
reconstructed = reconstruct_value(sign, exponent, mantissa)
print(f"sign : {sign} ({'ঋণাত্মক' if sign else 'ধনাত্মক'})")
print(f"exponent : {exponent:08b} (raw={exponent}, biased-127={exponent - 127})")
print(f"mantissa : {mantissa:023b} (raw={mantissa})")
print(f"stored value: {stored!r}")
print(f"manual reconstruct == hardware decode: {reconstructed == stored}")
if original is not None:
error = stored - original
print(f"intended (double) value: {original!r}")
print(f"precision error (stored - intended): {error!r}")
reconstructed == stored লাইনটা আসলে একটা assertion-এর কাজ করছে — যদি এটা False দেখায়, বুঝবেন reconstruct_value-এ ভুল আছে (একটা ব্যতিক্রম: NaN কখনো নিজের সমান হয় না, তাই NaN ইনপুটে এটা False দেখাবে — সেটাও IEEE 754-এর নিজের একটা বিখ্যাত নিয়ম)।
stored - original লাইনটাই আসল শিক্ষা — original হলো Python-এর double precision-এ পড়া মান (3.14), আর stored হলো সেটাকে single precision-এ round করার পর যা টেকে। এই দুইয়ের ফারাকই single precision-এর precision loss।
৫. সব জোড়া লাগিয়ে CLI বানানো
import argparse
def analyze(raw_input: str, bits: int, endian: str):
value, is_float_input = parse_input(raw_input)
kind = 'float' if is_float_input else 'integer'
print(f"input : {raw_input!r} → parsed as {kind} {value}")
if not is_float_input:
pattern, signed_value, overflowed = analyze_int(value, bits)
raw = to_raw_bytes(pattern, bits, endian)
print(f"bit width : {bits}")
print(f"binary : {format_binary(pattern, bits)}")
print(f"raw bytes ({endian}-endian, native={sys.byteorder}): {raw.hex(' ')}")
print(f"unsigned : {pattern}")
print(f"signed (2's complement): {signed_value}")
if overflowed:
print(f"⚠ overflow : {value} {bits}-bit-এ ধরে না, wrap হয়ে {signed_value} হয়ে গেছে")
if bits >= 32:
print()
print("--- একই 32-bit pattern-কে float32 হিসেবে reinterpret করলে ---")
print_float_breakdown(pattern & 0xFFFFFFFF)
else:
raw = struct.pack('\<f', value)
if endian == 'big':
raw = raw[::-1]
float_bits = int.from_bytes(struct.pack('\<f', value), 'little')
print(f"bit width : 32 (single-precision)")
print(f"binary : {format_binary(float_bits, 32)}")
print(f"raw bytes ({endian}-endian): {raw.hex(' ')}")
print_float_breakdown(float_bits, original=value)
def main():
parser = argparse.ArgumentParser(description='যেকোনো মানের bit-level breakdown')
parser.add_argument('value', help='decimal / hex (0x..) / binary (0b..) / float')
parser.add_argument('--bits', type=int, choices=[8, 16, 32, 64], default=32)
parser.add_argument('--endian', choices=['little', 'big'], default=sys.byteorder)
args = parser.parse_args()
analyze(args.value, args.bits, args.endian)
if __name__ == '__main__':
main()
নিজেকে চ্যালেঞ্জ করুন
- 64-bit double সাপোর্ট যোগ করুন — sign(1)/exponent(11)/mantissa(52) নিয়ে একই breakdown
- ASCII/Unicode reinterpretation — একই raw bytes-কে UTF-8 text হিসেবে পড়ার চেষ্টা করে দেখান (invalid হলে বলুন কেন)
--compareflag — দুইটা মান দিয়ে তাদের raw bytes-এর মধ্যে ঠিক কয়টা bit আলাদা (Hamming distance) সেটা highlight করুন- NaN payload inspection — NaN-এর mantissa bit-এ payload/signaling-bit encode থাকে, সেটাও দেখান
- Memory dump mode — একগুচ্ছ মান পাশাপাশি রেখে আসল
hexdump-এর মতো output বানান
এটা যেখানে গিয়ে মিশবে
| এখানে যা শিখলেন | পরে কোথায় লাগবে |
|---|---|
| Two’s complement wraparound simulate করা | Level 2 — ALU-তে ওভারফ্লো ফ্ল্যাগ, adder সার্কিট |
| Endianness-এর raw byte order | Level 3 — CPU architecture, instruction encoding, memory dump পড়া |
| IEEE 754 sign/exponent/mantissa breakdown | Level 2 — FPU/floating-point unit ডিজাইন |
| একই bit pattern-এর ভিন্ন interpretation | Level 5 — Compiler-এর constant literal encoding, type punning |
| Raw bit manipulation, memory inspection | Level 4 — Debugger, core dump, /proc/pid/mem পড়া |
| Bit-pattern-নির্ভর bug/exploit বোঝা | Level 10 — Type confusion, buffer overflow-এর মূল কারণ |