Binary, Octal, Hex — ইঞ্জিনিয়ারের ভাষায়
Positional Number Systems in Engineering
Hex আর বাইনারির মধ্যে ৪ bit = ১ digit-এর নিখুঁত সম্পর্কই কেন হেক্স ইঞ্জিনিয়ারের ভাষা, আর হেক্স ডাম্প পড়া কেন প্রথম হাতিয়ার — অক্টালের গল্প ও নিজে একটা হেক্স ডাম্প টুল বানানো সহ।
আগে এটা বুঝি
আগের লেসনে আমরা দেখেছি একটা byte 01000001 চারভাবে পড়া যায়।
কিন্তু লক্ষ্য করুন — আমরা সেই আটটা 0/1 লিখেছিলাম। বাস্তবে
কেউ ৩২-bit বা ৬৪-bit সংখ্যা এভাবে লেখে না। gdb-তে একটা pointer
দেখুন:
$rip = 0x0000555555555169কেউ এটাকে 0000000000000000010101010101010101010101010101010101000101101001
লিখে দেখায় না — চোখ সেটা পড়তেই পারবে না। বদলে দেখায় 0x...5169।
Level 0-এর number-systems লেসনে আমরা প্রমাণ করেছি হেক্স কেন
বাইনারির নিখুঁত shorthand — 16 = 2⁴, তাই এক হেক্স digit ঠিক
চার bit। এই লেসনটা সেই গণিতটাকে হাতিয়ারে পরিণত করবে।
আজকের প্রশ্ন গাণিতিক নয়, প্রকৌশলগত: bit pattern পড়া, লেখা, আর রূপান্তর করার জন্য বাস্তব ইঞ্জিনিয়াররা ঠিক কোন টুল আর কনভেনশন ব্যবহার করে, কেন, আর কীভাবে একটা compiler-এর ভেতরের lexer এই সংখ্যাগুলোকে আসলে পড়ে? শেষে আমরা একটা আসল ফাইলের raw byte সরাসরি চোখে দেখব — এই module-এর প্রথম প্রকৃত hex dump experiment।
মূল ধারণা
যা আগে থেকেই জানেন — এক লাইনে
Level 0-এর number-systems লেসন থেকে দুইটা তথ্য ধার করি, প্রমাণ ছাড়া (প্রমাণ ওখানেই আছে):
- Base
b-তেdₙ…d₁d₀মানে , যেখানে । - Base যত বড়, ততই কম digit লাগে — কিন্তু সেই লাভ লগারিদমিক, রৈখিক নয়।
এই লেসন এই গণিতটা আবার প্রমাণ করবে না। বরং প্রশ্ন করবে: কম্পিউটিং-এ ঠিক কোন base গুলো আসলে ব্যবহৃত হয়, কেন, আর মানুষ প্রতিদিন সেগুলোর সাথে কীভাবে কাজ করে।
Hex কেন — bit-group যুক্তিটা সম্পূর্ণ করা
। তাই ঠিক চারটা bit-এর প্রতিটা সম্ভাব্য প্যাটার্নের
(0000 থেকে 1111, মোট ১৬টা) সাথে একটা হেক্স digit-এর নিখুঁত
এক-এক (bijective) সম্পর্ক আছে।
| বাইনারি | হেক্স | দশমিক | বাইনারি | হেক্স | দশমিক |
|---|---|---|---|---|---|
0000 | 0 | 0 | 1000 | 8 | 8 |
0001 | 1 | 1 | 1001 | 9 | 9 |
0010 | 2 | 2 | 1010 | a | 10 |
0011 | 3 | 3 | 1011 | b | 11 |
0100 | 4 | 4 | 1100 | c | 12 |
0101 | 5 | 5 | 1101 | d | 13 |
0110 | 6 | 6 | 1110 | e | 14 |
0111 | 7 | 7 | 1111 | f | 15 |
এই টেবিলটা মুখস্থ করে ফেলা উচিত — এই লেসনের সবচেয়ে ব্যবহারিক একক তথ্য। এটা জানলে যেকোনো binary ↔ hex রূপান্তর কোনো গণনা ছাড়াই, শুধু টুকরো টুকরো করে অনুবাদ করে করা যায়:
1010 1111 0011 0001
a f 3 1 → 0xaf31কোনো ভাগ নেই, কোনো carry নেই, কোনো গুণ করা লাগে না।
এটাই hex আর decimal-এর মধ্যে মৌলিক পার্থক্য — 16, 2-র
একটা ঘাত, কিন্তু 10 নয়। এই একটা গাণিতিক দুর্ঘটনাই (base 2-এর
power হওয়া) hex-কে ইঞ্জিনিয়ারিং-এ অপরিহার্য করেছে।
Byte-এর সাথে নিখুঁত মিল: একটা byte ৮ bit, আর ৮ = ৪ + ৪। তাই
প্রতিটা byte ঠিক দুইটা হেক্স digit — কোনো ব্যতিক্রম নেই,
0x00 থেকে 0xff।
byte বাইনারি: 0000 0000 থেকে 1111 1111
byte হেক্স: 00 থেকে ff ← সবসময় ২ digit
byte decimal: 0 থেকে 255 ← ১, ২, বা ৩ digit — অনিয়মিত
'41' hex → নিশ্চিতভাবে একটা byte-এর মধ্যে
'299' dec → কখনোই একটা byte না (255 এর বেশি) — কিন্তু দেখতে
'99' বা '29'-এর মতোই সাধারণ, চোখে ধরা পড়ে নাএই “নিয়মিততা” প্রায়োগিকভাবে বিশাল — একটা 4-byte value সবসময়
ঠিক ৮ হেক্স digit, একটা 8-byte value ঠিক ১৬ digit। Decimal-এ
এই predictability নেই, তাই চোখে বাউন্ডারি ধরা কঠিন।
Octal তবু বেঁচে আছে — Unix permission
, তাই একটা অক্টাল digit ঠিক তিন bit। এই ধর্মটা এখনো এক জায়গায় নিখুঁত ফিট করে: Unix file permission, যেখানে প্রতিটা permission group (owner, group, other) ঠিক তিনটা bit — read, write, execute:
rwx r-x r-x
111 101 101 ← প্রতিটা group = 3 bit = 1 অক্টাল digit
7 5 5 → chmod 755chmod 755 deploy.sh # owner: rwx, group: r-x, other: r-x
chmod 644 config.yaml # owner: rw-, group: r--, other: r--
chmod 700 secret_key # owner: rwx, group/other: কিছুই নাএখানে অক্টাল hex-এর চেয়ে বেশি স্বাভাবিক — কারণ hex-এ একটা digit ৪ bit বোঝায়, কিন্তু permission group ৩ bit-এর, তাই একটা hex digit-এ দুইটা group এলোমেলোভাবে মিশে যেত। ৩-bit গঠনের সাথে ৩-bit base-ই মেলে — এই একই নীতি (data-এর গঠনের সাথে base মিলিয়ে নেওয়া) নিচে আমরা আরও উদাহরণে দেখব।
ভাষাভেদে literal syntax — ইতিহাসের দাগ
কীভাবে একটা ভাষায় 0x, 0b, 0o prefix লেখেন — এটা নিছক
syntax trivia নয়, এটা “leading zero ফাঁদ” থেকে শেখা প্রতিটা
ভাষার নিজস্ব সিদ্ধান্তের ইতিহাস।
| ভাষা | Hex | Binary | Octal | Leading 0 অর্থ |
|---|---|---|---|---|
| C / C++ | 0x1F | 0b1111 (C++14+, GNU ext) | 017 | অক্টাল (ঐতিহাসিক ফাঁদ) |
| Python 3 | 0x1F | 0b1111 | 0o17 | SyntaxError — explicit 0o বাধ্যতামূলক |
| JavaScript (ES6+) | 0x1F | 0b1111 | 0o17 | strict mode-এ error, sloppy mode-এ legacy octal |
| Java | 0x1F | 0b1111 | 017 | অক্টাল (C-র মতোই ফাঁদ আছে) |
| Rust | 0x1F | 0b1111 | 0o17 | কোনো ambiguity নেই — prefix-বিহীন leading zero শুধুই 0 |
| Go | 0x1F | 0b1111 | 0o17 (আর legacy 017) | দুটোই সমর্থিত, 0o recommended |
প্যাটার্নটা লক্ষ্য করুন: পুরনো ভাষা (C, Java) legacy
“bare leading zero = octal” রেখে দিয়েছে backward compatibility-র
জন্য। নতুন ভাষা (Python 3, Rust, Go) ইচ্ছাকৃতভাবে এই ambiguity
মুছে দিয়েছে — octal লিখতে চাইলে explicit 0o prefix লাগবেই।
এটা language design-এর একটা সাধারণ প্যাটার্ন: পুরনো ভাষার একটা
পরিচিত bug-class থেকে শেখা, নতুন ভাষায় সেটা syntactically অসম্ভব
করে দেওয়া। Level 5-এ programming language design-এ এই ধরনের
সিদ্ধান্ত আরও দেখব।
Color code — hex-এর সবচেয়ে পরিচিত দৈনন্দিন ব্যবহার
CSS-এ #ff6b35 লিখলে এটা ঠিক তিনটা byte — লাল, সবুজ, নীল, প্রতিটা
0-255 (0x00-0xff):
#ff6b35
ff 6b 35
| | |
R G B
255 107 53এখানেও একই যুক্তি — RGB-র প্রতিটা channel ঠিক এক byte, আর hex-এ
সেটা ঠিক দুই digit। Decimal-এ লিখলে rgb(255, 107, 53) — বৈধ,
কিন্তু তিনটা সংখ্যার প্রতিটার digit-সংখ্যা আলাদা হতে পারে
(255 তিন digit, 53 দুই digit), তাই চোখে byte boundary স্পষ্ট
থাকে না। এই একই কারণেই আগে দেখা 41 বনাম 299 উদাহরণ এখানে
আবার প্রযোজ্য।
Memory address পড়া
একটা gdb session-এ বা crash log-এ ঠিকানা এভাবে দেখা যায়:
0x00007ffee3a2b1c8লক্ষ্য করুন এখানে ১৬টা হেক্স digit — অর্থাৎ
bit-এর জায়গা, যদিও x86-64-এ বাস্তবে ব্যবহৃত address space তার
চেয়ে ছোট (সাধারণত ৪৮ bit, তাই উপরের কয়েকটা digit প্রায়ই 0
থাকে)। এই ১৬-digit প্যাটার্নটা কোনো কাকতালীয় নয় — এটা সরাসরি
CPU-র word size-এর ফলাফল, যা আমরা পরের লেসনে বিস্তারিত দেখব।
এখন শুধু লক্ষ্য করুন: address লেখার জন্যও hex-ই স্বাভাবিক পছন্দ,
ঠিক সেই একই bit-group যুক্তিতে।
ভেতরে কী ঘটছে
রূপান্তরটা আসলে কীভাবে ঘটে — মানুষের কাগজে, আর মেশিনের ভেতরে
হাতে রূপান্তর — দুইটা দিক
Binary → Hex (বা উল্টো): সরাসরি group করুন, ডান দিক থেকে ৪টা করে (প্রয়োজনে বাম দিকে শূন্য বসিয়ে ৪-এর গুণিতক করে নিন)। কোনো গণনা লাগে না — উপরের ১৬-এন্ট্রি টেবিলটাই যথেষ্ট।
Binary → Octal (বা উল্টো): একই কৌশল, কিন্তু ৩টা করে group।
Hex/Octal → Decimal, বা Decimal → Hex/Octal: এখানে shortcut নেই — Horner’s method (গুণ ও যোগ) বা repeated division লাগবে, যেমন Level 0-এ দেখানো হয়েছে। এটাই মূল কারণ কেন প্রোগ্রামাররা মাথায় hex ↔ binary সহজে করেন কিন্তু hex ↔ decimal-এ calculator খোঁজেন — একটা রূপান্তর pattern-matching, আরেকটা arithmetic।
বাইনারি: 1101 1110 1010 1101 1011 1110 1110 1111
(৩২ bit)
৪-করে group (hex):
1101 1110 1010 1101 1011 1110 1110 1111
d e a d b e e f
→ 0xdeadbeef (একটা পরিচিত debug marker — বাস্তব জগতে দেখা যায়!)
৩-করে group (octal, ডান থেকে, প্রয়োজনে বামে শূন্য যোগ করে):
011 011 110 101 010 110 110 111 101 110 111 101 111
3 3 6 5 2 6 6 7 5 6 7 5 7
→ 0o336526675670xdeadbeef কাকতালীয় নয় — এটা programmer-দের মধ্যে একটা
জনপ্রিয় “magic debug value”, ইচ্ছাকৃতভাবে বেছে নেওয়া কারণ এটা
হেক্সে ইংরেজি শব্দের মতো পড়া যায় (0xcafebabe, 0xdeadc0de
এরকম আরও আছে) — memory dump-এ চোখে সহজে চেনা যায় বলে debugging-এর
সময় ইচ্ছাকৃতভাবে ব্যবহার হয়।
printf("%x", ...) ভেতরে কী করে
Modulo আর division দিয়েই — কিন্তু base যেহেতু -এর ঘাত, CPU
আসলে ধীর div/mod instruction ব্যবহার করে না। Level 0-এর
number-systems লেসনে আমরা প্রমাণ করেছি:
Hex-এ প্রতি digit ৪ bit, তাই একটা সংখ্যাকে hex-এ রূপান্তর করা
মানে বারবার নিচের ৪ bit mask করে নেওয়া (x & 0xF), তারপর
৪ bit ডানে shift করা (x >>= 4) — কোনো div instruction লাগে
না, শুধু and আর shr, প্রতিটা ১ cycle। এটাই সেই একই
bitmask trick যা আমরা আগে ring buffer আর hash table-এ দেখেছি,
এবার ব্যবহৃত হচ্ছে number-to-string conversion-এ:
char buf[9];
buf[8] = '\0';
for (int i = 7; i >= 0; i--) {
buf[i] = "0123456789abcdef"[x & 0xF];
x >>= 4;
}Decimal-এ (printf("%d", ...)) এই শর্টকাট নেই — 10 কোনো
2-এর ঘাত নয়, তাই আসল div/mod (বা সংখ্যা-নির্দিষ্ট
reciprocal trick, number-systems লেসনে যা দেখেছি) লাগে। এটাও
hex-এর একটা সুবিধা, শুধু মানুষের চোখের জন্য নয় — মেশিনের জন্যও
সস্তা।
Lexer কীভাবে 0x1F চেনে
একটা compiler বা interpreter যখন সোর্স কোড পড়ে, প্রথম ধাপ হলো lexing (Level 5-এ compiler module-এ বিস্তারিত) — অক্ষরের স্ট্রিমকে token-এ ভাঙা। একটা সংখ্যা literal পড়ার state machine মোটামুটি এরকম:
- প্রথম character '0' দেখা গেলসম্ভাব্য prefix আসছে কি না দেখতে পরের character লাগবে
- পরের character 'x' বা 'X'HEX mode — এরপর শুধু 0-9, a-f, A-F গ্রহণযোগ্য
- পরের character 'b' বা 'B'BINARY mode — এরপর শুধু 0, 1 গ্রহণযোগ্য
- পরের character 'o' বা 'O'OCTAL mode — এরপর শুধু 0-7 গ্রহণযোগ্য
- পরের character একটা digit (0-9)ভাষাভেদে: C/Java → OCTAL mode; Python/Rust → SyntaxError (explicit prefix ছাড়া নিষিদ্ধ)
- পরের character দশমিক বিন্দু বা কিছুই নাশুধু '0' → DECIMAL, মান 0
এটাই একটা classic deterministic finite automaton (DFA) —
Level 13-এ automata theory-তে আমরা এই ধরনের state machine
আনুষ্ঠানিকভাবে সংজ্ঞায়িত করব। এখানে গুরুত্বপূর্ণ পয়েন্ট হলো:
0x/0b/0o prefix আলাদা করার জন্য lexer-এর মাত্র এক
character lookahead লাগে — এটা efficient-ভাবে parse করা যায়
বলেই প্রতিটা আধুনিক ভাষা এই convention বেছেছে।
xxd/hexdump ভেতরে কী করছে
এই টুলগুলো conceptually খুবই সহজ — একটা ফাইল byte-by-byte পড়ে, আর প্রতিটা byte-কে তিনভাবে দেখায়:
- Offset (নিজেও hex-এ, কারণ address!) — ফাইলের শুরু থেকে কত byte দূরে
- Hex pair — সেই byte-এর মান,
00-ff - ASCII rendering — যদি byte-টা printable ASCII হয়
(
0x20-0x7e), সেই অক্ষর দেখানো হয়; নাহলে একটা placeholder (.)
উদাহরণ
পূর্ণ conversion drill — হাতে করে দেখুন
দশমিক 3405691582 কে হেক্সে রূপান্তর — repeated division:
3405691582 ÷ 16 = 212855723, ভাগশেষ 14 (e)
212855723 ÷ 16 = 13303482, ভাগশেষ 11 (b)
13303482 ÷ 16 = 831467, ভাগশেষ 10 (a)
831467 ÷ 16 = 51966, ভাগশেষ 11 (b)
51966 ÷ 16 = 3247, ভাগশেষ 14 (e)
3247 ÷ 16 = 202, ভাগশেষ 15 (f)
202 ÷ 16 = 12, ভাগশেষ 10 (a)
12 ÷ 16 = 0, ভাগশেষ 12 (c)
উল্টো দিকে পড়ুন → c a f e b a b eআরেকটা পরিচিত magic value — Java class file-এর magic number
হিসেবে ব্যবহৃত হয় (CA FE BA BE)!
হেক্স থেকে বাইনারি (group ব্যবহার করে, কোনো গণনা ছাড়া):
0xcafebabe
c a f e b a b e
1100 1010 1111 1110 1011 1010 1011 1110বাইনারি থেকে অক্টাল (৩-করে group, বাম দিকে শূন্য বসিয়ে ৩৩-কে ৩৩ থেকে ৩৪-এ নিয়ে align করে — ৩২ bit ৩ দিয়ে বিভাজ্য নয় বলে এখানেই আমাদের earlier দাবিটা হাতেকলমে দেখা যাচ্ছে):
৩২ bit, কিন্তু ৩২/৩ পূর্ণসংখ্যা নয় — তাই বামে ১টা শূন্য বসিয়ে ৩৩ bit বানাতে হলো
0 1100 1010 1111 1110 1011 1010 1011 1110 ← মূল, বাঁয়ে extra 0
৩-করে ভাগ (ডান থেকে শুরু):
011 001 010 111 111 010 111 010 101 111 10
hmm — লক্ষ্য করুন গ্রুপিং বিশ্রী, শেষ group অসম্পূর্ণ থাকেএটাই ঠিক আগের callout-এর দাবির প্রমাণ — ৩২, ৩ দিয়ে বিভাজ্য না হওয়ায় অক্টাল গ্রুপিং কখনোই পরিষ্কার হয় না ৩২-bit শব্দে, যেখানে hex গ্রুপিং সবসময় নিখুঁত (৩২/৪ = ৮, সবসময়)। এই একটা হাতে-কলমে করা উদাহরণই দেখায় কেন আধুনিক ৮/১৬/৩২/৬৪-bit জগতে অক্টাল হারিয়ে গেছে।
IP address-এর অক্টেট hex-এ
192.168.1.100-কে হেক্সে লিখলে:
| Decimal octet | Hex |
|---|---|
| 192 | c0 |
| 168 | a8 |
| 1 | 01 |
| 100 | 64 |
ip route, /proc/net/route (Linux kernel-এর network table)-এর
মতো জায়গায় ঠিক এই hex ফর্ম দেখা যায় — কারণ kernel-এর কাছে একটা
IPv4 address আসলে একটা মাত্র ৩২-bit unsigned integer, “চারটা
আলাদা সংখ্যা” শুধু মানুষের পড়ার সুবিধার জন্য (dotted-decimal
notation, নিজেও একটা convention, RFC 790-এ প্রথম সংজ্ঞায়িত)।
Level 7-এ networking module-এ এটা বিস্তারিত ফিরে আসবে।
নিজে চালিয়ে দেখুন
Base conversion যাচাই — Python-এ built-in ব্যবহার করে
n = 3405691582
print(f"decimal : {n}")
print(f"hex : {hex(n)}") # 0xcafebabe
print(f"oct : {oct(n)}") # 0o31277675276
print(f"bin : {bin(n)}") # 0b11001010111111101011101010111110
# উল্টো দিকে — string থেকে int, base স্পষ্ট করে দিয়ে
assert int("cafebabe", 16) == n
assert int("0xcafebabe", 0) == n # base=0 মানে: prefix দেখেই বুঝে নাও
# হাতে hex conversion — shift আর mask দিয়ে (hood section-এর দাবিটা যাচাই)
def to_hex_manual(x: int) -> str:
if x == 0:
return "0"
digits = "0123456789abcdef"
out = []
while x > 0:
out.append(digits[x & 0xF]) # নিচের ৪ bit mask
x >>= 4 # ৪ bit ডানে shift
return "".join(reversed(out))
assert to_hex_manual(3405691582) == "cafebabe"
print("manual hex matches built-in:", to_hex_manual(3405691582))আউটপুট:
decimal : 3405691582
hex : 0xcafebabe
oct : 0o31277675276
bin : 0b11001010111111101011101010111110
manual hex matches built-in: cafebabeto_hex_manual-এ কোনো // বা % নেই — শুধু & আর >>, ঠিক
যেমন hood section-এ দাবি করা হয়েছিল। C-তে কম্পাইল করে
objdump -d-তে দেখলে (Level 3-এ শিখব) আপনি সেখানেও and আর
shr instruction-ই পাবেন, কোনো div নয়।
Python-এর int()/format()/bin()/oct()/hex() built-in ফাংশনগুলো হাতের হিসাবের সাথে হুবহু মেলে — আর negative shift trick দিয়ে base-2ᵏ conversion সত্যিই bitwise operation দিয়ে করা যায়।
আসল ফাইলের hex dump — xxd আর hexdump -C
প্রথমে একটা ছোট ফাইল বানাই — কিছু text আর কিছু raw byte মিশিয়ে, যাতে printable আর non-printable দুই ধরনের byte দেখা যায়:
printf 'Hi\n\x00\x01\x02\xff' > sample.bin
wc -c sample.bin # 7xxd দিয়ে দেখি:
xxd sample.bin00000000: 4869 0a00 0102 ff Hi.....পড়ার নিয়ম:
00000000:— offset, hex-এ (ফাইলের শুরু থেকে ০ byte দূরে)4869 0a00 0102 ff— সাতটা byte, দুই-দুই করে group, শেষটা একা (কারণ ৭ বিজোড়)Hi.....— ASCII column:H(0x48),i(0x69) printable তাই দেখানো হলো;\n(0x0a),\x00,\x01,\x02,\xffnon-printable তাই প্রতিটার জায়গায়.
hexdump -C দিয়ে একই ফাইল দেখি:
hexdump -C sample.bin00000000 48 69 0a 00 01 02 ff |Hi.....|Layout একটু ভিন্ন (প্রতিটা byte আলাদা করে দেখানো, ASCII অংশ
|...| দিয়ে ঘেরা) কিন্তু তথ্য হুবহু একই — সাতটা byte, একই
মান, একই ক্রমে।
এখন একটা real ফাইলের magic number দেখি:
python3 -c "
from PIL import Image
Image.new('RGB', (1,1)).save('tiny.png')
" 2>/dev/null || printf '\x89PNG\r\n\x1a\n' > tiny.png
xxd tiny.png | head -100000000: 8950 4e47 0d0a 1a0a .... .PNG....89 50 4e 47 0d 0a 1a 0a — এই আট byte প্রতিটা বৈধ PNG ফাইলের
প্রথম আট byte, ব্যতিক্রমহীন। 50 4e 47 অংশটা ASCII-তে PNG —
ইচ্ছাকৃতভাবে মানুষের চোখে চেনার জন্য রাখা, বাকি byte গুলো
(89, 0d 0a 1a 0a) ইচ্ছাকৃতভাবে অ-প্রিন্টযোগ্য, যাতে একটা
text-mode ফাইল ট্রান্সফার (যা line-ending বদলে দিতে পারে) সহজেই
ধরা পড়ে — 0d 0a (CRLF) আর 0a (LF) দুটোই আছে বলে যদি কোনো
টুল ভুলভাবে line ending রূপান্তর করে, magic number ভেঙে যাবে
আর reader সাথে সাথে বুঝবে ফাইলটা corrupted। আগের লেসনের থিসিস
আবার — এই আট byte-এর “অর্থ” (magic number) সম্পূর্ণ convention,
আর সেই convention সচেতনভাবে ডিজাইন করা হয়েছিল।
xxd আর hexdump -C দুইটা ভিন্ন layout-এ একই raw byte দেখায় — offset, hex bytes, আর ASCII rendering তিনটাই একসাথে পড়তে শিখলে যেকোনো ফাইলের ভেতরের গঠন সরাসরি চোখে দেখা যায়, কোনো বিশেষ tool ছাড়াই।
নিজে বানান
নিজের মিনি hex dump টুল
- একটা ফাইল ১৬ byte-এর chunk-এ পড়ুন
- প্রতিটা chunk-এর offset হেক্সে প্রিন্ট করুন (৮ digit, শূন্য-padded)
- প্রতিটা byte হেক্সে দেখান, ১৬টা না হলে জায়গা ফাঁকা রাখুন (alignment ঠিক রাখতে)
- একই byte-গুলো ASCII column-এ দেখান — printable হলে অক্ষর, নাহলে "."
import sys
def hexdump(path: str, width: int = 16) -> None:
with open(path, "rb") as f:
data = f.read()
for offset in range(0, len(data), width):
chunk = data[offset:offset + width]
# হেক্স অংশ — প্রতিটা byte দুই digit, ফাঁকা জায়গা alignment-এর জন্য
hex_part = " ".join(f"{b:02x}" for b in chunk)
hex_part = hex_part.ljust(width * 3 - 1)
# ASCII অংশ — printable হলে অক্ষর, নাহলে '.'
ascii_part = "".join(
chr(b) if 0x20 <= b <= 0x7e else "."
for b in chunk
)
print(f"{offset:08x} {hex_part} |{ascii_part}|")
if __name__ == "__main__":
hexdump(sys.argv[1] if len(sys.argv) > 1 else "sample.bin")প্রত্যাশিত আউটপুট (sample.bin-এর উপর চালালে, আগের experiment
থেকে):
00000000 48 69 0a 00 01 02 ff |Hi.....|এটা প্রায় হুবহু hexdump -C-এর format — কারণ এখন আপনি জানেন
টুলটা ভেতরে ঠিক কী করে।
নিজে বাড়ান:
-g(group size) option যোগ করুন —xxd-এর মতো ২-বাইট group সমর্থন করুন- দুইটা ফাইলের hex dump পাশাপাশি রেখে diff highlight করুন — কোন byte পাল্টেছে
- একটা offset range নিয়ে শুধু সেই অংশ দেখান (
--start,--lengthargument) - একটা ৪-byte বা ৮-byte group-কে little-endian integer হিসেবেও পাশে দেখান — সাবধান, এটা পরের লেসনের বিষয়বস্তুতে হাত দিচ্ছে, endianness লেসনের আগে এই অংশটা optional রাখুন
বাস্তব সিস্টেমে
Hex যেখানে প্রতিদিন দেখা যায়
-
Git object hash। প্রতিটা commit, tree, blob-এর নাম একটা SHA-1 (পুরনো repo) বা SHA-256 (নতুন) hash, hex-এ লেখা —
a94a8fe5ccb19ba61c4c0873d391e987982fbbd3। ৪০ হেক্স digit মানে ১৬০ bit — hash function লেসনের birthday bound সরাসরি প্রযোজ্য এখানে। -
MAC address। নেটওয়ার্ক কার্ডের hardware address ছয়টা byte, হেক্স জোড়ায় লেখা —
00:1a:2b:3c:4d:5e। প্রতিটা জোড়া ঠিক এক byte, colon দিয়ে আলাদা করা মানুষের পড়ার সুবিধার জন্য। -
UUID। ১২৮-bit random identifier, হেক্সে
8-4-4-4-12গ্রুপে লেখা —550e8400-e29b-41d4-a716-446655440000। ৩২টা হেক্স digit = ১২৮ bit, নিখুঁত। -
CSS/design color code।
#ff6b35— উপরে দেখেছি, প্রতিটা channel ঠিক এক byte। -
Crash dump ও core file address।
gdb, Windows-এর blue screen error code (যেমন0x0000007B), Linux kernel panic-এর stack trace — সবই hex, কারণ এগুলো raw memory address বা register value, আর সেগুলো byte-aligned। -
IPv6 address।
2001:0db8:85a3:0000:0000:8a2e:0370:7334— আটটা ১৬-bit group, প্রতিটা ৪ হেক্স digit। IPv4-এর ৩২ bit থেকে IPv6-এর ১২৮ bit-এ যাওয়ার সময় dotted-decimal ছেড়ে hex বাছা হয়েছিল ঠিক এই readability কারণেই — ১২৮ bit decimal-এ লিখলে পড়া অসম্ভব হয়ে যেত। -
Cryptographic key ও digest। SHA-256 digest, AES key, public key fingerprint — cryptography-র প্রায় সব জায়গায় hex (বা base64, ভিন্ন trade-off-এর জন্য, number-systems লেসনে যেটা আমরা তুলনা করেছিলাম) স্ট্যান্ডার্ড representation।
-
Unix file permission — একমাত্র বেঁচে থাকা অক্টাল।
chmod,umask,/proc/[pid]/status-এর কিছু field আজও অক্টালে — একমাত্র জায়গা যেখানে group-of-3 bit গঠন base-এর সাথে নিখুঁত মেলে।
যে ভুলগুলো সবাই করে
“কম্পিউটার সংখ্যা 'হেক্সে সংরক্ষণ করে' — হেক্স একটা ভেতরের representation।”
কম্পিউটার শুধু বাইনারি সংরক্ষণ করে — transistor-এর দুইটা অবস্থা, আগের লেসনে যা দেখেছি। Hex সম্পূর্ণভাবে একটা মানুষের জন্য টেক্সট notation, memory বা disk-এ কোথাও “হেক্স digit” বলে কিছু নেই।
যখন আপনি printf("%x", n) লেখেন, CPU internal binary value-টা
নেয় আর সেটাকে হেক্স character-এ (ASCII ‘0’-‘9’, ‘a’-‘f’)
রূপান্তর করে — একটা সম্পূর্ণ আলাদা রূপান্তর ধাপ, hood section-এ
যেটা আমরা দেখেছি। “হেক্সে সংরক্ষণ” বলে কিছু নেই; আছে
“হেক্সে প্রদর্শন”।
“C-তে একটা numeric literal-এর সামনে leading zero লেখা নিরাপদ, এটা শুধু cosmetic padding।”
এটা একটা ক্লাসিক, বাস্তব bug-এর উৎস। int x = 0755; মানে
অক্টাল ৭৫৫, decimal ৪৯৩ নয়।
int discount = 010; // ভাবছেন 10, আসলে 8 (অক্টাল)
int code = 099; // কম্পাইল error — '9' অক্টালে অবৈধ digitএই ফাঁদটা এতটাই সুপরিচিত যে এটাই মূল কারণ কেন Python 3,
JavaScript strict mode, Rust — সবাই explicit 0o prefix ছাড়া
leading-zero octal সম্পূর্ণ নিষিদ্ধ করে দিয়েছে (উপরের ভাষা
টেবিল দেখুন)। যদি আপনি C বা Java কোডে leading zero সহ একটা
integer literal দেখেন, থামুন আর নিশ্চিত করুন এটা ইচ্ছাকৃত।
“Octal সম্পূর্ণ মৃত — শেখার কোনো দরকার নেই।”
প্রায় মৃত, কিন্তু সম্পূর্ণ নয়। chmod 755, umask 022 — এগুলো
আজও রোজকার Unix ব্যবহারে। যদি অক্টাল না চেনেন, chmod 755-এর
মানে বুঝতে হলে প্রতিবার lookup করতে হবে, আর একটা ভুল permission
(যেমন সবার জন্য write access খোলা রেখে দেওয়া) একটা বাস্তব
security vulnerability।
এছাড়া পুরনো codebase, পুরনো protocol spec, আর কিছু embedded
system-এ অক্টাল literal আজও দেখা যায় — চেনা না থাকলে একটা 017
দেখে ভুল সংখ্যা ধরে নেওয়ার ঝুঁকি থাকে।
“একটা hex dump-এ '00 00 40 41' দেখলে এর মানে সরাসরি বলে দেওয়া যায় এটা কোন সংখ্যা।”
Hood section-এর callout-টা আবার — hex dump শুধু raw byte order দেখায়, কোনো numeric interpretation চাপায় না।
এই চারটা byte-কে একটা সংখ্যা হিসেবে পড়তে হলে জানতে হবে: (১) কতটা byte একসাথে একটা মান গঠন করে (৪ byte ধরে নিলাম, কিন্তু এটাও একটা assumption), আর (২) কোন byte সবচেয়ে গুরুত্বপূর্ণ — প্রথমটা (big-endian) নাকি শেষেরটা (little-endian)।
Big-endian ধরলে 0x00004041 = 16449। Little-endian ধরলে
0x41400000-কে float হিসেবে পড়লে 12.0। একই চারটা byte,
তিনটা ভিন্ন সংখ্যা, নির্ভর করে দুইটা আলাদা convention-এর উপর।
এটাই এই module-এর পরের একটা লেসন — endianness — সম্পূর্ণভাবে
সমাধান করবে।
বুঝেছেন কি না দেখুন
1গাণিতিকভাবে প্রমাণ করুন কেন একটা ৩২-bit value কখনো পরিষ্কারভাবে
পূর্ণসংখ্যক অক্টাল digit-এ ভাগ হয় না, কিন্তু সবসময় পরিষ্কারভাবে
হেক্স digit-এ ভাগ হয়। ১৮-bit word-এ (PDP যুগের) কী হতো?
যুক্তি
একটা n-bit value পরিষ্কারভাবে (কোনো padding ছাড়া) k-bit
group-এ ভাগ হয় যদি এবং কেবল যদি k | n (k, n-কে ভাগ করে)।
হেক্সের জন্য (k=4): 32 / 4 = 8 — পূর্ণসংখ্যা, তাই ৩২-bit
সবসময় ঠিক ৮টা হেক্স digit-এ পরিষ্কারভাবে ভাগ হয়। ব্যাপকভাবে,
8, 16, 32, 64 — এই সবগুলোই 4-এর গুণিতক (আসলে এরা প্রতিটা
নিজেই 2-এর ঘাত, আর 4 = 2², তাই 4 | 2^m সব m ≥ 2-এর
জন্য)। এই কারণেই আধুনিক প্রতিটা byte/word size hex-এ নিখুঁত ফিট
করে — এটা কাকতালীয় নয়, বরং সব আধুনিক word size নিজেরাই ২-এর
ঘাত হওয়ার সরাসরি ফলাফল।
অক্টালের জন্য (k=3): 32 / 3 = 10.67 — পূর্ণসংখ্যা নয়।
কারণ আর মৌলিক, -এর কোনো
ঘাত নয়, তাই — কখনোই ভাগ যাবে না। একই যুক্তি
-এর জন্যও প্রযোজ্য: এরা সবাই আকারের, আর
কখনো -কে ভাগ করে না।
১৮-bit word-এ: । এখন (, পরিষ্কার!), কিন্তু (, পরিষ্কার নয়)। এটাই ঠিক উল্টো ফলাফল — PDP-যুগের ১৮-bit (ও ১২-bit, , উভয়েই বিভাজ্য) word-এ অক্টাল পরিষ্কার ফিট করত, hex করত না (পরিষ্কারভাবে, যদিও hex ব্যবহারযোগ্য ছিল, শুধু padding লাগত)।
সাধারণ নীতি: কোন base “স্বাভাবিক” তা নির্ভর করে word size-এর prime factorization-এর উপর — modular arithmetic লেসনের ধারণারই একটা সরাসরি প্রকৌশল প্রয়োগ। Word size বদলালে “স্বাভাবিক” base-ও বদলে যায়, ঠিক যেমন ইতিহাসে সত্যিই ঘটেছিল।
2হাতে হিসাব করুন: 0xFF (এক byte)-এর decimal মান কত? আর 11
byte-এর একটা group (যেমন 0xFFFFFFFFFFF, ১১ হেক্স digit)
সর্বোচ্চ কত decimal মান ধারণ করতে পারে, সাধারণ সূত্র আকারে?
প্রয়োগ
0xFF (এক byte)-এর decimal মান কত? আর 11
byte-এর একটা group (যেমন 0xFFFFFFFFFFF, ১১ হেক্স digit)
সর্বোচ্চ কত decimal মান ধারণ করতে পারে, সাধারণ সূত্র আকারে?0xFF:
এটাই এক byte-এর সর্বোচ্চ মান — যা আমরা আগেও দেখেছি ()।
সাধারণ সূত্র: n টা হেক্স digit দিয়ে সর্বোচ্চ যে মান লেখা
যায়, তা প্রতিটা digit f (=15) হলে পাওয়া যায়:
১১টা হেক্স digit-এর জন্য:
যাচাই — এটা হওয়া উচিত 2^44 - 1, আর ,
তাই — উপরের সংখ্যাটার (1.76 × 10^13) সাথে
matches, ক্রম ঠিক আছে।
সাধারণ নীতি: n হেক্স digit 4n bit
সর্বোচ্চ মান । এই সরাসরি সম্পর্কটাই
(কোনো log/ratio হিসাব ছাড়া) hex-এর মূল ব্যবহারিক সুবিধা —
Level 0-এর number-systems লেসনে দেখানো সূত্রটা এখানে trivial হয়ে যায় কারণ ।
3একজন নতুন প্রোগ্রামার লিখল:
int retry_delay_ms = 050;
সে আশা করছিল retry_delay_ms = 50। প্রকৃতপক্ষে কম্পাইলার এটাকে
কী মান দেবে, আর কেন? এই bug ধরার তিনটা ভিন্ন উপায় বলুন।
যুক্তি
int retry_delay_ms = 050;retry_delay_ms = 50। প্রকৃতপক্ষে কম্পাইলার এটাকে
কী মান দেবে, আর কেন? এই bug ধরার তিনটা ভিন্ন উপায় বলুন।050 C-তে অক্টাল literal (leading zero), decimal ৫০ নয়।
তাই retry_delay_ms হবে 40, 50 নয় — একটা silent, কম্পাইল-টাইম
error ছাড়াই ভুল মান, ঠিক এই lesson-এর “leading zero ফাঁদ”
misconception-এর বাস্তব উদাহরণ।
ধরার তিনটা উপায়:
১. Static analysis / linter। cppcheck, clang-tidy,
বা GCC নিজেই -Wall-এ কিছু ক্ষেত্রে সন্দেহজনক octal literal নিয়ে
warn করে, বিশেষত যদি digit 8 বা 9 থাকে (তখন সরাসরি error)।
কিন্তু 050-এর মতো valid অক্টাল কোনো error ছাড়াই কম্পাইল হয়ে
যায় — তাই এই পদ্ধতিটা নির্ভরযোগ্য নয়।
২. কোড রিভিউ / নিয়ম হিসেবে leading zero নিষিদ্ধ। টিম policy
হিসেবে “কোনো numeric literal-এ leading zero লেখা যাবে না, 0x
বা 0o(GNU extension) ছাড়া” — একটা সহজ grep-able নিয়ম:
grep -rnE '\b0[0-9]+\b' *.c # সন্দেহজনক leading-zero literal খুঁজুন৩. এমন ভাষা বাছুন যেখানে এই ambiguity নেই। এই lesson-এর
ভাষা টেবিল থেকে — Python 3, Rust, Go-তে 050 সরাসরি একটা
compile/parse error (bare leading zero অবৈধ), অথবা কিছু ভাষায়
সরাসরি decimal 50 হিসেবে গ্রহণ করা হয় (কোনো implicit octal
নেই)। ভাষা ডিজাইনের সিদ্ধান্তই এখানে সবচেয়ে শক্তিশালী প্রতিকার —
bug-টা ঘটার সুযোগই দেওয়া হয় না।
ব্যবহারিক পরামর্শ: সবসময় দশমিক সংখ্যা লিখতে চাইলে leading
zero সম্পূর্ণ এড়িয়ে চলুন; অক্টাল বোঝাতে চাইলে explicit করুন
(সম্ভব হলে 0o ব্যবহার করুন, GNU C extension-এ পাওয়া যায়)।
4নিচের hexdump snippet-টা দেখুন এবং বলুন এটা কোন ফাইল ফরম্যাট
হতে পারে, কেন:
00000000 25 50 44 46 2d 31 2e 34 0a 25 e2 e3 cf d3 0a 0a |%PDF-1.4.%......|
প্রয়োগ
00000000 25 50 44 46 2d 31 2e 34 0a 25 e2 e3 cf d3 0a 0a |%PDF-1.4.%......|প্রথম ৪ byte: 25 50 44 46 → ASCII-তে %, P, D, F → %PDF।
এটা PDF ফাইলের সিগনেচার — প্রতিটা বৈধ PDF ফাইল %PDF-
দিয়ে শুরু হয়, তারপর version number (এখানে 1.4, byte
31 2e 34 = 1.4 ASCII-তে)।
পরের byte-গুলো (0a 25 e2 e3 cf d3 0a 0a) একটা সচরাচর PDF
convention — একটা newline, তারপর চারটা ইচ্ছাকৃতভাবে উচ্চ-মান
non-ASCII byte (e2 e3 cf d3, প্রতিটা 0x80-এর উপরে)। এই
byte গুলো ঠিক আগের experiment-এর PNG magic number-এর 0d 0a 1a 0a-এর মতোই একই উদ্দেশ্যে রাখা — কোনো text-mode file transfer
tool যদি “binary” আর “text” ফাইল আলাদা করার চেষ্টা করে অক্ষরের
উপর ভিত্তি করে (পুরনো কিছু FTP client এমন করত), high-byte
সিকোয়েন্স দেখলে সেটা নিশ্চিত হয় ফাইলটা binary, আর ভুলভাবে
byte পরিবর্তন করে না।
সাধারণ পদ্ধতি এই প্রশ্নে: প্রথম কয়েক byte-কে ASCII-তে অনুবাদ
করে দেখুন কোনো পরিচিত magic string (%PDF, PNG, GIF89a,
PK (ZIP), \x7fELF) মেলে কি না — লেসন ১-এ যা আলোচনা হয়েছিল,
এখানে সরাসরি ব্যবহারিক প্রয়োগ। file কমান্ড ঠিক এই একই কৌশল
স্বয়ংক্রিয়ভাবে করে, একটা magic number ডাটাবেস (/usr/share/ file/magic) ব্যবহার করে।
5আপনি একটা নতুন প্রোগ্রামিং ভাষার lexer ডিজাইন করছেন। আপনি চান
0x, 0b, 0o prefix সমর্থন করবেন, কিন্তু “leading zero
octal ফাঁদ” যেন কখনো না ঘটে। Lexer-এর নিয়ম কী হবে? Edge case
গুলো (0, 007, 0.5, 0x) কীভাবে সামলাবেন?
ডিজাইন
0x, 0b, 0o prefix সমর্থন করবেন, কিন্তু “leading zero
octal ফাঁদ” যেন কখনো না ঘটে। Lexer-এর নিয়ম কী হবে? Edge case
গুলো (0, 007, 0.5, 0x) কীভাবে সামলাবেন?লক্ষ্য: 0x/0b/0o ছাড়া কোনো implicit base পরিবর্তন
নেই — bare leading zero সবসময় decimal-ই থাকবে (অথবা explicit
error দেবে, কিন্তু কখনো silently ভিন্ন base-এ পড়বে না)।
নিয়ম:
- যদি প্রথম character
0হয় আর পরের characterx/Xহয় → HEX mode। এরপর কমপক্ষে একটা hex digit (0-9a-fA-F) বাধ্যতামূলক — শুধু0xহলে error (নিচে দেখুন)। - যদি প্রথম character
0আর পরেরb/Bহয় → BINARY mode, একই নিয়মে কমপক্ষে একটা0/1বাধ্যতামূলক। - যদি প্রথম character
0আর পরেরo/Oহয় → OCTAL mode, কমপক্ষে একটা0-7বাধ্যতামূলক। - যদি প্রথম character
0আর তার পরে সরাসরি আরেকটা digit (0-9) আসে, কোনো prefix letter ছাড়াই → এখানেই সিদ্ধান্ত নিতে হবে। দুইটা নিরাপদ বিকল্প:- (ক) সবসময় error — Python 3-এর পদ্ধতি (
SyntaxError: leading zeros in decimal integer literals are not permitted)। প্রোগ্রামারকে বাধ্য করা explicit হতে। - (খ) decimal হিসেবেই পড়া, warning সহ — ambiguity নেই, কিন্তু সম্ভবত ভুল intent (কেউ কেন leading zero লিখবে?) সম্পর্কে সতর্ক করা। কখনোই implicit octal নয় — এটাই মূল সিদ্ধান্ত যা C-র ভুল এড়ায়।
- (ক) সবসময় error — Python 3-এর পদ্ধতি (
0একা (কিছু না অনুসরণ করলে) → বৈধ, মান0, decimal।0.5→ দশমিক বিন্দু দেখলে prefix-checking বন্ধ, এটা একটা float literal, নিয়ম ৪-এর আওতায় পড়ে না।
Edge case-গুলো:
| Input | সিদ্ধান্ত | কারণ |
|---|---|---|
0 | বৈধ, 0 | একা শূন্য কোনো prefix দরকার নেই |
007 | error (Python-স্টাইল) | leading zero + আরও digit, ambiguous intent |
0.5 | বৈধ float, 0.5 | দশমিক বিন্দু prefix-checking বাতিল করে |
0x (prefix, কোনো digit নেই) | error | prefix আছে কিন্তু কোনো digit নেই — “0x কী?” |
0X1f | বৈধ, hex (case-insensitive prefix) | বেশিরভাগ ভাষা x/X দুটোই মানে |
0b2 | error | BINARY mode-এ 2 অবৈধ digit |
একটা সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ ডিজাইন প্রশ্ন: hex digit-এর
case কি gুরুত্বপূর্ণ? সাধারণত না — 0xAF আর 0xaf একই মান
বোঝায় (case শুধু style, semantics নয়), যদিও প্রকল্প স্টাইল
গাইড একটা নির্দিষ্ট case-এ consistency চাইতে পারে।
মূল শিক্ষা: ভালো lexer design মানে ambiguity যতটা সম্ভব compile-time error-এ রূপান্তর করা, silent-এ ভুল আচরণ নয় — ঠিক Python 3, Rust যা করেছে, C যা করেনি।
এরপর কী
এরপর কী
এই লেসনে আমরা bit pattern পড়ার আর লেখার ভাষা শিখলাম —
কোন base কখন স্বাভাবিক, কেন, আর বাস্তব টুলে (compiler literal,
xxd) সেটা কীভাবে কাজ করে। কিন্তু একটা প্রশ্ন এখনো এড়িয়ে
গেছি: আমরা বারবার “৮ bit-এর একটা group”, “৩২-bit value”,
“৪-byte integer” বলেছি — কিন্তু কেন ৮? কেন সব জায়গায় byte
মানে ৮ bit, যখন এই লেসনেই আমরা দেখলাম ইতিহাসে ১২, ১৮, ৩৬-bit
word-ও ছিল?
পরের লেসনে আমরা সেই প্রশ্নের উত্তর খুঁজব — bit, nibble, byte, word-এর সম্পূর্ণ পরিচয়, কীভাবে ৮-bit byte “জিতল” (একটা বাণিজ্যিক সিদ্ধান্ত, IBM System/360-এর গল্প), word size কীভাবে একটা CPU-র register width, address space, আর এমনকি pointer-এর আকার নির্ধারণ করে (৩২-bit-এর বিখ্যাত “4GB wall” সহ), আর কেন memory-তে একটা value-কে align করে রাখা (৪-byte int-কে ৪-এর গুণিতক address-এ) শুধু একটা style choice নয়, বরং hardware bus-এর একটা সরাসরি ফলাফল।
আরও পড়ুন
- Mathematics for Computer Science, Chapter 9 — Lehman, Leighton, Meyer · Base conversion-এর গাণিতিক ভিত্তি — এই লেসনের prerequisite
- The Unix Programming Environment, Chapter 2 — Brian Kernighan, Rob Pike · Unix file permission-এর octal convention-এর ঐতিহাসিক প্রেক্ষাপট
- xxd(1) and hexdump(1) man pages — GNU / BSD coreutils · প্রকৃত টুলের ব্যবহারিক reference