এই পর্বে যা শিখবেন
- কম্পিউটার কেন শুধু ০ আর ১ ব্যবহার করে, আর বিট-বাইট দিয়ে কীভাবে মাপা হয়
- বাইনারি ও হেক্সাডেসিমালে গোনা — হাতে রূপান্তর করতে পারা
- ঋণাত্মক সংখ্যা কীভাবে থাকে, আর overflow কেন বিপজ্জনক
- দশমিক সংখ্যা কেন হুবহু থাকে না, আর টাকার হিসাব কীভাবে নিরাপদে রাখতে হয়
- ASCII, Unicode, UTF-8 — বাংলা লেখা কম্পিউটারে কীভাবে থাকে, আর ভেঙে যায় কেন
১ · সবকিছুই ০ আর ১ — তবু অর্থ আলাদা
আপনার ফোনটি হাতে নিন। এর ভেতরে আছে গত ঈদের ছবি, মায়ের পাঠানো একটি বাংলা SMS, bKash অ্যাপে আপনার ব্যালান্স, আর প্রিয় একটি গান। চারটি একেবারে ভিন্ন জিনিস। অথচ ফোনের মেমরির ভেতরে এদের মধ্যে কোনো পার্থক্য নেই — সবই শুধু ০ আর ১-এর লম্বা সারি।
তাহলে ফোন কীভাবে বোঝে কোনটি ছবি আর কোনটি টাকা? উত্তরটি এই পর্বের, এমনকি পুরো C ভাষার সবচেয়ে গুরুত্বপূর্ণ ধারণাগুলোর একটি:
অর্থ ঠিক হয় — আপনি সেগুলো কীভাবে পড়ছেন।
একটি উদাহরণ দেখুন। নিচের ৮টি বিট হুবহু একই। কিন্তু চারজন ভিন্ন “পাঠক” এগুলো থেকে চারটি ভিন্ন অর্থ বের করছে:
| একই ৮টি বিট | যে নিয়মে পড়া হলো | যা পাওয়া গেল |
|---|---|---|
01000001 | চিহ্নহীন পূর্ণসংখ্যা হিসেবে | সংখ্যা 65 |
01000001 | ASCII অক্ষর হিসেবে | ইংরেজি বড় হাতের A |
01000001 | ছবির একটি পিক্সেলের লাল অংশ হিসেবে | ২৫৬ ধাপের মধ্যে ৬৫ মাত্রার লাল — বেশ গাঢ় লাল |
01000001 | সাদা-কালো ছবির উজ্জ্বলতা হিসেবে | গাঢ় ধূসর একটি বিন্দু |
বাংলা আর ইংরেজিতে “bat” আর “বাত” শুনতে কাছাকাছি, কিন্তু অর্থ সম্পূর্ণ আলাদা — কোন ভাষায় শুনছেন তার ওপর নির্ভর করে। আরেকটি উদাহরণ: আপনার রোল নম্বর ৬৫. ক্লাসের হাজিরা খাতায় সেটি একজন ছাত্রের পরিচয়, আবার নম্বরপত্রে ৬৫ মানে প্রাপ্ত নম্বর। সংখ্যা একই, কিন্তু কোন খাতায় লেখা সেটাই অর্থ ঠিক করে। কম্পিউটারে সেই “খাতা”-র নাম হলো টাইপ।
C ভাষায় আপনি যখন লিখবেন int, char বা double — তখন আসলে compiler-কে বলে দিচ্ছেন, “এই বিটগুলো এই নিয়মে পড়বে।” পরের পর্বে আমরা এই টাইপগুলো নিয়েই কাজ শুরু করব। তার আগে এই পর্বে দেখে নেব, নিয়মগুলো আসলে কী — সংখ্যা, ঋণাত্মক সংখ্যা, দশমিক আর লেখা কীভাবে বিটে বসে।
মেমরি জানে না সে কী রাখছে। সে শুধু বিট রাখে। টাইপ হলো বিট পড়ার নিয়ম। ভুল নিয়মে পড়লে সঠিক বিট থেকেও ভুল ফল আসে — এই পর্বের প্রায় প্রতিটি বাগের মূলে এই একটি কথা।
২ · কেন শুধু ০ আর ১? সুইচ, ভোল্টেজ আর নির্ভরযোগ্যতা
মানুষ দশটি আঙুলে গুনতে শিখেছে বলে আমরা দশ-ভিত্তিক decimal সংখ্যা ব্যবহার করি। কিন্তু কম্পিউটারের আঙুল নেই — আছে কোটি কোটি ক্ষুদ্র সুইচ, যাদের নাম transistor. একটি সুইচের দুটি অবস্থা: বিদ্যুৎ যাচ্ছে, অথবা যাচ্ছে না। অন মানে ১, অফ মানে ০. এই দুই-অঙ্কের পদ্ধতির নাম binary.
প্রশ্ন আসতে পারে — তারের ভোল্টেজ তো অনেক রকম হতে পারে। তাহলে ১০টি ভোল্টেজ-স্তর দিয়ে সরাসরি ০ থেকে ৯ রাখা হয় না কেন? কারণ নির্ভরযোগ্যতা।
লোডশেডিংয়ের পর বিদ্যুৎ ফিরলে ঘরের বাতি কখনো একটু কম, কখনো একটু বেশি জ্বলে। এখন ভাবুন, বাতির উজ্জ্বলতা দেখে আপনাকে বলতে হবে এটি ১০টি স্তরের কোনটি — “৬ নাকি ৭?” বারবার ভুল হবে। কিন্তু যদি শুধু বলতে হয় “বাতি জ্বলছে, না নিভে আছে?” — ভোল্টেজ একটু ওঠানামা করলেও উত্তর ভুল হবে না। কম্পিউটার ঠিক এই কারণেই দুটি অবস্থা বেছে নিয়েছে: অনেক ভোল্টেজের মধ্যে একটা সীমা টানা হয় — তার ওপরে হলে ১, নিচে হলে ০।
দুটি অবস্থার আরও সুবিধা আছে। দুই-অবস্থার সুইচ বানানো সস্তা, ছোট, দ্রুত। আর যুক্তির নিয়ম (সত্য/মিথ্যা, এবং/অথবা) সরাসরি এই সুইচ দিয়ে বানানো যায়। এ কারণেই আজকের অনেক দামি ফোনের প্রসেসরে এক হাজার কোটিরও বেশি transistor থাকে, প্রতিটি সেকেন্ডে কোটি কোটি বার অন-অফ হয়।
বিট, বাইট আর বড় মাপগুলো
একটি ০ বা ১-কে বলে বিট (binary digit-এর সংক্ষেপ)। একটি বিট খুব সামান্য তথ্য রাখে — শুধু “হ্যাঁ বা না”. তাই ৮টি বিট একসাথে করে একটি দল বানানো হয়, যার নাম byte. মেমরির প্রতিটি ঘরের নিজস্ব ঠিকানা থাকে, আর প্রতিটি ঘরে থাকে ঠিক এক বাইট।
| একক | কত বড় | মোটামুটি কী আঁটে |
|---|---|---|
| ১ বিট | ০ অথবা ১ | একটি হ্যাঁ/না উত্তর |
| ১ বাইট | ৮ বিট | একটি ইংরেজি অক্ষর |
| ১ KB (কিলোবাইট) | প্রায় এক হাজার বাইট | আধা পাতার একটি চিঠি |
| ১ MB (মেগাবাইট) | প্রায় দশ লাখ বাইট | এক মিনিটের একটি MP3 গান বা একটি হালকা ছবি |
| ১ GB (গিগাবাইট) | প্রায় একশো কোটি বাইট | মাঝারি মানের একটি সিনেমা |
নতুন ১ TB হার্ডডিস্ক কিনে Windows-এ দেখেন মাত্র ৯৩১ GB? কেউ ঠকায়নি! কোম্পানি গোনে দশের ঘাতে (১ KB = ১০০০ বাইট), আর Windows গোনে দুইয়ের ঘাতে (১ KB = ১০২৪ বাইট)। একই বাইট, দুই রকম গোনার নিয়ম — এই পর্বের মূল কথারই আরেকটি উদাহরণ। কড়া নিয়মে ১০২৪-ভিত্তিক এককের নাম KiB, MiB, GiB.
৩ · বাইনারিতে গোনা — ঘরের মান দ্বিগুণ হয়
দশমিকে আমরা কীভাবে গুনি, একটু খেয়াল করুন। ৩৪৭ মানে ৩টি একশো + ৪টি দশ + ৭টি এক। প্রতিটি ঘরের মান তার ডানের ঘরের ১০ গুণ: ১, ১০, ১০০, ১০০০… একটি ঘরে ৯ পেরোলে সেটি ০ হয়, আর বাঁয়ের ঘরে ১ “হাতে” যায়।
বাইনারিতেও হুবহু একই নিয়ম — শুধু প্রতিটি ঘরের মান ২ গুণ হয়: ১, ২, ৪, ৮, ১৬, ৩২, ৬৪, ১২৮. আর প্রতিটি ঘরে অঙ্ক মাত্র দুটি: ০ আর ১. ফলে ১ পেরোলেই হাতে চলে যায়। নিচে চারটি বাতি (৪টি বিট) দিয়ে ০ থেকে ৮ পর্যন্ত গুনে দেখুন। প্রতিটি ধাপে কোন বাতিগুলো বদলাচ্ছে — সবুজ দাগ দেখুন:
| ৮-এর ঘর | |
| ৪-এর ঘর | |
| ২-এর ঘর | |
| ১-এর ঘর | |
| জ্বলা বাতির যোগফল |
- শূন্য। চারটি বাতিই নিভে আছে:
0000. কোনো ঘরের মান যোগ হচ্ছে না, তাই সংখ্যাটি ০। - এক। সবচেয়ে ডানের বাতি (১-এর ঘর) জ্বলল:
0001= ১। - দুই — প্রথম “হাতে ১”. ১-এর ঘরে আর জায়গা নেই (সেখানে শুধু ০ বা ১ বসে)। তাই সেটি নিভে গেল, আর বাঁয়ের ২-এর ঘরে ১ চলে গেল:
0010= ২। দশমিকে ৯ থেকে ১০ হওয়ার মতোই ঘটনা। - তিন। ১-এর ঘর আবার জ্বলল:
0011= ২ + ১ = ৩। - চার — হাতে হাতে ১. ১-এর ঘর পূর্ণ, তাই হাতে গেল ২-এর ঘরে; সেটিও পূর্ণ, তাই হাতে গেল ৪-এর ঘরে। দুটি বাতি নিভল, একটি জ্বলল:
0100= ৪। দশমিকে ৯৯ থেকে ১০০ হওয়ার মতো। - পাঁচ।
0101= ৪ + ১ = ৫। খেয়াল করুন, যেকোনো সংখ্যা আসলে কিছু “২-এর ঘাত”-এর যোগফল। - ছয়।
0110= ৪ + ২ = ৬। - সাত — সব বাতি জ্বলছে (৮-এর ঘর বাদে)।
0111= ৪ + ২ + ১ = ৭। তিনটি বিটে এটাই সবচেয়ে বড় সংখ্যা। লক্ষ করুন: ৭ = ৮ − ১। - আট — তিনটি বাতি একসাথে নিভল। হাতের ১ গড়িয়ে গড়িয়ে ৮-এর ঘরে পৌঁছাল:
1000= ৮। চারটি বিটে সর্বোচ্চ যাওয়া যায়1111= ১৫ পর্যন্ত; ১৬ লিখতে পঞ্চম বাতি লাগবে।
এখান থেকে একটি সুন্দর নিয়ম বেরিয়ে আসে: n টি বিটে মোট ২nটি আলাদা মান রাখা যায় — ০ থেকে ২n − ১ পর্যন্ত। ৪ বিটে ১৬টি মান (০–১৫), ৮ বিটে ২৫৬টি মান (০–২৫৫)। প্রতিটি বাড়তি বিট ধারণক্ষমতা দ্বিগুণ করে।
দশমিক থেকে বাইনারি — হাতে রূপান্তর
৪৫-কে বাইনারিতে লিখতে চাইলে বড় থেকে ছোট ঘরের মান দেখে দেখে বিয়োগ করুন: ৪৫-এর মধ্যে ৩২ আঁটে (বাকি ১৩), ১৬ আঁটে না, ৮ আঁটে (বাকি ৫), ৪ আঁটে (বাকি ১), ২ আঁটে না, ১ আঁটে। যেসব ঘর আঁটল সেখানে ১, বাকিগুলোয় ০:
101101C কোডে বা বইয়ে বাইনারি সংখ্যা আলাদা করে চেনাতে সামনে 0b লেখা হয়, যেমন 0b101101. না হলে 101 দেখে বোঝা যায় না এটি একশো এক, নাকি পাঁচ। 0b লেখার সুবিধা C23 মানে আনুষ্ঠানিকভাবে যোগ হয়েছে; GCC ও Clang অনেক আগে থেকেই এটি মেনে নেয়।
৪ · হেক্সাডেসিমাল — প্রোগ্রামারের সংক্ষিপ্ত লিপি
বাইনারি কম্পিউটারের জন্য আদর্শ, কিন্তু মানুষের জন্য ক্লান্তিকর। ৩২ বিটের একটি সংখ্যা দেখতে এমন: 00000000001110011011010101001001. একটা ০ বাদ পড়লেও চোখে পড়বে না। দশমিকে লিখলে ছোট হয়, কিন্তু তখন কোন বিট কোথায় তা আর বোঝা যায় না।
সমাধান হলো ষোলো-ভিত্তিক hexadecimal পদ্ধতি, সংক্ষেপে যাকে hex বলা হয়। এতে অঙ্ক ১৬টি: ০ থেকে ৯, তারপর A (১০), B (১১), C (১২), D (১৩), E (১৪), F (১৫)। এর জাদুটা হলো: ৪টি বিট মিলে ঠিক একটি hex অঙ্ক — কারণ ৪ বিটে ঠিক ১৬টি মান। তাই বাইনারিকে ৪টি করে ভাগ করে প্রতিটি ভাগকে একটি অঙ্কে লিখে ফেলা যায়, কোনো হিসাব ছাড়াই।
| Hex | বাইনারি | দশমিক | Hex | বাইনারি | দশমিক |
|---|---|---|---|---|---|
0 | 0000 | 0 | 8 | 1000 | 8 |
1 | 0001 | 1 | 9 | 1001 | 9 |
2 | 0010 | 2 | A | 1010 | 10 |
3 | 0011 | 3 | B | 1011 | 11 |
4 | 0100 | 4 | C | 1100 | 12 |
5 | 0101 | 5 | D | 1101 | 13 |
6 | 0110 | 6 | E | 1110 | 14 |
7 | 0111 | 7 | F | 1111 | 15 |
উপরের সেই লম্বা ৩২ বিটকে ৪টি করে ভাগ করুন: 0000 0000 0011 1001 1011 0101 0100 1001. প্রতিটি ভাগ টেবিল থেকে বদলে নিলে পাই 0x0039B549. C-তে hex সংখ্যার সামনে 0x লেখা হয়। এক বাইট (৮ বিট) সবসময় ঠিক দুটি hex অঙ্ক — তাই মেমরি দেখার সময় প্রোগ্রামাররা hex-ই ব্যবহার করেন।
যেখানে যেখানে hex দেখবেন
ওয়েবের রং
ABCL TECH-এর সবুজ রং #39B549 আসলে তিনটি বাইট: লাল 0x39 (৫৭), সবুজ 0xB5 (১৮১), নীল 0x49 (৭৩)। প্রতিটি ০ থেকে ২৫৫ পর্যন্ত। সবুজের মান সবচেয়ে বেশি, তাই রংটি সবুজ।
মেমরির ঠিকানা
Debugger বা error-এ যে ঠিকানা দেখবেন, যেমন 0x7ffd5e8c — সেগুলো hex-এ লেখা। পয়েন্টারের পর্বে এগুলো প্রতিদিনের সঙ্গী হবে।
নেটওয়ার্ক ও হার্ডওয়্যার
ফোন বা ল্যাপটপের Wi-Fi-এর MAC ঠিকানা, যেমন 3C:22:FB:… — ছয়টি বাইট, প্রতিটি hex-এ।
ফাইলের ভেতরটা
যেকোনো ফাইল “hex editor” দিয়ে খুললে বাইটগুলো hex-এ দেখা যায়। পরের অংশগুলোতে আমরা বাংলা লেখার বাইট এভাবেই দেখব।
xxd, HxD৫ · ঋণাত্মক সংখ্যা — two's complement-এর কৌশল
এখন পর্যন্ত আমরা শুধু শূন্য আর ধনাত্মক সংখ্যা দেখেছি — এদের বলে unsigned (চিহ্নহীন)। কিন্তু তাপমাত্রা -৫° হয়, ব্যাংক হিসাবে ঋণ থাকে। কম্পিউটারে “−” চিহ্ন রাখার আলাদা কোনো জায়গা নেই — আছে শুধু বিট। তাহলে ঋণাত্মক সংখ্যা রাখা যায় এমন signed সংখ্যা কীভাবে বানাবেন?
প্রথম চেষ্টা: একটি বিট চিহ্নের জন্য
সবচেয়ে সহজ বুদ্ধি: বাঁ দিকের প্রথম বিটটিকে চিহ্ন বানাই — ০ মানে ধনাত্মক, ১ মানে ঋণাত্মক। এই বিটকে বলে sign bit. তাহলে +5 = 00000101, আর -5 = 10000101. শুনতে চমৎকার, কিন্তু দুটি বড় সমস্যা:
- দুটি শূন্য!
00000000= +0 আর10000000= -0. গণিতে শূন্য একটাই। এখন প্রতিটি তুলনায় প্রসেসরকে মনে রাখতে হবে যে এ দুটি “সমান”. - যোগ ভেঙে যায়। +5 আর -5 সাধারণ বাইনারি নিয়মে যোগ করলে পাই
10001010= -10, অথচ উত্তর হওয়ার কথা ০. যোগ আর বিয়োগের জন্য আলাদা আলাদা জটিল সার্কিট লাগবে।
আসল সমাধান: গাড়ির মিটারের বুদ্ধি
পুরোনো মোটরসাইকেল বা CNG-র মিটারের কথা ভাবুন — তিন ঘরের, ০০০ থেকে ৯৯৯. মিটার ০০০-তে থাকা অবস্থায় যদি গাড়ি এক কিলোমিটার পেছনে চালান, মিটার দেখাবে ৯৯৯. তাহলে ৯৯৯-কে “-১” ধরে নিলে কেমন হয়? ৯৯৮ হবে -২, ৯৯৭ হবে -৩… আর মজার ব্যাপার: ৫ + ৯৯৯ = ১০০৪, কিন্তু মিটারে ঘর মাত্র তিনটি, তাই দেখাবে ০০৪ — অর্থাৎ ৫ + (-১) = ৪. কোনো আলাদা নিয়ম ছাড়াই যোগটা ঠিক হয়ে গেল!
কম্পিউটার ঠিক এই বুদ্ধিই ব্যবহার করে, দশমিকের বদলে বাইনারিতে। এর নাম two's complement. ৮ বিটে 00000000 থেকে এক পেছালে পাই 11111111 — তাই এটিই -1. কোনো সংখ্যার ঋণাত্মক রূপ বের করার নিয়ম মাত্র দুই ধাপের: সব বিট উল্টে দিন, তারপর ১ যোগ করুন। নিচে দেখুন:
| বিট প্যাটার্ন | |
| unsigned হিসেবে পড়লে | |
| signed (two's complement) হিসেবে পড়লে |
5 = 4 + 1
= 0000 0101
↑ বাঁয়ের বিট 0 — তাই ধনাত্মক
0000 0101 ↓ প্রতিটি 0 হলো 1, প্রতিটি 1 হলো 0 1111 1010
1111 1010
+ 1
-----------
1111 1011 ← এটাই -5
0000 0101 (+5) + 1111 1011 (-5) ------------- 1 0000 0000 ↑ নবম বিট — ৮ বিটের ঘরে জায়গা নেই, বাদ পড়ল ফল: 0000 0000 = 0
127 = 64 + 32 + 16 + 8 + 4 + 2 + 1
= 0111 1111
0111 1111 (127) + 1 ----------- 1000 0000 ← বাঁয়ের বিট 1 হয়ে গেল! signed হিসেবে = -128
- শুরু: +5. ৮ বিটে ৫ লিখলে
0000 0101. বাঁয়ের বিটটি ০, তাই signed হিসেবে পড়লেও এটি ধনাত্মক ৫। ধনাত্মক সংখ্যায় দুই নিয়মে কোনো পার্থক্য নেই। - ধাপ ক — সব বিট উল্টে দিন। প্রতিটি ০ হলো ১, প্রতিটি ১ হলো ০:
1111 1010. এটি এখনো -5 নয় — signed হিসেবে এটি -6. আরেক ধাপ বাকি। - ধাপ খ — ১ যোগ করুন।
1111 1010+ 1 =1111 1011. এটাই -5. খেয়াল করুন, একই বিট unsigned হিসেবে পড়লে ২৫১ — আর ২৫১ = ২৫৬ − ৫. মিটারের ৯৯৯-এর মতোই হিসাব। - যাচাই — 5 + (-5). সাধারণ বাইনারি যোগ করলে নবম একটি বিট তৈরি হয়। কিন্তু ঘর মাত্র ৮টি, তাই সেটি বাদ পড়ে, থাকে
0000 0000. ফল ঠিক ০ — কোনো বিশেষ নিয়ম ছাড়াই। এ কারণেই প্রসেসরে যোগ আর বিয়োগের জন্য একটিই সার্কিট যথেষ্ট। - সীমানায় পৌঁছানো: 127. signed ৮ বিটে সবচেয়ে বড় ধনাত্মক সংখ্যা
0111 1111= ১২৭। বাঁয়ের বিটটি ১ হলেই সংখ্যা ঋণাত্মক হয়ে যায়, তাই এর চেয়ে বড় ধনাত্মক সংখ্যা রাখা সম্ভব নয়। - 127 + 1 = -128! যোগ করলে পাই
1000 0000. unsigned হিসেবে এটি সঠিক ১২৮, কিন্তু signed হিসেবে এটি -128 — সবচেয়ে ছোট সংখ্যা। মিটার পুরো ঘুরে উল্টো প্রান্তে চলে গেছে। এটাই overflow, আর পরের অংশে দেখব এটি বাস্তবে কত বড় ক্ষতি করতে পারে।
Two's complement-এ বাঁয়ের বিটটি এখনো চিহ্ন বোঝায় (১ মানে ঋণাত্মক), কিন্তু শূন্য একটাই, আর যোগ-বিয়োগ একই নিয়মে চলে। তাই আজকের প্রায় সব কম্পিউটার এই পদ্ধতিই ব্যবহার করে। নিচের টেবিলে দেখুন কত বিটে কত বড় সংখ্যা ধরে:
| বিট | unsigned সীমা | signed সীমা | C-তে সাধারণ টাইপ |
|---|---|---|---|
| 8 | 0 থেকে 255 | -128 থেকে 127 | char |
| 16 | 0 থেকে 65,535 | -32,768 থেকে 32,767 | short |
| 32 | 0 থেকে 4,294,967,295 | -2,147,483,648 থেকে 2,147,483,647 | int (সাধারণ কম্পিউটারে) |
| 64 | 0 থেকে প্রায় 1.8 × 1019 | প্রায় ±9.2 × 1018 | long long |
signed সীমা কখনো সমান-সমান নয় — ঋণাত্মক দিকে একটি বেশি থাকে, যেমন ৮ বিটে -১২৮ থেকে ১২৭ পর্যন্ত। কারণ শূন্য বসেছে ধনাত্মক দিকের অর্ধেকে। এই ছোট্ট অসমতা থেকেই একটি বিখ্যাত বাগ আসে: সবচেয়ে ছোট সংখ্যার পরম মান (যেমন abs(-2147483648)) ওই টাইপে রাখাই যায় না।
৬ · Overflow — যখন সংখ্যা তার ঘরে আঁটে না
ঘর সীমিত — এটা শুধু তাত্ত্বিক কথা নয়। বাস্তবের সফটওয়্যারে overflow রকেট ধ্বংস করেছে, আর ভবিষ্যতে কোটি কোটি যন্ত্রের ঘড়ি ভুল করার ঝুঁকি তৈরি করেছে। তিনটি সত্য ঘটনা দেখুন:
Ariane 5, ফ্লাইট ৫০১ (১৯৯৬): ইউরোপের নতুন Ariane 5 রকেট প্রথম উৎক্ষেপণের ৪০ সেকেন্ডের মধ্যেই পথ হারিয়ে ধ্বংস হয়ে যায়, সাথে বিজ্ঞানীদের চারটি স্যাটেলাইট। তদন্তে দেখা যায়, দিক নির্ণয়ের সফটওয়্যার একটি ৬৪ বিটের দশমিক সংখ্যাকে ১৬ বিটের signed পূর্ণসংখ্যায় রূপান্তর করছিল। কোডটি নেওয়া হয়েছিল পুরোনো Ariane 4 থেকে, যেখানে মানটি কখনো এত বড় হতো না। কিন্তু নতুন রকেট অনেক দ্রুত, মান ৩২,৭৬৭ ছাড়িয়ে গেল, রূপান্তর ব্যর্থ হলো — আর ব্যাকআপ কম্পিউটারেও হুবহু একই কোড ছিল, তাই সেটিও একইভাবে থেমে গেল।
Gangnam Style ও YouTube (২০১৪): YouTube-এর ভিউ কাউন্টার ছিল ৩২ বিটের signed সংখ্যা — সর্বোচ্চ ২,১৪৭,৪৮৩,৬৪৭। কেউ ভাবেনি একটি ভিডিও দুশো কোটির বেশিবার দেখা হবে। ২০১৪ সালের ডিসেম্বরে PSY-এর “Gangnam Style” সেই সীমার কাছে পৌঁছালে Google কাউন্টারটি ৬৪ বিটে বদলে নেয়। নতুন সীমা ১৯ অঙ্কের একটি সংখ্যা — নিকট ভবিষ্যতে সেখানে পৌঁছানোর কোনো সম্ভাবনা নেই।
২০৩৮ সালের সমস্যা: বহু সিস্টেম সময় রাখে “১ জানুয়ারি ১৯৭০ থেকে কত সেকেন্ড পেরিয়েছে” হিসেবে। এটি ৩২ বিটের signed সংখ্যায় রাখলে সর্বোচ্চ সীমায় পৌঁছাবে ১৯ জানুয়ারি ২০৩৮, UTC সময় রাত ৩টা ১৪ মিনিট ৭ সেকেন্ডে। এক সেকেন্ড পরেই সংখ্যাটি ঘুরে সবচেয়ে ছোট ঋণাত্মক মানে চলে যাবে — যন্ত্র ভাববে তারিখটা ১৯০১ সালের ডিসেম্বর!
লক্ষ করুন, তিনটি ঘটনাতেই কোডে কোনো “ব্যাকরণগত ভুল” ছিল না। Compiler খুশিমনে compile করেছে, পরীক্ষায়ও সব ঠিক চলেছে। ভুলটা ছিল একটি ধারণায় — “মানটা কখনো এত বড় হবে না।”
C-তে overflow-এর দুই রকম নিয়ম
unsigned — নিয়মমাফিক ঘুরে আসে
unsigned সংখ্যা সীমা পেরোলে ঘড়ির কাঁটার মতো আবার ০ থেকে শুরু হয়। এই wraparound C-র নিয়মে লেখা আছে, তাই ফল সবসময় নিশ্চিত: ৮ বিটে ২৫৫ + ১ = ০।
ফল নির্দিষ্টsigned — নিয়মের বাইরে
signed সংখ্যার overflow C-তে undefined behavior — অর্থাৎ ভাষার নিয়ম কোনো ফলের নিশ্চয়তা দেয় না। বেশিরভাগ সময় -128-এর মতো ঘুরে আসা মান দেখবেন, কিন্তু optimization চালু থাকলে compiler আরও অদ্ভুত কিছু করতে পারে।
কখনো নির্ভর করবেন নাএবার নিজে চালিয়ে দেখুন। নিচের প্রোগ্রামটি দেখাবে আপনার কম্পিউটারে কোন টাইপ কত বাইট, int-এর সর্বোচ্চ মান কত, আর ৮ বিটের unsigned ঘরে ২৫৫ + ১ কী হয়। শেষ লাইনে দেখবে -1-এর আসল বিটগুলো:
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("sizeof(char) = %zu byte\n", sizeof(char));
printf("sizeof(int) = %zu bytes\n", sizeof(int));
printf("sizeof(long long) = %zu bytes\n", sizeof(long long));
printf("INT_MAX = %d\n", INT_MAX);
unsigned char u = 255; /* 8 bits: 1111 1111 */
u = u + 1; /* unsigned: wraps to 0 */
printf("255 + 1 in 8 bits = %d\n", u);
unsigned char m = (unsigned char)-1; /* raw bits of -1 */
printf("-1 as raw 8 bits = %d (0x%X)\n", m, m);
return 0;
}sizeof বলে দেয় একটি টাইপ মেমরিতে কত বাইট নেয়। সাধারণ কম্পিউটারে int ৪ বাইট (৩২ বিট), তাই INT_MAX = 2147483647 — YouTube-এর সেই সংখ্যাটিই! u ২৫৫ থেকে এক বাড়তেই ৮ বিটে আর জায়গা থাকে না, মান ঘুরে ০ হয়ে যায়। আর -1-কে unsigned ৮ বিট হিসেবে পড়লে পাই 255, অর্থাৎ 0xFF = 1111 1111 — two's complement-এর ভবিষ্যদ্বাণী মিলে গেল।
“int সবসময় ৩২ বিট” — এই ধারণাটি ভুল। C-র নিয়ম শুধু বলে int অন্তত ১৬ বিট হবে। Arduino Uno-র মতো ছোট মাইক্রোকন্ট্রোলারে int সত্যিই ১৬ বিট — সেখানে ৩২,৭৬৭-এর পরেই overflow. আবার long ৬৪ বিটের Linux-এ ৮ বাইট, কিন্তু ৬৪ বিটের Windows-এ মাত্র ৪ বাইট। নির্দিষ্ট মাপ দরকার হলে পরের পর্বে দেখানো int32_t, int64_t-এর মতো টাইপ ব্যবহার করুন।
৭ · দশমিক সংখ্যা — floating point কেন পুরোপুরি নির্ভুল নয়
পূর্ণসংখ্যা তো হলো। কিন্তু ৩.১৪, ০.৫ বা ৯৯.৯৯ টাকা? দশমিক সংখ্যা রাখার জন্য কম্পিউটার বৈজ্ঞানিক নোটেশনের মতো একটি কৌশল ব্যবহার করে। পদার্থবিজ্ঞানে যেমন আলোর বেগ লেখেন ৩ × ১০৮ — একটি অঙ্কের অংশ আর একটি ঘাত — কম্পিউটারও তেমনি সংখ্যাকে রাখে “অঙ্কের সারি × ২-এর ঘাত” হিসেবে। দশমিক বিন্দুটি ঘাত অনুযায়ী সামনে-পেছনে “ভেসে” বেড়ায়, তাই নাম floating point.
এর নিয়ম ঠিক করা আছে IEEE 754 নামের একটি আন্তর্জাতিক মানে, যা ১৯৮৫ সালে প্রকাশিত হয়। এর প্রধান কারিগর ছিলেন অধ্যাপক William Kahan, যিনি এই কাজের জন্য ১৯৮৯ সালে Turing Award পান। C-র float হলো ৩২ বিট (১টি চিহ্ন-বিট, ৮টি ঘাতের বিট, ২৩টি অঙ্কের বিট), আর double হলো ৬৪ বিট।
| টাইপ | মাপ | মোটামুটি নির্ভুল অঙ্ক | কখন ব্যবহার করবেন |
|---|---|---|---|
float | ৪ বাইট | প্রায় ৭টি দশমিক অঙ্ক | গেমের গ্রাফিক্স, সেন্সরের মান — যেখানে মেমরি বাঁচানো জরুরি |
double | ৮ বাইট | প্রায় ১৫–১৬টি দশমিক অঙ্ক | বৈজ্ঞানিক হিসাব ও বেশিরভাগ সাধারণ কাজ |
এই “কত অঙ্ক নির্ভুল” মাপটির নাম precision. সমস্যা হলো, সীমিত বিটে অসীম সংখ্যক দশমিক সংখ্যা রাখা অসম্ভব। তাই বেশিরভাগ দশমিক সংখ্যা হুবহু থাকে না, থাকে সবচেয়ে কাছাকাছি যে মান বিটে লেখা যায় সেটি। সবচেয়ে চমকপ্রদ উদাহরণ ০.১ নিজেই।
০.১ কেন বাইনারিতে শেষ হয় না
দশমিকে ১/৩ লিখতে গেলে পান ০.৩৩৩৩… — অসীম পর্যন্ত চলে, কারণ ১০-কে ৩ দিয়ে ভাগ করা যায় না। বাইনারিতে ঠিক একই সমস্যা হয় ১/১০-এর সাথে, কারণ ১০-এর মধ্যে ৫ আছে, আর ২-এর ঘাত দিয়ে ৫ ভাগ হয় না। দশমিক অংশকে বাইনারিতে আনার নিয়ম হলো: বারবার ২ দিয়ে গুণ করুন, পূর্ণ অংশটি (০ বা ১) পরের বিট, বাকি ভগ্নাংশ নিয়ে আবার গুণ। দেখুন:
| হাতে থাকা ভগ্নাংশ | |
| ২ দিয়ে গুণ | |
| পাওয়া বিট |
- ০.১ × ২ = ০.২. পূর্ণ অংশ ০, তাই দশমিকের পরের প্রথম বিট ০। হাতে থাকল ০.২।
- ০.২ × ২ = ০.৪. আবার পূর্ণ অংশ ০ — দ্বিতীয় বিট ০। হাতে ০.৪।
- ০.৪ × ২ = ০.৮. এখনো ১-এর নিচে, তৃতীয় বিটও ০।
- ০.৮ × ২ = ১.৬. এবার পূর্ণ অংশ ১ — চতুর্থ বিট ১। পূর্ণ অংশ ফেলে হাতে রাখি ০.৬।
- ০.৬ × ২ = ১.২. পঞ্চম বিটও ১। হাতে থাকল ০.২ — কিন্তু দাঁড়ান, ০.২ তো ধাপ ২-এ আমরা আগেই দেখেছি!
- চক্র শুরু — শেষ আর হবে না। ০.২ থেকে আবার ০.৪, ০.৮, ১.৬, ১.২, ০.২… একই চারটি বিট
0011চিরকাল ঘুরবে:0.000110011001100…. কিন্তুdouble-এ অঙ্কের জন্য জায়গা মাত্র ৫২টি বিট। তাই কোথাও কেটে গোল করতে হয় — আর তখনই ০.১ হয়ে যায় “০.১-এর খুব কাছাকাছি একটি সংখ্যা”, হুবহু ০.১ নয়।
এই ক্ষুদ্র পার্থক্য সাধারণত চোখে পড়ে না। কিন্তু দুটি আনুমানিক মান যোগ করলে ভুলগুলোও যোগ হয়। চালিয়ে দেখুন — %.17f মানে দশমিকের পর ১৭টি অঙ্ক পর্যন্ত দেখাও:
#include <stdio.h>
int main(void) {
double a = 0.1, b = 0.2;
printf("0.1 + 0.2 = %.17f\n", a + b);
printf("0.3 = %.17f\n", 0.3);
printf("Equal? %s\n", (a + b == 0.3) ? "yes" : "NO");
/* Money the safe way: count poisha (1 taka = 100 poisha) */
long long p1 = 10, p2 = 20; /* 0.10 tk, 0.20 tk */
long long total = p1 + p2;
printf("Total = %lld.%02lld taka\n", total / 100, total % 100);
return 0;
}প্রথম লাইনে আসে 0.30000000000000004, দ্বিতীয় লাইনে 0.29999999999999999 — দুটিই ০.৩-এর কাছাকাছি, কিন্তু একই নয়। তাই == দিয়ে তুলনা বলছে NO. অথচ পয়সায় গুনলে ১০ + ২০ = ৩০ একটি নিখুঁত পূর্ণসংখ্যা — শেষ লাইনে হুবহু 0.30 taka. শিক্ষা: দশমিক সংখ্যা কখনো == দিয়ে তুলনা করবেন না, আর টাকা কখনো float/double-এ রাখবেন না।
লাখ লাখ লেনদেনে প্রতিবার এক পয়সার ভগ্নাংশ ভুল হলেও শেষে হিসাব মেলে না। তাই পেশাদার আর্থিক সফটওয়্যারে ভালো চর্চা হলো টাকাকে সবচেয়ে ছোট এককে — পয়সায় — পূর্ণসংখ্যা হিসেবে রাখা (৳১২৫.৫০ মানে ১২৫৫০ পয়সা), অথবা বিশেষ দশমিক টাইপ (যেমন ডেটাবেসের DECIMAL) ব্যবহার করা। bKash বা ব্যাংকের মতো যেকোনো লেনদেন-ব্যবস্থার জন্য এটাই নিরাপদ পথ।
৮ · লেখা — ASCII থেকে Unicode আর UTF-8
সংখ্যা তো বিটে রাখা গেল। কিন্তু লেখা? কৌশলটি সহজ: প্রতিটি অক্ষরকে একটি নম্বর দিন, তারপর নম্বরটি বিটে রাখুন। কোন অক্ষর কোন নম্বর পাবে, সেটা সবাইকে একমত হতে হবে — সেই চুক্তির তালিকাই হলো অক্ষরের মান।
ASCII — ইংরেজির জন্য ১২৮টি ঘর
১৯৬৩ সালে আমেরিকায় প্রকাশিত হয় ASCII — ৭ বিটের একটি তালিকা, মোট ১২৮টি ঘর। এতে আছে ইংরেজি বড় ও ছোট হাতের অক্ষর, ০–৯ অঙ্ক, যতিচিহ্ন আর কিছু নিয়ন্ত্রণ-চিহ্ন (যেমন নতুন লাইন)। কয়েকটি মনে রাখার মতো:
| অক্ষর | দশমিক | Hex | লক্ষ করুন |
|---|---|---|---|
'A' | 65 | 0x41 | এরপর B = 66, C = 67 … ক্রমানুসারে |
'a' | 97 | 0x61 | ছোট হাতের অক্ষর বড়টির চেয়ে ঠিক ৩২ বেশি |
'0' | 48 | 0x30 | অক্ষর '0' আর সংখ্যা 0 এক জিনিস নয়! |
' ' (ফাঁকা) | 32 | 0x20 | ফাঁকা জায়গাও একটি অক্ষর |
'\n' | 10 | 0x0A | নতুন লাইন — চোখে দেখা যায় না |
এই কারণেই C-তে 'A' + 1 লিখলে পান 66, অর্থাৎ 'B'. C-র char আসলে একটি ছোট সংখ্যা মাত্র — প্রথম অংশের সেই “একই বিট, ভিন্ন অর্থ” কথাটির সরাসরি উদাহরণ।
Unicode — পৃথিবীর সব লেখার জন্য একটি তালিকা
১২৮টি ঘরে বাংলা, আরবি, হিন্দি, চীনা — কিছুই আঁটে না। একসময় প্রতিটি দেশ নিজের নিজের তালিকা বানাত, ফলে একটি দেশের ফাইল আরেক দেশে খুললে আবর্জনা দেখাত। এ সমস্যা মেটাতে তৈরি হয় Unicode — পৃথিবীর প্রায় সব লিপির প্রতিটি অক্ষরকে একটি স্থায়ী নম্বর দেওয়ার উদ্যোগ। এই নম্বরকে বলে code point, লেখা হয় U+ দিয়ে hex-এ। বাংলা লিপি পেয়েছে U+0980 থেকে U+09FF পর্যন্ত একটি ব্লক।
| অক্ষর | Code point | UTF-8 বাইট | কত বাইট |
|---|---|---|---|
| A | U+0041 | 41 | ১ |
| অ | U+0985 | E0 A6 85 | ৩ |
| ক | U+0995 | E0 A6 95 | ৩ |
| ৳ | U+09F3 | E0 A7 B3 | ৩ |
| 😀 | U+1F600 | F0 9F 98 80 | ৪ |
UTF-8 — নম্বরকে বাইটে বদলানোর নিয়ম
Code point হলো শুধু একটি নম্বর। সেটিকে ফাইলে বা নেটওয়ার্কে পাঠাতে হলে বাইটে বদলাতে হয় — এই বদলের নিয়মকে বলে encoding. আজকের ইন্টারনেটের প্রায় সব ওয়েবসাইট যে encoding ব্যবহার করে তার নাম UTF-8. এর মূল বুদ্ধি: ছোট নম্বরের জন্য কম বাইট, বড় নম্বরের জন্য বেশি বাইট। ইংরেজি অক্ষর ১ বাইট (হুবহু ASCII-র মতো), বাংলা ৩ বাইট, ইমোজি ৪ বাইট। নিচে দেখুন 'অ' কীভাবে ৩টি বাইট হয়:
- অক্ষর
U+0985Unicode নম্বর - বাইনারি
16 বিটপ্রতি hex অঙ্ক = ৪ বিট - ছাঁচ বাছাই
3 বাইটনম্বরের আকার দেখে - বিট বসানো
4 + 6 + 6ফাঁকা ঘর পূরণ - বাইট
E0 A6 85ফাইলে যা থাকে
অক্ষর: অ নাম: BENGALI LETTER A code point: U+0985 (দশমিকে 2437)
0 9 8 5
0000 1001 1000 0101
0985 = 0000 1001 1000 0101
U+0000 – U+007F → 0xxxxxxx U+0080 – U+07FF → 110xxxxx 10xxxxxx U+0800 – U+FFFF → 1110xxxx 10xxxxxx 10xxxxxx ← 0985 এখানে U+10000 – U+10FFFF → 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
বিট: 0000 | 100110 | 000101 ছাঁচ: 1110xxxx 10xxxxxx 10xxxxxx ফল: 11100000 10100110 10000101
1110 0000 → E0 1010 0110 → A6 1000 0101 → 85 "অ" = E0 A6 85 (৩ বাইট) "A" = 41 (১ বাইট)
- অক্ষর থেকে নম্বর। Unicode তালিকায় বাংলা স্বরবর্ণ 'অ'-এর নম্বর
U+0985, দশমিকে ২৪৩৭। পৃথিবীর যেকোনো কম্পিউটারে, যেকোনো ভাষার সফটওয়্যারে এই নম্বর একই। - নম্বর থেকে বিট। Hex-এর সুবিধা এখানেই — প্রতিটি অঙ্ককে আলাদা করে ৪ বিটে লিখলেই হলো: 0 → 0000, 9 → 1001, 8 → 1000, 5 → 0101. মোট ১৬টি বিট।
- ছাঁচ বাছাই। UTF-8 নম্বরের আকার দেখে ঠিক করে কত বাইট লাগবে।
0985পড়েছে0800থেকেFFFF-এর মধ্যে, তাই ৩ বাইটের ছাঁচ: প্রথম বাইট শুরু হয়1110দিয়ে (“আমার পরে আরও ২টি বাইট আসছে”), বাকি দুটি শুরু হয়10দিয়ে (“আমি আগের অক্ষরের অংশ”)। ফাঁকা x-ঘর আছে ৪ + ৬ + ৬ = ১৬টি — আমাদের বিটের সংখ্যার সমান। - ফাঁকা ঘরে বিট বসানো। ১৬টি বিটকে বাঁ থেকে ৪, ৬, ৬ করে ভাগ করুন:
0000|100110|000101. তারপর ক্রমানুসারে ছাঁচের x-গুলোর জায়গায় বসিয়ে দিন। লাল অংশগুলোই আমাদের আসল তথ্য, বাকিটা UTF-8-এর “ঠিকানা লেবেল”। - তিনটি বাইট তৈরি। প্রতিটি বাইটকে hex-এ লিখলে পাই
E0 A6 85. কোনো বাংলা ওয়েবপেজ বা ফাইলে 'অ' লিখলে ভেতরে আসলে এই তিনটি বাইটই থাকে। তুলনায় 'A' মাত্র একটি বাইট41— হুবহু ASCII-র মতো।
এর একটি সরাসরি প্রভাব C-তে: strlen("অ") দেয় 3, কারণ strlen অক্ষর নয়, বাইট গোনে। “আমি” শব্দে চোখে দেখা অক্ষর দুটি, Unicode code point তিনটি (আ, ম, ি), আর বাইট নয়টি। বাংলা নাম কেটে ছোট করতে গিয়ে বাইটের মাঝখান থেকে কাটলে ভাঙা অক্ষর তৈরি হয়।
লেখা ভেঙে আবর্জনা — কেন হয়
কখনো কোনো ওয়েবসাইট বা পুরোনো সফটওয়্যারে বাংলার জায়গায় অ বা ইমোজির জায়গায় 😀 দেখেছেন? একে বলে mojibake. বাইটগুলো ঠিকই আছে — শুধু ভুল নিয়মে পড়া হয়েছে। 'অ'-এর তিনটি বাইট E0 A6 85 যদি পুরোনো ইউরোপীয় encoding (Windows-1252) দিয়ে পড়া হয়, তাহলে প্রতিটি বাইটকে একটি আলাদা অক্ষর ধরে নেয়: E0 = à, A6 = ¦, 85 = … — ফলে দেখা যায় অ. আবারও সেই মূল কথা: বিট একই, পড়ার নিয়ম ভুল।
সমাধান সবসময় একটাই — লেখা আর পড়া, দুই জায়গাতেই একই encoding ব্যবহার করুন, আর সম্ভব হলে সর্বত্র UTF-8. HTML পাতায় এজন্যই প্রথম লাইনগুলোর একটি হয় <meta charset="UTF-8">. এই পাতাটিতেও আছে।
বাইটের ক্রম — endianness
শেষ একটি খুঁটিনাটি। ৪ বাইটের একটি সংখ্যা, যেমন 0x12345678, মেমরির চারটি ঘরে কোন ক্রমে বসবে? দুই রকম প্রথা আছে, আর এই বিষয়টির নাম endianness:
Big-endian
বড় দিকের বাইট আগে: 12 34 56 78 — আমরা যেভাবে লিখি। নেটওয়ার্কে ডেটা পাঠানোর প্রচলিত নিয়ম এটি।
Little-endian
ছোট দিকের বাইট আগে: 78 56 34 12 — উল্টো দেখালেও প্রসেসরের কিছু হিসাবে সুবিধাজনক। Intel/AMD-এর কম্পিউটার আর বেশিরভাগ ARM ফোন এভাবেই রাখে।
একই কম্পিউটারের ভেতরে এটি নিয়ে চিন্তা করতে হয় না। কিন্তু ফাইল বা নেটওয়ার্কের মাধ্যমে দুটি ভিন্ন যন্ত্র যখন বাইট আদান-প্রদান করে, তখন ক্রম না মিললে 0x12345678 পড়া হয় 0x78563412 হিসেবে। নামটি এসেছে “Gulliver's Travels” গল্প থেকে, যেখানে দুই দেশ যুদ্ধ করে ডিম বড় দিক থেকে ভাঙা উচিত নাকি ছোট দিক থেকে — তা নিয়ে।
৯ · AI-এর যুগে — ডেটার ভুল যা compiler ধরে না
এই পর্বের ভুলগুলোর একটি ভয়ংকর মিল আছে: কোনোটিতেই compile error হয় না। প্রোগ্রাম দিব্যি চলে, ছোট ইনপুটে ঠিক উত্তরও দেয়। ভুল ধরা পড়ে বড় সংখ্যায়, বাংলা লেখায়, বা লাখতম লেনদেনে। আর AI ঠিক এই জায়গাগুলোতেই সবচেয়ে বেশি অসতর্ক — কারণ তার শেখা কোডের বড় অংশই ছোট উদাহরণ, যেখানে এসব প্রশ্ন ওঠেই না।
আপনাকে ভাবতে হবে “সীমানায় কী হবে”।
AI-এর লেখা কোডে যেসব ডেটা-ভুল প্রায়ই থাকে
| AI-এর ভুল | কী সমস্যা হয় | যা চাইবেন |
|---|---|---|
টাকা রেখেছে float বা double-এ | যোগফলে পয়সার গরমিল, == তুলনা ব্যর্থ | পয়সায় পূর্ণসংখ্যা (long long) |
বড় যোগফল বা গুণফল রেখেছে int-এ | ২১৪ কোটি পেরোলেই overflow, ঋণাত্মক ফল | সর্বোচ্চ মান হিসাব করে বড় টাইপ, বা overflow যাচাই |
ধরে নিয়েছে int = ৩২ বিট, long = ৬৪ বিট | মাইক্রোকন্ট্রোলারে বা Windows-এ ভুল ফল | int32_t, int64_t ধরনের নির্দিষ্ট মাপের টাইপ |
বাংলা লেখার “দৈর্ঘ্য” মেপেছে strlen দিয়ে | অক্ষরের বদলে বাইট গোনে; কাটলে ভাঙা অক্ষর | UTF-8 বুঝে অক্ষর গোনা, বাইট নয় |
দশমিক সংখ্যা তুলনা করেছে == দিয়ে | ০.১ + ০.২ ≠ ০.৩ — শর্ত কখনো সত্য হয় না | ছোট একটি সহনশীলতা (tolerance) দিয়ে তুলনা |
AI-কে কোড লিখতে দেওয়ার সময়ই ডেটার সীমা নিয়ে প্রশ্ন জুড়ে দিন। নিচের প্রম্পটটি কপি করে নিজের কাজ অনুযায়ী বদলে নিন:
AI-এর কোড হাতে পেলে — ডেটা যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১ঋণাত্মক সংখ্যা রাখার আরও উপায় ছিল — তাহলে two's complement-ই কেন জিতল?
কম্পিউটারের শুরুর দশকগুলোতে তিনটি পদ্ধতিই আসল যন্ত্রে ব্যবহার হয়েছে:
- Sign-magnitude (একটি চিহ্ন-বিট + মান): IBM-এর পঞ্চাশের দশকের বৈজ্ঞানিক কম্পিউটারগুলো (যেমন IBM 704) এভাবে পূর্ণসংখ্যা রাখত। মানুষের জন্য পড়া সহজ, কিন্তু দুটি শূন্য, আর যোগ করার আগে চিহ্ন দেখে ঠিক করতে হয় যোগ করবে নাকি বিয়োগ।
- Ones' complement (শুধু সব বিট উল্টানো, ১ যোগ নয়): CDC 6600-এর মতো বিখ্যাত সুপারকম্পিউটার এটি ব্যবহার করত। এখানেও দুটি শূন্য (
00000000আর11111111), আর যোগের সময় শেষের হাতের বিটটি আবার শুরুতে যোগ করতে হয় — যার নাম “end-around carry” পদ্ধতি। - Two's complement: শূন্য একটাই। যোগ, বিয়োগ, এমনকি গুণের নিচের বিটগুলো — signed ও unsigned দুই রকম সংখ্যার জন্য একই সার্কিট কাজ করে। বিয়োগ মানে শুধু “উল্টে ১ যোগ করে যোগ করা”.
হার্ডওয়্যার ডিজাইনারের চোখে এটি বিশাল সুবিধা: কম transistor, কম জটিলতা, দ্রুততর সার্কিট, কম বাগ। ১৯৬৪ সালে IBM তাদের যুগান্তকারী System/360 পরিবারে two's complement বেছে নেয়, আর তারপর প্রায় সব নতুন প্রসেসর একই পথে হাঁটে। আজ Intel, AMD, ARM, RISC-V — সবাই two's complement.
মজার ব্যাপার, C ভাষার নিয়ম বহু বছর তিনটি পদ্ধতিকেই অনুমতি দিত, কারণ C তৈরির সময় পুরোনো যন্ত্রগুলো তখনো চালু ছিল। শেষ পর্যন্ত C23 মানে (আর C++20-এ) ঘোষণা করা হয় — signed পূর্ণসংখ্যা অবশ্যই two's complement হবে। বাস্তবতা যা বহু আগেই ঠিক করে দিয়েছিল, নিয়ম অবশেষে তা স্বীকার করল। তবে signed overflow এখনো undefined behavior রয়ে গেছে — কারণটা পরের প্রশ্নে।
মূল উপলব্ধি: সেরা ডিজাইন প্রায়ই সেটি, যা মানুষের কাছে প্রথমে সবচেয়ে কম স্বাভাবিক লাগে কিন্তু যন্ত্রের কাজকে সবচেয়ে সরল করে। “পড়তে সহজ” আর “বানাতে সহজ” — দুটি আলাদা জিনিস।
প্র ০২০.১ কেন বাইনারিতে হুবহু রাখা যায় না — আর তাহলে ব্যাংক বা আর্থিক সিস্টেম হিসাব মেলায় কীভাবে?
যেকোনো সংখ্যা-পদ্ধতিতে একটি ভগ্নাংশ “শেষ হয়” কেবল তখনই, যখন তার হরকে ওই পদ্ধতির ভিত্তির উৎপাদক দিয়ে গঠন করা যায়। দশমিকের ভিত্তি ১০ = ২ × ৫, তাই ১/২, ১/৪, ১/৫ শেষ হয়, কিন্তু ১/৩ হয় না। বাইনারির ভিত্তি ২, তাই শুধু যেসব ভগ্নাংশের হর ২-এর ঘাত (১/২, ১/৪, ১/৮…) সেগুলোই হুবহু থাকে। ১/১০-এর হরে ৫ আছে — তাই এটি বাইনারিতে চিরকাল চলতে থাকে, আর সীমিত বিটে কেটে গোল করতে হয়।
ক্ষুদ্র ভুল জমে কত বড় হতে পারে, তার একটি দুঃখজনক উদাহরণ ১৯৯১ সালের উপসাগরীয় যুদ্ধ। একটি Patriot ক্ষেপণাস্ত্র-প্রতিরোধ ব্যবস্থা সময় গুনত সেকেন্ডের দশ ভাগের এক ভাগে, আর ০.১-কে রাখত ২৪ বিটে — সামান্য কেটে। প্রায় ১০০ ঘণ্টা একটানা চলার পর সেই ক্ষুদ্র ভুল জমে দাঁড়ায় এক সেকেন্ডের প্রায় এক-তৃতীয়াংশে। ধেয়ে আসা একটি ক্ষেপণাস্ত্রকে ব্যবস্থাটি ভুল জায়গায় খুঁজল, থামাতে পারল না, আর ২৮ জন সৈন্য নিহত হন।
আর্থিক সিস্টেম তাই কয়েকটি কৌশল ব্যবহার করে:
- ছোট এককে পূর্ণসংখ্যা: টাকার বদলে পয়সা, ডলারের বদলে সেন্ট। পূর্ণসংখ্যার যোগ-বিয়োগে কোনো গোল করার ভুল নেই।
- দশমিক টাইপ: ডেটাবেসের
DECIMAL, Java-রBigDecimal, Python-এরdecimal— এগুলো ভেতরে দশ-ভিত্তিক অঙ্ক রাখে, তাই ০.১ হুবহু থাকে। - গোল করার লিখিত নিয়ম: সুদ বা ভাগের হিসাবে পয়সার ভগ্নাংশ আসবেই। কখন, কোন দিকে গোল হবে — সেটা আগে থেকে ঠিক করা থাকে, যাতে প্রতিটি সিস্টেম একই ফল দেয়।
মূল উপলব্ধি: floating point ভুল নয় — এটি পদার্থবিজ্ঞান, গ্রাফিক্স বা মেশিন লার্নিংয়ের জন্য চমৎকার, যেখানে “খুব কাছাকাছি” যথেষ্ট। কিন্তু যেখানে প্রতিটি পয়সা গুনতে হয়, সেখানে দরকার নিখুঁত হিসাব। কোন কাজে কোন টাইপ — এই সিদ্ধান্তটাই প্রকৌশল।
প্র ০৩signed overflow C-তে “undefined behavior” — এতে compiler কী কী করার সুযোগ পায়, আর কেন এমন নিয়ম রাখা হলো?
Undefined behavior মানে C-র নিয়ম বলে: “এমন হলে কী ঘটবে, আমি কোনো কথা দিচ্ছি না।” এর একটি গভীর ফল আছে — compiler ধরে নিতে পারে আপনার কোডে এমন কিছু কখনো ঘটবে না, আর সেই ধারণার ওপর দাঁড়িয়ে কোড বদলে দিতে পারে।
- অদ্ভুত শর্ত বাদ:
if (x + 1 < x)লিখে কেউ overflow ধরতে চাইলেন। কিন্তু compiler ভাবে — “signed overflow তো ঘটতেই পারে না, তাহলে x + 1 সবসময় x-এর চেয়ে বড়।” তাই পুরো শর্তটিকে মিথ্যা ধরে মুছে ফেলে। আপনার নিরাপত্তা-যাচাই নিঃশব্দে গায়েব! - দ্রুত লুপ:
for (int i = 0; i < n; i++)-তে compiler ধরে নিতে পারে i কখনো ঘুরে ঋণাত্মক হবে না, তাই লুপটিকে ৬৪ বিটের রেজিস্টারে চালানো বা একসাথে কয়েক ধাপ করা (vectorize) সহজ হয়। এটাই নিয়মটির মূল কারণ — পারফরম্যান্স।
ঐতিহাসিক কারণও আছে: C তৈরির সময় যন্ত্রভেদে ঋণাত্মক সংখ্যার পদ্ধতি আলাদা ছিল, কোনো কোনো যন্ত্র overflow-এ প্রোগ্রাম থামিয়েও দিত। সবার জন্য একটি ফল বাধ্যতামূলক করলে কোনো না কোনো যন্ত্রে C ধীর হয়ে যেত।
পেশাদাররা কীভাবে সামলান:
- যোগের আগে যাচাই:
if (x > INT_MAX - y)— এতে নিজে কখনো overflow ঘটে না। - GCC/Clang-এর
__builtin_add_overflow, আর C23-এ নতুন<stdckdint.h>-এরckd_add— যোগ করে, সাথে জানায় overflow হলো কি না। - পরীক্ষার সময়
-fsanitize=undefined— চলার সময় overflow হাতেনাতে ধরে; এটি পর্ব ০৩-এ দেখা সেই sanitizer-এরই একটি রূপ। -fwrapvflag — GCC-কে বলে signed overflow-কেও নিয়মমাফিক ঘুরে আসা ধরে নিতে। Linux kernel-এর মতো কিছু বড় প্রজেক্ট এই ধরনের flag দিয়েই compile হয়।
মূল উপলব্ধি: C আপনাকে গতি দেয়, বিনিময়ে দায়িত্ব নেয় না। “আমার কম্পিউটারে তো ঠিক চলছে” — undefined behavior-এর ক্ষেত্রে এই বাক্যটির কোনো মূল্য নেই, কারণ পরের compiler version বা -O2 flag পুরো আচরণ বদলে দিতে পারে।
প্র ০৪UTF-8 কেন পুরো ওয়েবের encoding হয়ে উঠল? এর নকশায় এমন কী ছিল?
১৯৯২ সালের সেপ্টেম্বরে Bell Labs-এর Ken Thompson আর Rob Pike নিউ জার্সির একটি রেস্তোরাঁয় বসে খাবারের টেবিলের কাগজে UTF-8-এর নকশা আঁকেন — তাঁদের Plan 9 অপারেটিং সিস্টেমের জন্য। (Ken Thompson Unix-এরও সহ-স্রষ্টা; পর্ব ০৩-এ তাঁর “Trusting Trust” ভাষণের কথা পড়েছেন।) কয়েক দিনের মধ্যেই সেটি চালু হয়ে যায়। নকশাটির কয়েকটি গুণ একে অপ্রতিরোধ্য করে তোলে:
- ASCII-র সাথে পুরোপুরি মানানসই: প্রতিটি পুরোনো ইংরেজি ASCII ফাইল কোনো পরিবর্তন ছাড়াই বৈধ UTF-8. কোটি কোটি পুরোনো ফাইল আর প্রোগ্রাম রাতারাতি “Unicode-সমর্থিত” হয়ে গেল।
- শূন্য বাইট নেই: বাংলা বা অন্য অক্ষরের বাইটে কখনো
00আসে না। C-র স্ট্রিং শেষ হয় শূন্য বাইটে, তাই পুরোনো C ফাংশনগুলো (strcpy,printf) UTF-8 লেখা নিয়ে ভাঙে না। - নিজেই নিজেকে চেনায়: প্রথম বাইট শুরু হয়
0,110,1110বা11110দিয়ে, বাকিগুলো10দিয়ে। তাই লেখার মাঝখান থেকে পড়া শুরু করলেও পরের অক্ষরের শুরু খুঁজে পাওয়া যায়; একটি বাইট নষ্ট হলে শুধু একটি অক্ষর ভাঙে। - বাইটের ক্রমের ঝামেলা নেই: একক এক বাইট, তাই endianness-এর প্রশ্নই ওঠে না।
বিকল্পটির কী হলো? নব্বইয়ের দশকে ধারণা ছিল ৬৫,৫৩৬টি ঘরেই পৃথিবীর সব অক্ষর আঁটবে, তাই Windows, Java আর JavaScript প্রতি অক্ষর ২ বাইটের পদ্ধতি বেছে নেয় (যা পরে UTF-16 হয়)। পরে ঘর কম পড়ায় জটিলতা বাড়ে, আর আজও এই সিস্টেমগুলোর ভেতরে তার ছাপ আছে। ওয়েবে অবশ্য UTF-8 প্রায় একচ্ছত্র।
বাংলার জন্য একটি দাম আছে: UTF-8-এ বাংলা অক্ষর ৩ বাইট, UTF-16-এ ২ বাইট। শুধু বাংলা লেখার জন্য UTF-8 একটু বেশি জায়গা নেয়। কিন্তু বাস্তবের ওয়েবপেজে HTML ট্যাগ, লিংক আর কোড প্রায় সবই ASCII, তাই সব মিলিয়ে ক্ষতি সামান্য, আর লাভ বিশাল।
মূল উপলব্ধি: নতুন প্রযুক্তি জেতে তখনই, যখন পুরোনোকে না ভেঙে তার পাশে জায়গা করে নেয়। Backward compatibility — পুরোনোর সাথে মানানসই থাকা — প্রকৌশলের সবচেয়ে কম আলোচিত কিন্তু সবচেয়ে শক্তিশালী গুণগুলোর একটি।
প্র ০৫২০৩৮ সালের সমস্যা তো অনেক দূরে — এখনই এটা নিয়ে ভাবতে হবে কেন, বিশেষ করে embedded যন্ত্রের ক্ষেত্রে?
কারণ যে যন্ত্র আজ তৈরি হচ্ছে, তার অনেকগুলো ২০৩৮ সালেও চলবে। ল্যাপটপ বা ফোন কয়েক বছরে বদলায়, কিন্তু embedded যন্ত্র — অর্থাৎ অন্য যন্ত্রের ভেতরে বসানো ছোট কম্পিউটার — দশকের পর দশক চলে: কারখানার নিয়ন্ত্রক, বিদ্যুৎ গ্রিডের মিটার, লিফট, গাড়ির ইলেকট্রনিক্স, হাসপাতালের যন্ত্র, রাউটার, CCTV. অনেকগুলো সস্তা ৩২ বিটের প্রসেসরে চলে, আর কখনো সফটওয়্যার আপডেট পায় না।
সমস্যা আসলে ২০৩৮-এর আগেই শুরু হতে পারে। যে সফটওয়্যার ভবিষ্যতের তারিখ নিয়ে হিসাব করে — ৩০ বছরের বাড়ির ঋণ, দীর্ঘমেয়াদি বিমা, সার্টিফিকেটের মেয়াদ — সেগুলোকে ২০০৮ সাল থেকেই ২০৩৮-এর পরের তারিখ নিয়ে কাজ করতে হচ্ছে।
আগে কি এমন হয়েছে? হ্যাঁ। GPS স্যাটেলাইট সপ্তাহ গোনে ১০ বিটের একটি কাউন্টারে, যা ১০২৪ সপ্তাহ পরপর শূন্যে ফেরে। ১৯৯৯ সালে আর আবার ২০১৯ সালের এপ্রিলে এমন ঘোরার সময় কিছু পুরোনো GPS যন্ত্র ভুল তারিখ দেখাতে শুরু করে। আর ২০০০ সালের “Y2K” সমস্যায় বছর দুই অঙ্কে রাখার ফল সামলাতে বিশ্বজুড়ে বিপুল অর্থ ও শ্রম খরচ হয়েছিল।
সমাধান কী? সময় ৬৪ বিটে রাখা। ৬৪ বিটের signed সেকেন্ড-কাউন্টার প্রায় ২৯২০০ কোটি বছর চলবে — মহাবিশ্বের বর্তমান বয়সের চেয়েও অনেক বেশি। আধুনিক ৬৪ বিটের Linux, Windows আর macOS অনেক আগেই ৬৪ বিটের সময় ব্যবহার করে, আর ২০২০ সালের দিকে Linux kernel ৩২ বিটের যন্ত্রেও ৬৪ বিটের সময় ব্যবহারের ব্যবস্থা যোগ করে। কিন্তু এতে পুরোনো যন্ত্র নিজে থেকে ঠিক হয় না। ফাইল ফরম্যাট আর ডেটাবেসও ঝুঁকিতে — যেমন MySQL-এর TIMESTAMP টাইপের সীমা ঠিক ২০৩৮ সালের ১৯ জানুয়ারি।
মূল উপলব্ধি: টাইপ বাছাই আসলে ভবিষ্যৎ নিয়ে একটি অনুমান। “এই মান কত বড় হতে পারে?” প্রশ্নের উত্তর শুধু আজকের জন্য নয়, যন্ত্রটি যতদিন চলবে ততদিনের জন্য দিতে হয়। ভালো ইঞ্জিনিয়ার সময়ের মাত্রাটাও হিসাবে রাখেন।
অনুশীলন
খাতা-কলম নিয়ে বসুন। এগুলোর বেশিরভাগে কোড লিখতে হবে না — হাতে হিসাব করাই আসল কাজ।
-
হাতে রূপান্তর: (ক) ৪৫-কে ৮ বিটের বাইনারি ও hex-এ লিখুন। (খ)
11010110-কে দশমিক ও hex-এ লিখুন।(ক) ৪৫ = ৩২ + ৮ + ৪ + ১, তাই ৮ বিটে
0010 1101. চারটি করে ভাগ করে টেবিল দেখুন:0010= 2,1101= D — অর্থাৎ0x2D.(খ) যেসব ঘরে ১ আছে: ১২৮ + ৬৪ + ১৬ + ৪ + ২ = ২১৪। Hex-এ:
1101= D,0110= 6, তাই0xD6. লক্ষ করুন, hex-এ যেতে কোনো যোগ-গুণই লাগল না — এ কারণেই প্রোগ্রামাররা hex ভালোবাসেন। -
Two's complement অনুশীলন: ৮ বিটে -1 আর -128 লিখুন। তারপর বলুন, signed ৮ বিটে
11111110কোন সংখ্যা?-1: 1 =
00000001→ উল্টে11111110→ ১ যোগ করে11111111.-128:
10000000. (১২৮ নিজে ৮ বিটের signed সীমার বাইরে, তাই এখানে নিয়মটি সরাসরি খাটানো যায় না — এটিই সেই “একটি বেশি” ঋণাত্মক সংখ্যা।)11111110: বাঁয়ের বিট ১, তাই ঋণাত্মক। মান জানতে উল্টো পথে যান: উল্টে00000001, ১ যোগ করে00000010= ২। অর্থাৎ এটি -2. আরেকভাবে: unsigned হিসেবে এটি ২৫৪, আর ২৫৪ − ২৫৬ = -2. -
বাংলা শব্দের বাইট: “আমি” শব্দটি UTF-8-এ কত বাইট নেবে? সূত্র: আ =
U+0986, ম =U+09AE, ি (ই-কার) =U+09BF. আরstrlen("আমি")কত দেবে?তিনটি code point-ই
U+0800থেকেU+FFFF-এর মধ্যে, তাই প্রতিটি ৩ বাইট — মোট ৯ বাইট, আরstrlenদেবে 9. অথচ চোখে আমরা দেখি দুটি অক্ষর (“আ” আর “মি”)। কারণ বাংলায় কার-চিহ্ন (ি, া, ু…) আলাদা code point, যা আগের ব্যঞ্জনের সাথে জুড়ে দেখানো হয়। তাই বাংলা লেখায় “দৈর্ঘ্য” তিন রকম হতে পারে — বাইট, code point আর চোখে দেখা অক্ষর। কোনটি দরকার, আগে সেটা ঠিক করতে হয়। -
ছোট প্রোগ্রাম: একটি
doubleভ্যারিয়েবলে ০.১ দশবার যোগ করুন, তারপর%.17fদিয়ে ফল ছাপুন আর== 1.0দিয়ে তুলনা করুন। কী দেখলেন? একই কাজ পয়সায় (১০ পয়সা দশবার) করলে কী হয়?double sum = 0.0; long long poisha = 0; for (int i = 0; i < 10; i++) { sum = sum + 0.1; poisha = poisha + 10; } printf("%.17f %s\n", sum, sum == 1.0 ? "equal" : "NOT equal"); printf("%lld poisha\n", poisha);ফল আসে
0.99999999999999989— আর তুলনা বলেNOT equal. প্রতিবার যোগে ছোট্ট গোল করার ভুল জমেছে। অথচ পয়সার হিসাবে হুবহু ১০০ পয়সা = ১ টাকা। এক লাখ লেনদেনে এই ভুল কোথায় গিয়ে দাঁড়াবে, ভেবে দেখুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমবাংলা 'অ' থেকে ফাইলের বাইট পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- Unicode তালিকা থেকে code point বের করা —
U+0985 - code point-কে বাইনারিতে লেখা — ১৬টি বিট
- নম্বরের আকার দেখে ৩ বাইটের ছাঁচ বাছাই
- বিটগুলো ৪ + ৬ + ৬ ভাগে ছাঁচের ফাঁকা ঘরে বসানো
- তিনটি বাইট hex-এ লেখা —
E0 A6 85
৮ বিটের একটি unsigned ঘরে সবচেয়ে বড় কোন সংখ্যা রাখা যায়?
- 256
- 255
- 127
- 8
0x2F দশমিকে কত?
- 215
- 32
- 47
- 52
৮ বিটের signed (two's complement) সংখ্যা হিসেবে 11111111 কোন মান?
- -1
- 255
- -128
- -127
C-তে double দিয়ে 0.1 + 0.2 == 0.3 লিখলে কী হয়, আর কেন?
- সত্য, কারণ গণিতে ০.১ + ০.২ = ০.৩
- Compile error হয়
- প্রোগ্রাম crash করে
- মিথ্যা, কারণ এই দশমিকগুলো বাইনারিতে হুবহু রাখা যায় না
বাংলা অক্ষর 'অ' (U+0985) UTF-8-এ কত বাইট জায়গা নেয়?
- ১ বাইট
- ২ বাইট
- ৩ বাইট
- ৪ বাইট
একটি মোবাইল-ওয়ালেট অ্যাপে ব্যালান্স রাখার সবচেয়ে নিরাপদ উপায় কোনটি?
float-এ টাকা হিসেবে, যেমন 125.50long long-এ পয়সা হিসেবে, যেমন 12550double-এ টাকা হিসেবে — বেশি নির্ভুল বলে- প্রতিটি অঙ্ক আলাদা
char-এ
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- মেমরিতে সবই বিট; ৮ বিটে ১ বাইট। বিটের অর্থ ঠিক করে পড়ার নিয়ম — অর্থাৎ টাইপ।
- n বিটে ২nটি মান। ৪ বিট = ১টি hex অঙ্ক, ১ বাইট = ২টি hex অঙ্ক।
- ঋণাত্মক সংখ্যা two's complement-এ: সব বিট উল্টে ১ যোগ। সীমা পেরোলে overflow — unsigned ঘুরে আসে, signed হলে undefined behavior.
- দশমিক সংখ্যা আনুমানিক:
==দিয়ে তুলনা নয়, আর টাকা সবসময় পয়সায় পূর্ণসংখ্যা। - লেখা = অক্ষরের নম্বর। Unicode নম্বর দেয়, UTF-8 বাইট বানায় — বাংলা অক্ষর ৩ বাইট। ভুল encoding মানেই আবর্জনা লেখা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৬ · ভ্যারিয়েবল ও টাইপ — ডেটার বাক্সপরবর্তী পর্বমডিউল ২-এর শুরু — বিটগুলোকে নাম দিয়ে কাজে লাগানো।
- C প্রোগ্রামিং · কোর্স হোম৫৬টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Computer Architecture & Digital Logicগভীরে যানtransistor থেকে লজিক গেট, adder সার্কিট আর two's complement-এর হার্ডওয়্যার।
- Discrete Mathematicsপাশের ট্র্যাকসংখ্যা-পদ্ধতি, মডুলার গণিত আর বুলিয়ান যুক্তির গাণিতিক ভিত্তি।
- Digital Signal Processingপাশের ট্র্যাকশব্দ আর ছবি কীভাবে সংখ্যায় রূপ নেয় — sampling ও quantization.