এই পর্বে যা শিখবেন
- একটি number মেমরিতে কীভাবে থাকে — sign, exponent আর mantissa-র সহজ ছবি
- 0.1 + 0.2 কেন 0.3 নয়, আর দশমিক তুলনার সঠিক উপায়
- বড় ID কেন নষ্ট হয়,
Infinity,-0,NaNআর string থেকে সংখ্যা বানানোর ফাঁদ - Math-এর rounding (ঋণাত্মক সংখ্যাসহ), random-এর সীমা আর BigInt
- দোকানের কার্ট, VAT আর বিল ভাগের নির্ভুল হিসাব — এবং AI-এর লেখা হিসাবের কোড যাচাই
১ · একটিই number টাইপ — ছোট-বড়, পূর্ণ-দশমিক সবই এক ঘরে
একটি পরিচিত দৃশ্য দিয়ে শুরু করি। নাদিয়া একটি অনলাইন দোকানের কার্ট বানাচ্ছেন। তিনটি পণ্যের দাম ১২০.৪৫, ৮০.১০ আর ৪৫.৩০ টাকা। যোগফল দেখানোর লাইনে স্ক্রিনে এলো 245.85000000000002. নাদিয়া ভাবলেন ব্রাউজারে বাগ আছে। আসলে বাগ নেই — কম্পিউটার যেভাবে সংখ্যা রাখে, এটি তারই স্বাভাবিক ফল। এই পর্ব শেষে আপনি ব্যাখ্যা করতে পারবেন এই শেষের 2 কোথা থেকে এলো, আর টাকার হিসাব কীভাবে প্রতি পয়সায় নির্ভুল রাখা যায়।
C বা Java-তে সংখ্যার অনেক টাইপ — int, long, float, double. JavaScript-এ সাধারণ কাজের জন্য টাইপ মাত্র একটি: number. 7, -3, 3.14, 1e21 — সবার typeof হলো "number". ভেতরে প্রতিটি number হলো একটি floating-point সংখ্যা, ৬৪টি বিটে রাখা — যাকে বলে double precision বা সংক্ষেপে double. পর্ব ০৬-এ দেখেছেন JavaScript-এর আটটি টাইপের মধ্যে number একটি; এখানে আমরা তার ভেতরটা খুলব।
একটি number পূর্ণসংখ্যার জন্য নির্ভুল, যতক্ষণ মানটি ±(253 − 1)-এর ভেতরে থাকে। কিন্তু বেশিরভাগ দশমিক সংখ্যা সে হুবহু রাখতে পারে না — রাখে সবচেয়ে কাছের একটি মান। বিশাল পূর্ণসংখ্যার জন্য আছে আলাদা টাইপ BigInt, আর টাকার হিসাবের জন্য দরকার আলাদা সতর্কতা।
সংখ্যা লেখার নানা রূপ — literal
কোডে সরাসরি লেখা সংখ্যাকে বলে literal. একই সংখ্যা কয়েকভাবে লেখা যায়, আর লম্বা সংখ্যা পড়ার সুবিধার জন্য আছে numeric separator (_)।
| লেখা | মান | কখন কাজে লাগে |
|---|---|---|
1_000_000 | 1000000 | বড় সংখ্যা পড়তে সহজ (ES2021 থেকে); _ শুধু চোখের জন্য, মানে কোনো প্রভাব নেই |
0xff | 255 | Hexadecimal (ষোলো-ভিত্তিক) — রং, byte, বিটের কাজ |
0b1010 | 10 | Binary (দুই-ভিত্তিক) — flag আর bitmask (ES2015 থেকে) |
0o17 | 15 | Octal (আট-ভিত্তিক) — যেমন Unix ফাইল permission (ES2015 থেকে) |
1e3, 2.5e-3 | 1000, 0.0025 | Exponent রূপ — খুব বড় বা খুব ছোট সংখ্যা |
.5, 5. | 0.5, 5 | চলে, কিন্তু পড়তে বিভ্রান্তিকর — 0.5 লিখুন |
একটি পুরোনো ফাঁদ: শুরুতে 0 দিয়ে 017 লিখলে পুরোনো নিয়মে সেটি octal (মান ১৫) ধরা হতো। Strict mode-এ এটি SyntaxError. তাই octal লিখতে চাইলে সবসময় 0o17, আর দশমিক সংখ্যার শুরুতে বাড়তি শূন্য দেবেন না। আরেকটি বিষয়: 1_000 কোডে চলে, কিন্তু Number("1_000") দেয় NaN — separator শুধু source code-এর জন্য, string পড়ার নিয়মে নেই।
১৯৯৫ সালে Netscape-এ Brendan Eich খুব অল্প সময়ে JavaScript-এর প্রথম রূপ বানান। লক্ষ্য ছিল ওয়েব পাতায় ছোট ছোট script — ফর্ম যাচাই, ছোট হিসাব। নতুনদের জন্য সহজ রাখতে সংখ্যার টাইপ রাখা হয় একটিই, Java-র double-এর মতো। এই সরলতার দাম কী, আর ২০২০ সালে BigInt কেন যোগ করতে হলো — ভাবনার প্রশ্ন ২ ও ৩-এ আছে।
২ · IEEE 754-এর ভেতরে — ৬৪টি বিটের তিন ভাগ
বিজ্ঞানের বইয়ে খুব বড় বা ছোট সংখ্যা লেখা হয় এভাবে: পৃথিবী থেকে সূর্যের দূরত্ব প্রায় 1.496 × 1011 মিটার। এখানে তিনটি অংশ — চিহ্ন (+), অঙ্কগুলো (1.496) আর ঘাত (11). দশমিক বিন্দুটি ঘাতের সাথে “ভেসে” সরে যায় — এজন্যই নাম floating-point. কম্পিউটার ঠিক এই কাজটি করে, শুধু ১০-এর বদলে ২-এর ঘাতে।
একটি double-এর ৬৪টি বিট (সরল ছবি)
sign · exponent · mantissa (fraction)
- Sign
১ বিট0 = ধনাত্মক, 1 = ঋণাত্মক - Exponent
১১ বিট২-এর কত ঘাত — বিন্দু কোথায় বসবে - Mantissa
৫২ বিটআসল অঙ্কগুলো (binary-তে)
এই নকশার দুটি ফল মনে রাখুন। এক: mantissa-য় ৫২টি বিট (আর লুকানো একটি “1”) — অর্থাৎ যেকোনো সংখ্যার মাত্র ৫৩টি binary অঙ্ক রাখা যায়, দশমিকে প্রায় ১৫–১৭টি অঙ্ক। তার পরের সব অঙ্ক কেটে কাছের মানে গোল করা হয়। দুই: exponent-এর ১১ বিটের কারণে পরিসর বিশাল — প্রায় 10−308 থেকে 10308 (Number.MAX_VALUE = 1.7976931348623157e+308). পরিসর বড়, কিন্তু অঙ্কের সংখ্যা সীমিত — এই দুইয়ের টানাপোড়েনই পুরো পর্বের মূল বিষয়।
দশমিকে ১/৩ লিখতে চেষ্টা করুন: 0.3333… — কখনো শেষ হয় না। খাতায় ছয় ঘর জায়গা থাকলে লিখবেন 0.333333, আর তিনবার যোগ করলে পাবেন 0.999999, ১ নয়। Binary-তে ঠিক একই সমস্যা হয় ১/১০-এর সাথে। আমাদের কাছে 0.1 খুব “গোল” সংখ্যা, কিন্তু দুই-ভিত্তিক সংখ্যায় এটি ১/৩-এর মতোই অন্তহীন।
কোন দশমিক binary-তে হুবহু লেখা যায়? যার হর (denominator) শুধু ২-এর ঘাত — যেমন 0.5 (১/২), 0.25 (১/৪), 0.375 (৩/৮)। 0.1, 0.2, 0.3, 0.7 — এদের হরে ৫ আছে, তাই অন্তহীন। নিচের ডায়াগ্রামে ধাপে ধাপে দেখুন 0.1 + 0.2-এর আসল যাত্রা।
0.1 × 2 = 0.2 → 0 0.2 × 2 = 0.4 → 0 0.4 × 2 = 0.8 → 0 0.8 × 2 = 1.6 → 1 0.6 × 2 = 1.2 → 1 0.2 × 2 = 0.4 → 0 ← আবার 0.2! চক্র শুরু 0.1 = 0.00011 0011 0011 0011 … (চিরকাল)
লেখেন: 0.1 জমা থাকে: 0.1000000000000000055511151231257827… (0.1).toFixed(20) → "0.10000000000000000555"
0.2 জমা থাকে: 0.2000000000000000111022302462515654… (একটু বেশি) 0.3 জমা থাকে: 0.2999999999999999888977697537484345… (একটু কম)
সঠিক যোগফল: 0.30000000000000001665… নিচের double: 0.29999999999999998889… (= literal 0.3) ওপরের double: 0.30000000000000004440… ঠিক মাঝখানে → নিয়ম “ties to even” → ওপরেরটি
> 0.1 + 0.2 0.30000000000000004 > 0.1 + 0.2 === 0.3 false // দুটি আলাদা double
- 0.1 binary-তে অন্তহীন। দশমিক ভগ্নাংশকে binary-তে নিতে বারবার ২ দিয়ে গুণ করে পূর্ণ অংশটি লিখি। পাঁচ ধাপ পরে আবার 0.2 ফিরে আসে — অর্থাৎ
0011চিরকাল ঘুরতে থাকবে, ঠিক দশমিকে ১/৩-এর 3-এর মতো। - ৫৩ বিটে কাটা পড়ে। Mantissa-য় জায়গা সীমিত, তাই অন্তহীন সারিটি কেটে সবচেয়ে কাছের মানে গোল করা হয়। ফলে মেমরিতে থাকে 0.1-এর চেয়ে সামান্য বড় একটি সংখ্যা।
toFixed(20)দিয়ে এই লুকানো অঙ্কগুলো দেখা যায়। - 0.2 আর 0.3-ও আনুমানিক। 0.2 জমা থাকে একটু বেশি হয়ে, আর 0.3 একটু কম হয়ে। খেয়াল করুন — দুটি “একটু বেশি” যোগ করলে ফল “একটু কম”-এর সমান হওয়ার কোনো কারণ নেই।
- যোগফলও আবার গোল হয়। জমা থাকা দুটি মানের সঠিক যোগফল দুটি পাশাপাশি double-এর ঠিক মাঝখানে পড়ে। IEEE 754-এর ডিফল্ট নিয়ম “round half to even” তখন যার শেষ বিট জোড়, সেটি বেছে নেয় — এখানে ওপরেরটি। আর নিচেরটি হলো literal
0.3-এর মান। - Console কী দেখায়। JavaScript একটি number ছাপার সময় সবচেয়ে ছোট সেই দশমিক লেখা দেখায়, যা আবার পড়লে ঠিক ওই double-টিই ফেরত দেয়। 0.3-এর double-এর জন্য সেটি “0.3”, আর ওপরের প্রতিবেশীর জন্য “0.30000000000000004”. দুটি আলাদা double, তাই
===দেয়false.
এটি JavaScript-এর দোষ নয়। C-র double, Java-র double, Python-এর float — সবই একই IEEE 754 মানদণ্ড মানে, তাই সব জায়গায় 0.1 + 0.2 একই উত্তর দেয়। পার্থক্য শুধু এটুকু: অন্য ভাষায় দরকার হলে integer টাইপ বেছে নেওয়া যায়, JavaScript-এ সাধারণ number-ই একমাত্র পথ।
৩ · দশমিক তুলনা, Number.EPSILON আর safe integer-এর সীমা
তাহলে দুটি দশমিক সমান কি না কীভাবে দেখবেন? উত্তর: “হুবহু সমান” না দেখে দেখুন “যথেষ্ট কাছাকাছি” কি না। এর জন্য একটি ছোট সহনসীমা (tolerance) লাগে। JavaScript দেয় Number.EPSILON — 1 আর তার ঠিক পরের double-এর ব্যবধান, প্রায় 2.22e-16.
- ১-এর কাছাকাছি সংখ্যায়:
Math.abs(a - b) < Number.EPSILONযথেষ্ট। - বড় সংখ্যায়: double-এর ফাঁক সংখ্যার আকারের সাথে বাড়ে — 1000-এর কাছে ফাঁক ১-এর কাছের ফাঁকের প্রায় ৫০০ গুণ। তাই সহনসীমাকেও আকার অনুযায়ী বাড়ান:
Number.EPSILON * Math.max(1, |a|, |b|). - ব্যবসার হিসাবে: সমস্যার মাপে সহনসীমা ঠিক করুন — দূরত্ব মাপায় ১ মিলিমিটার, টাকায় আধা পয়সা। অথবা আরও ভালো: integer-এ কাজ করুন (অংশ ৭)।
253 — যেখানে পূর্ণসংখ্যাও ফসকে যায়
Mantissa-য় ৫৩ বিট — তাই 0 থেকে 253 পর্যন্ত প্রতিটি পূর্ণসংখ্যা হুবহু রাখা যায়। এর সবচেয়ে বড় “নিরাপদ” মান হলো Number.MAX_SAFE_INTEGER = 253 − 1 = 9007199254740991 (প্রায় ৯০ কোটি কোটি)। Safe integer মানে: এই সংখ্যা আর তার প্রতিবেশী দুটিও আলাদা আলাদাভাবে রাখা যায়। সীমার ওপারে কী ঘটে, দেখুন:
… 9007199254740989, 9007199254740990, 9007199254740991 Number.MAX_SAFE_INTEGER = 2**53 - 1
> 2 ** 53 9007199254740992 > Number.isSafeInteger(2 ** 53) false // নিজে ঠিক আছে, কিন্তু প্রতিবেশী নেই
double আছে: 9007199254740992, (…993 নেই), 9007199254740994 > 2 ** 53 + 1 9007199254740992 // …993 গোল হয়ে …992 > 2 ** 53 + 1 === 2 ** 53 true
> JSON.parse('{"id": 1234567890123456789}').id
1234567890123456800 // শেষের অঙ্কগুলো বদলে গেছে!
কোনো error নেই, কোনো warning নেই
{"id_str": "1234567890123456789"} // string হিসেবে পাঠান
BigInt("1234567890123456789") // হিসাব লাগলে
1234567890123456789n
- সীমার ভেতরে সব ঠিক। 253 − 1 পর্যন্ত পাশাপাশি দুটি double-এর ফাঁক 1 বা তার কম, তাই প্রতিটি পূর্ণসংখ্যা নিজের ঘর পায়। রোল নম্বর, দাম (পয়সায়), গণনা — সাধারণ সব কাজ এর অনেক নিচে।
- 253 নিজে ঠিক আছে। এটি ২-এর ঘাত, তাই হুবহু থাকে। কিন্তু
Number.isSafeIntegerবলেfalse, কারণ এখান থেকে ফাঁক বেড়ে যায় — এর পরের সংখ্যাটির আলাদা ঘর নেই। - ফাঁক এখন 2. Exponent এক ধাপ বাড়ায় mantissa-র শেষ বিটের মান হয়েছে 2. ফলে 9007199254740993 রাখার কোনো ঘর নেই; সেটি গোল হয়ে পাশের জোড় সংখ্যায় যায়। তাই
2 ** 53 + 1 === 2 ** 53সত্য — গণিতে অসম্ভব, double-এ স্বাভাবিক। আরও ওপরে ফাঁক হয় 4, 8, 16… - বাস্তবের বিপদ: বড় ID. অনেক ডেটাবেস আর সার্ভিস ৬৪-বিট integer ID ব্যবহার করে (Java-র
long, SQL-এরBIGINT). এমন ID JSON-এ সংখ্যা হিসেবে এলেJSON.parseসেটিকে number বানায় — আর শেষের অঙ্ক নীরবে বদলে যায়। ভুল ID দিয়ে ভুল অর্ডার বা ভুল গ্রাহকের তথ্য খোলার মতো বাগ এভাবেই হয়। - সমাধান: ID মানে string. ID দিয়ে কেউ যোগ-গুণ করে না, তাই সার্ভার থেকে ID string হিসেবে পাঠানোই নিরাপদ নিয়ম। সত্যিই বড় পূর্ণসংখ্যায় হিসাব লাগলে string থেকে BigInt বানান (অংশ ৬)। JSON নিয়ে বিস্তারিত পর্ব ১৯-এ।
এবার নিজে চালিয়ে দেখুন। প্রতিটি লাইনের ফল আগে মনে মনে অনুমান করুন, তারপর মিলিয়ে নিন:
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log((0.1).toFixed(20)); // আসলে কী জমা আছে
// আকার অনুযায়ী সহনসীমা দিয়ে তুলনা
const near = (a, b) =>
Math.abs(a - b) < Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b));
console.log(near(0.1 + 0.2, 0.3));
console.log(2 ** 53 + 1 === 2 ** 53);
console.log(Number.MAX_SAFE_INTEGER, Number.isSafeInteger(2 ** 53));
const api = JSON.parse('{"id": 1234567890123456789, "idStr": "1234567890123456789"}');
console.log(api.id, api.idStr);
console.log(Object.is(-0, 0), -0 === 0, 1 / -0);
console.log(Number(""), Number("12px"), parseInt("12px", 10));প্রথম তিন লাইন ধাপে ধাপে দেখা গল্পটিই নিশ্চিত করে: যোগফল 0.30000000000000004, তুলনা false, আর 0.1-এর ভেতরে লুকিয়ে আছে 0.10000000000000000555. near ফাংশন আকার অনুযায়ী সহনসীমা নেয়, তাই true দেয়। 2 ** 53 + 1 === 2 ** 53 সত্যিই true. API-র সংখ্যা-ID হয়ে গেছে 1234567890123456800, কিন্তু string-ID অক্ষত। শেষ দুই লাইনের -0 আর Number("")-এর রহস্য পরের অংশে।
৪ · বিশেষ মান — Infinity, -0, NaN আর Number.is… পরিবার
IEEE 754 শুধু সাধারণ সংখ্যা নয়, কয়েকটি বিশেষ মানও রাখে। C-তে পূর্ণসংখ্যাকে ০ দিয়ে ভাগ করলে প্রোগ্রাম crash করতে পারে; JavaScript-এ কখনো crash হয় না — বদলে আসে এই বিশেষ মানগুলো। সুবিধা হলো প্রোগ্রাম থামে না; অসুবিধা হলো ভুলটি নীরবে সামনে এগিয়ে যায়।
∞ Infinity ও -Infinity
1 / 0 দেয় Infinity, -1 / 0 দেয় -Infinity. সীমা ছাড়ালেও তাই: 1e308 * 10 → Infinity. Infinity সব সংখ্যার চেয়ে বড়, তাই সর্বনিম্ন খোঁজার শুরুর মান হিসেবে কাজে লাগে।
Math.max() → -Infinity
−0 ঋণাত্মক শূন্য
Sign বিট আলাদা বলে 0 আর -0 দুটি আলাদা বিট-রূপ। -0 === 0 হলো true, কিন্তু 1 / -0 → -Infinity. আলাদা করে চিনতে Object.is(-0, 0) → false.
Math.round(-0.4) → -0
NaN — “সংখ্যা নয়” নামের সংখ্যা
0 / 0, Math.sqrt(-1), Number("abc") দেয় NaN. এর typeof হলো "number", আর NaN === NaN হলো false — নিজের সাথেও সমান নয়।
Number.isNaN(x)
-0 কোথায় কাজে লাগে? দিক বোঝাতে — যেমন একটি গাড়ির বেগ গোল করে শূন্য হলেও সে কোন দিকে যাচ্ছিল, সেই তথ্য sign-এ থেকে যায়। আবার বিপদও আছে: তাপমাত্রা দেখানোর অ্যাপে Math.round(-0.4) থেকে আসা -0 কিছু জায়গায় “-0” হয়ে দেখা দিতে পারে। String(-0) আর JSON.stringify(-0) অবশ্য "0" দেয়। NaN আর coercion-এর মূল নিয়মগুলো পর্ব ০৬-এ আছে; এখানে শুধু যাচাইয়ের সঠিক টুলগুলো দেখি।
| যাচাই | কী বলে | উদাহরণ |
|---|---|---|
Number.isNaN(x) | x কি সত্যিই NaN মান? | Number.isNaN("abc") → false; পুরোনো isNaN("abc") → true (আগে সংখ্যায় রূপান্তর করে) |
Number.isFinite(x) | x কি সাধারণ সসীম number? | Number.isFinite("5") → false; পুরোনো isFinite("5") → true |
Number.isInteger(x) | x কি পূর্ণসংখ্যা? | 5.0 → true (5.0 আর 5 একই number), 5.5 → false |
Number.isSafeInteger(x) | পূর্ণসংখ্যা এবং নিরাপদ সীমার ভেতরে? | 2 ** 53 → false; 1e300 পূর্ণসংখ্যা হলেও নিরাপদ নয় |
Object.is(a, b) | “হুবহু একই মান” | Object.is(NaN, NaN) → true; Object.is(0, -0) → false |
ES2015-এ যুক্ত Number.isNaN আর Number.isFinite রূপান্তর করে না — যা পায় তা-ই যাচাই করে। পুরোনো global isNaN আর isFinite আগে মানটিকে সংখ্যায় রূপান্তর করে, ফলে isNaN("hello") true আর isNaN("") false দেয়। নতুন কোডে সবসময় Number. যুক্ত রূপটি লিখুন। আর array-তে NaN খুঁজতে indexOf কাজ করে না (-1), includes করে (true).
৫ · String থেকে সংখ্যা, সংখ্যা থেকে সুন্দর লেখা
ফর্মের ইনপুট, URL, CSV ফাইল, API — বাইরে থেকে আসা প্রায় সব সংখ্যাই আসলে string. তাকে number বানানোর তিনটি প্রধান পথ আছে, আর তিনটির আচরণ বেশ আলাদা।
| ইনপুট | Number(s) | parseInt(s, 10) | parseFloat(s) |
|---|---|---|---|
"42" | 42 | 42 | 42 |
" 42 " | 42 | 42 | 42 |
"12px" | NaN | 12 | 12 |
"3.99" | 3.99 | 3 | 3.99 |
"1e3" | 1000 | 1 | 1000 |
"" (খালি) | 0 ⚠ | NaN | NaN |
"0x1F" | 31 | 0 (radix 10 দিলে) | 0 |
"১২৩" (বাংলা অঙ্ক) | NaN | NaN | NaN |
Number(s)কড়া: পুরো string (দুই পাশের ফাঁকা বাদে) বৈধ সংখ্যা হতে হবে, নইলে NaN. ব্যবহারকারীর ইনপুট যাচাইয়ে এটিই সাধারণত ঠিক পছন্দ।+s(unary plus) একই কাজ করে।parseIntউদার: শুরু থেকে যতদূর অঙ্ক পায় পড়ে, বাকিটা ফেলে দেয়। CSS-এর"12px"পড়তে কাজের, কিন্তু"12abc"-কেও চুপচাপ 12 বানায়।- Radix সবসময় দিন:
parseInt(s, 10). দ্বিতীয় argument না দিলে"0x1F"-এর মতো string-কে hex ধরে নেয়।parseInt("101", 2)→ 5 — অন্য ভিত্তির সংখ্যা পড়তেও এটিই পথ। parseIntnumber-এ দেবেন না:parseInt(0.0000005)দেয়5! কারণ আগে string বানায় —"5e-7"— তারপর শুরুর5পড়ে। দশমিক কাটতেMath.truncব্যবহার করুন।
Number("") → 0 ফাঁদ: ফর্মের একটি ঘর খালি রেখে জমা দিলে Number(input.value) দেয় 0, NaN নয়। ফলে “দাম দেননি” আর “দাম শূন্য” আলাদা করা যায় না — বিনামূল্যে অর্ডার চলে যেতে পারে! খালি ঘর আগে আলাদা করে যাচাই করুন: if (s.trim() === "") …. একইভাবে Number(null) → 0, কিন্তু Number(undefined) → NaN. আর বাংলা অঙ্ক (১২৩) কোনো পদ্ধতিই সরাসরি পড়ে না — আগে ইংরেজি অঙ্কে বদলাতে হয় (পর্ব ০৯-এর Unicode অংশ দেখুন)।
সংখ্যা থেকে লেখা — toFixed, toPrecision আর Intl
toFixed(n): দশমিকের পরে ঠিক n অঙ্ক।(1234.5678).toFixed(2)→"1234.57". মনে রাখুন, ফল string — এর সাথে+করলে যোগ নয়, জোড়া লাগে (পর্ব ০৮)।toPrecision(n): মোট n টি অর্থবহ অঙ্ক।(123.456).toPrecision(4)→"123.5",(123456).toPrecision(2)→"1.2e+5". বৈজ্ঞানিক মাপজোখে কাজের।Intl.NumberFormat: ভাষা ও দেশের নিয়মে সাজানো।"bn-BD"locale দিলে বাংলা অঙ্ক আর লাখ-কোটির কমা: 1234567.5 হয় ১২,৩৪,৫৬৭.৫; মুদ্রা চাইলে ১২,৩৪,৫৬৭.৫০৳.toLocaleString("bn-BD")একই কাজের ছোট রূপ। তারিখসহ Intl-এর পুরো ব্যবহার পর্ব ২০-এ।
কিন্তু toFixed-এর গোল করা মাঝে মাঝে অবাক করে: (1.005).toFixed(2) দেয় "1.00", "1.01" নয়! কেন — আর অন্য পদ্ধতিগুলো কী দেয় — দেখুন:
> (1.005).toFixed(20)
"1.00499999999999989342"
> (1.005).toFixed(2) "1.00" // 1.00499… → নিচে
> 1.005 * 100
100.49999999999999
> Math.round(1.005 * 100) / 100
1
> new Intl.NumberFormat("en", { maximumFractionDigits: 2 }).format(1.005)
"1.01"
100.5 পয়সা → নিয়ম লিখে নিন: Math.round → 101 পয়সা
দেখানোর সময় শুধু format, হিসাব নয়
- শুরুতেই 1.005 নেই। 0.1-এর মতোই 1.005 binary-তে অন্তহীন, তাই জমা থাকে 1.00499999…—আসল 1.005-এর চেয়ে সামান্য কম। বাকি সব গল্প এখান থেকেই।
toFixedসৎ, কিন্তু অপ্রত্যাশিত।toFixedজমা থাকা সঠিক মানটি দেখে গোল করে। 1.00499… দুই দশমিকে নিচের দিকে যায়, তাই"1.00". একই কারণে(1.255).toFixed(2)→"1.25", অথচ(8.345).toFixed(2)→"8.35"— কোনটি কোন দিকে যাবে, দেখে বলা যায় না।- ১০০ দিয়ে গুণ করলেও রক্ষা নেই। AI প্রায়ই লেখে
Math.round(x * 100) / 100. কিন্তু1.005 * 100নিজেই100.49999999999999, তাই ফল আবার নিচে —1. গুণ করার মুহূর্তেও floating-point ভুল ঢুকতে পারে। - Intl অন্যভাবে ভাবে। আধুনিক মানদণ্ডে
Intl.NumberFormatএকটি number-কে আগে তার সবচেয়ে ছোট দশমিক লেখায় (“1.005”) নেয়, তারপর সেটি গোল করে — তাই"1.01". দেখানোর জন্য এটি মানুষের প্রত্যাশার কাছাকাছি, কিন্তু হিসাবের যুক্তি এর ওপর দাঁড় করাবেন না। - আসল সমাধান। টাকার মতো জায়গায় সিদ্ধান্ত নিন integer-এ: হিসাব পয়সায় করুন, যেখানে গোল করতে হবে সেখানে নির্দিষ্ট নিয়মে (
Math.round, বা নীতি অনুযায়ী অন্য) পূর্ণ পয়সায় আনুন।toFixedআর Intl শুধু শেষে দেখানোর কাজে।
৬ · Math — গোল করা, বড়-ছোট, ঘাত আর random
Math একটি built-in object — ক্লাস নয়, new Math() লেখা যায় না। এর ভেতরে গণিতের প্রস্তুত function আর ধ্রুবক (Math.PI, Math.E). সবচেয়ে বেশি ভুল হয় চারটি গোল করার function নিয়ে, বিশেষ করে ঋণাত্মক সংখ্যায়।
| function | নিয়ম | 2.5 | -2.5 | -2.6 |
|---|---|---|---|---|
Math.round | কাছের পূর্ণসংখ্যা; ঠিক .5 হলে ওপরের দিকে (+∞-এর দিকে) | 3 | -2 | -3 |
Math.floor | সবসময় নিচে (−∞-এর দিকে) | 2 | -3 | -3 |
Math.ceil | সবসময় ওপরে (+∞-এর দিকে) | 3 | -2 | -2 |
Math.trunc | দশমিক অংশ কেটে ফেলা (০-এর দিকে) | 2 | -2 | -2 |
খেয়াল করুন Math.round(-2.5) → -2, কারণ JavaScript-এ .5 সবসময় +∞-এর দিকে যায়। স্কুলের নিয়মে (শূন্য থেকে দূরে) উত্তর হতো -3. আবার (-1.5).toFixed(0) দেয় "-2" — একই ভাষায় দুই নিয়ম! ঋণাত্মক টাকা (ফেরত, ছাড়) গোল করার আগে নিয়মটি নিশ্চিত করুন।
বাকি কাজের টুল
Math.abs(x)— পরম মান। দুটি মানের ব্যবধান মাপতে:Math.abs(a - b).Math.max/Math.min— অনেকগুলো argument নেয়, array নেয় না। Array থেকে নিতে spread:Math.max(...marks)(পর্ব ১৭)। খালি হলেMath.max()→-Infinity. লাখ লাখ উপাদানের array spread করলে argument-এর সীমা ছাড়িয়ে RangeError হতে পারে — তখন লুপ বাreduce.Math.pow(2, 10)বা2 ** 10— ঘাত (**ES2016 থেকে)।Math.sqrtবর্গমূল,Math.cbrtঘনমূল;Math.sqrt(-1)→ NaN.Math.hypot(3, 4)→ 5 — দুটি বিন্দুর দূরত্ব; ভেতরে বড় সংখ্যার বর্গ করেও overflow এড়ায়।Math.sign(x)— 1, -1, 0 বা -0: শুধু চিহ্নটুকু।
Integer ভাগ আর ভাগশেষ
JavaScript-এ / সবসময় দশমিক ভাগ: 7 / 2 → 3.5. পূর্ণ ভাগফল চাইলে Math.trunc(7 / 2) → 3, আর ভাগশেষ 7 % 2 → 1. ঋণাত্মকে সাবধান: -7 % 3 → -1 (চিহ্ন আসে ভাজ্য থেকে), আর Math.floor(-7 / 3) → -3 কিন্তু Math.trunc(-7 / 3) → -2. ঘড়ির মতো “ঘুরে আসা” ভাগশেষ লাগলে লিখুন ((n % m) + m) % m.
Math.random — খেলার জন্য, নিরাপত্তার জন্য নয়
Math.random() 0 (সহ) থেকে 1 (বাদে) পর্যন্ত একটি দশমিক দেয়। একটি পরিসরে random পূর্ণসংখ্যা — যেমন লুডুর ছক্কা:
// min থেকে max পর্যন্ত (দুটিসহ) একটি random পূর্ণসংখ্যা
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
randomInt(1, 6); // 1, 2, 3, 4, 5 বা 6 — প্রতিবার আলাদা
// নিরাপত্তার কাজে (OTP, token, পাসওয়ার্ড রিসেট লিংক):
const bytes = crypto.getRandomValues(new Uint32Array(1));
const id = crypto.randomUUID();Math.random() একটি PRNG — একটি সূত্র থেকে বানানো “random-এর মতো দেখতে” সংখ্যা। V8 এখন xorshift128+ নামের দ্রুত পদ্ধতি ব্যবহার করে; খেলা, অ্যানিমেশন বা নমুনা বাছাইয়ে চমৎকার। কিন্তু কয়েকটি আউটপুট দেখে পরেরগুলো অনুমান করা তাত্ত্বিকভাবে সম্ভব, তাই OTP বা login token-এ কখনো নয়। সেখানে crypto.getRandomValues বা crypto.randomUUID — এরা cryptographically secure উৎস ব্যবহার করে (ব্রাউজার আর Node.js দুই জায়গাতেই আছে)। টেস্টে প্রতিবার একই “random” ফল চাইলে seed নেওয়া যায় এমন ছোট PRNG ফাংশন লিখে নেওয়া হয়।
পারফরম্যান্সের ভেতরের কথা: ভাষার নিয়মে সব number double হলেও engine ভেতরে চালাকি করে। V8 ছোট পূর্ণসংখ্যাকে আলাদা দ্রুত রূপে রাখে — একে বলে Smi (small integer). Array-র সব উপাদান ছোট পূর্ণসংখ্যা হলে engine সেটিকে ঘন integer array হিসেবে রাখে; একটি 1.5 ঢুকলে double array-তে, আর একটি string ঢুকলে সাধারণ array-তে নামিয়ে দেয়। পর্ব ০৫-এ দেখা JIT আর hidden class-এর মতোই এটি engine-এর অনুমান — আপনার কোড ঠিক থাকে, শুধু গতি বদলায়। তাই অকারণে একই array-তে সংখ্যা আর লেখা মেশাবেন না।
৭ · BigInt — যত বড় খুশি পূর্ণসংখ্যা
253-এর সীমা পার হতে ES2020-এ যুক্ত হয় নতুন primitive টাইপ BigInt. সংখ্যার শেষে n লিখলেই BigInt: 10n, typeof 10n → "bigint". মেমরি যতটা দেয়, ততটা বড় পূর্ণসংখ্যা হুবহু রাখা যায় — 2n ** 64n → 18446744073709551616n.
- শুধু পূর্ণসংখ্যা: ভাগে দশমিক অংশ কেটে যায় —
7n / 2n→3n,-7n / 2n→-3n(০-এর দিকে)।BigInt(1.5)দেয়RangeError: The number 1.5 cannot be converted to a BigInt because it is not an integer. - মেশানো নিষেধ:
10n + 1→TypeError: Cannot mix BigInt and other types, use explicit conversions. Engine চুপচাপ রূপান্তর করে না, কারণ যেদিকেই করুক তথ্য হারাতে পারে। নিজে লিখুন:10n + BigInt(1)বাNumber(10n) + 1. - তুলনা চলে:
10n > 5→true,10n == 10→true, কিন্তু10n === 10→false(টাইপ আলাদা)। - Math চলে না:
Math.sqrt(4n)→TypeError: Cannot convert a BigInt value to a number. - JSON সরাসরি নেয় না:
JSON.stringify({ id: 10n })→TypeError: Do not know how to serialize a BigInt(V8-এর বার্তা)। পাঠানোর আগেString(id)করুন। - দামি: BigInt-এর হিসাব number-এর চেয়ে ধীর — দরকার হলেই ব্যবহার করুন।
একটি বাস্তব হিসাব: বাংলাদেশে প্রায় ১৭ কোটি মানুষ। ধরা যাক জনসংখ্যা 169828911 (উদাহরণের সংখ্যা)। প্রত্যেক দুজন মানুষের মধ্যে একটি করে সম্ভাব্য “পরিচয়ের জোড়া” — মোট জোড়া n(n − 1)/2. number দিয়ে হিসাব করলে n * (n - 1) মাঝপথেই 253 পেরিয়ে যায়, আর উত্তর আসে 14420929420808504. BigInt দিয়ে আসে 14420929420808505n — শেষ অঙ্কে এক কম-বেশি! মাঝের ধাপ সীমা ছাড়ালে শেষ ফল ছোট দেখালেও ভুল হতে পারে।
C, C++ আর Java-তে int/long নির্দিষ্ট বিটের, সীমা ছাড়ালে সাধারণত নীরবে ঘুরে যায় (overflow; C/C++-এ signed integer-এর overflow আসলে undefined behavior); JavaScript-এর number ঘোরে না, বরং নির্ভুলতা হারায়, আর BigInt অনেকটা Java-র BigInteger-এর মতো (Java-র টাইপ ও ভ্যারিয়েবল)। Python-এর int নিজেই সীমাহীন, তাই Python-এ BigInt-এর আলাদা দরকার নেই, আর // দিয়ে integer ভাগ হয় (Python-এর সংখ্যা ও গণিত)। দশমিকের জন্য Python-এর decimal বা Java-র BigDecimal-এর মতো কিছু JavaScript-এ এখনো built-in নেই — library লাগে।
৮ · টাকার হিসাব — পয়সায় রাখুন, শেষে সাজান
এবার নাদিয়ার কার্টে ফিরি। পেশাদার নিয়মটি সহজ: টাকাকে সবসময় পূর্ণসংখ্যা পয়সায় রাখুন। ১২০.৪৫ টাকা মানে 12045 পয়সা। পূর্ণসংখ্যার যোগ-বিয়োগ-গুণ safe সীমার ভেতরে হুবহু নির্ভুল — প্রায় ৯০ লক্ষ কোটি টাকা পর্যন্ত। ভাগ বা শতাংশে দশমিক এলে আপনি নিজে ঠিক করবেন কোথায় আর কোন নিয়মে গোল হবে। নিচের debugger-এ দুই পদ্ধতি পাশাপাশি দেখুন (VAT-এর ১৫% হারটি শুধু উদাহরণ — পণ্য অনুযায়ী আসল হার আলাদা হতে পারে)।
1const f = 120.45 + 80.10 + 45.30; // টাকায়2const cart = [12045, 8010, 4530]; // পয়সায়3const sub = cart[0] + cart[1] + cart[2];4const vat = Math.round(sub * 15 / 100);5const total = sub + vat;6console.log(tk.format(total / 100));
| f | |
| sub | |
| vat | |
| total |
- টাকায় float যোগ। ১২০.৪৫ + ৮০.১০ + ৪৫.৩০ — তিনটির কোনোটিই binary-তে হুবহু নেই, তাই ফল
245.85000000000002. এখনই হয়তো চোখে পড়ে না, কিন্তু হাজার হাজার লেনদেন যোগ হতে থাকলে এই ছোট ভুলগুলো জমে, আর রিপোর্টের যোগফল ব্যাংকের হিসাবের সাথে মেলে না। - দাম পয়সায়। একই দাম পূর্ণসংখ্যা পয়সায় রাখা হলো। খেয়াল করুন: টাকা থেকে পয়সা বানাতে শুধু ১০০ দিয়ে গুণ করবেন না —
19.99 * 100দেয়1998.9999999999998! লিখুনMath.round(19.99 * 100), আর সবচেয়ে ভালো হয় ডেটাবেসেই পয়সায় রাখলে। - পূর্ণসংখ্যার যোগ — হুবহু। 12045 + 8010 + 4530 =
24585. কোনো লুকানো অঙ্ক নেই, কারণ প্রতিটি পূর্ণসংখ্যা safe সীমার অনেক ভেতরে। - VAT — গোল করার সিদ্ধান্ত। 24585 × 15 / 100 = 3687.75 পয়সা। আধা পয়সা দেওয়া যায় না, তাই
Math.roundদিয়ে 3688. আগে গুণ, পরে ভাগ — এতে মাঝের ধাপ পূর্ণসংখ্যা থাকে। প্রতিটি পণ্যে আলাদা VAT গোল করবেন, নাকি মোটের ওপর একবার — এটি ব্যবসার নীতি; দুইভাবে কয়েক পয়সা পার্থক্য হতে পারে, তাই নিয়মটি লিখে রাখুন। - মোট। 24585 + 3688 =
28273পয়সা — হিসাবের সব কাজ শেষ, এবং প্রতিটি ধাপ নির্ভুল। - একদম শেষে সাজানো। শুধু দেখানোর সময় ১০০ দিয়ে ভাগ করে
Intl.NumberFormat("bn-BD", { style: "currency", currency: "BDT" })দিয়ে লেখা হলো২৮২.৭৩৳. হিসাব integer-এ, সাজসজ্জা শেষে — এই ভাগাভাগিই মূল নিয়ম।
বিল ভাগ — হারিয়ে যাওয়া এক পয়সা
তিন বন্ধু রেস্টুরেন্টে ১০০০ টাকা বিল দেবেন। 1000 / 3 = 333.333… — কেউ ৩৩৩.৩৩ দিলে মোট হয় ৯৯৯.৯৯; এক পয়সা কোথায় গেল? পয়সায় ভাবলে সমাধান পরিষ্কার: 100000 পয়সা, Math.trunc(100000 / 3) = 33333 প্রত্যেকে, আর 100000 % 3 = 1 পয়সা বাকি — সেটি একজনের ভাগে যোগ হবে। মোট সবসময় মিলবে। বেতন ভাগ, কিস্তি, ডিসকাউন্ট ভাগ — সব জায়গায় এই “ভাগফল + ভাগশেষ বিলি” কৌশল কাজে লাগে।
এবার পুরো কৌশলটি চালিয়ে দেখুন — কার্ট, VAT, বিল ভাগ, বাংলায় সাজানো আর BigInt-এর আচরণ:
const cart = [12045, 8010, 4530]; // দাম পয়সায়
let subtotal = 0;
for (const p of cart) subtotal += p;
const vat = Math.round(subtotal * 15 / 100); // উদাহরণ হার 15%
const total = subtotal + vat;
const tk = new Intl.NumberFormat("bn-BD", { style: "currency", currency: "BDT" });
console.log(subtotal, vat, total, tk.format(total / 100));
const bill = 100000, people = 3; // ১০০০ টাকা, ৩ জন
console.log(Math.trunc(bill / people), bill % people);
const bn = new Intl.NumberFormat("bn-BD", { minimumFractionDigits: 2 });
console.log(bn.format(1234567.5));
console.log((1.005).toFixed(2), new Intl.NumberFormat("en", { maximumFractionDigits: 2 }).format(1.005));
let f = 1n;
for (let i = 1n; i <= 25n; i++) f *= i; // 25!
console.log(f, Number(f));
try { f + 1; } catch (e) { console.log(e.name + ": " + e.message); }
try { JSON.stringify({ id: 10n }); } catch (e) { console.log(e.message); }প্রথম লাইনে আসে 24585 3688 28273 ২৮২.৭৩৳ — সব হিসাব integer-এ, টাকার চিহ্ন আর বাংলা অঙ্ক শুধু শেষে। বিল ভাগে 33333 1: প্রত্যেকে ৩৩৩.৩৩ টাকা, আর এক পয়সা একজনের ভাগে। bn-BD দেয় লাখ-কোটির কমায় ১২,৩৪,৫৬৭.৫০. তারপর "1.00" বনাম "1.01" — ওপরের ডায়াগ্রামের গল্প। ২৫-এর factorial BigInt-এ হুবহু 15511210043330985984000000n, কিন্তু number-এ রূপান্তর করলে শুধু কাছাকাছি 1.5511210043330986e+25. শেষ দুই লাইন TypeError-এর বার্তা দুটি ধরে দেখায় (এগুলো Edge/Chrome আর Node.js-এর V8-এর বার্তা; Firefox-এ ভাষা আলাদা)।
0.1 + 0.2 === 0.3কেনfalse? — 0.1, 0.2, 0.3 binary-তে অন্তহীন, তাই IEEE 754 double-এ কাছাকাছি মানে থাকে; যোগফল 0.3-এর double নয়, পাশের double —0.30000000000000004.Number.MAX_SAFE_INTEGERকত, কেন? — 253 − 1 = 9007199254740991, কারণ double-এ নির্ভুলতা ৫৩ বিটের (৫২ বিট mantissa + লুকানো 1).typeof NaNআরNaN === NaN? —"number"আরfalse; যাচাই করতেNumber.isNaNবাObject.is.parseInt("08"),Number(""),parseInt(0.0000005)? — 8, 0, 5; শেষেরটি কারণ number আগে"5e-7"string হয়।- টাকার হিসাবে float কেন নয়? — দশমিক আনুমানিক থাকে, ভুল জমে; পূর্ণসংখ্যা পয়সায় (বা BigInt-এ) হিসাব করে শেষে format করা হয়।
৯ · AI-এর যুগে — হিসাবের কোডে AI-এর চেনা ভুল
AI সংখ্যার কোড খুব দ্রুত লেখে, আর বেশিরভাগ সময় সাধারণ ইনপুটে ঠিকঠাক চলে। সমস্যা লুকিয়ে থাকে সীমানায় — .5-এ গোল করা, খালি ইনপুট, বড় ID, ঋণাত্মক টাকা। AI যে কোড থেকে শিখেছে, তার বড় অংশেই এই ভুলগুলো আছে, তাই সে-ও একই ভুল আত্মবিশ্বাসের সাথে দেয়।
| AI যা প্রায়ই লেখে | সমস্যা | যা চাইবেন |
|---|---|---|
price * qty টাকায় float দিয়ে, মোট যোগ | ভুল জমে, রিপোর্ট মেলে না | integer পয়সায় হিসাব, শেষে format |
Number(x.toFixed(2)) বা Math.round(x*100)/100 দিয়ে “গোল করা” | 1.005-এর মতো মানে ভুল দিকে যায়; toFixed string দেয় | পয়সায় এনে নির্দিষ্ট নিয়মে গোল |
parseInt(value) radix ছাড়া, ইনপুট যাচাই ছাড়া | "12abc" → 12, খালি ঘর → NaN/0 | Number + খালি ঘর ও NaN যাচাই |
Math.random().toString(36) দিয়ে token/OTP | অনুমানযোগ্য, নিরাপদ নয় | crypto.getRandomValues / crypto.randomUUID |
API-র id-কে number ধরা | 253-এর ওপরে নীরবে বদলায় | ID string হিসেবে রাখা |
AI-কে হিসাবের কোড লিখতে বললে শর্তগুলো আগেই বলে দিন — কোন একক, কোন rounding নিয়ম, কোন সীমার ইনপুট। আর পরীক্ষার কেসও চেয়ে নিন:
AI-এর লেখা সংখ্যার কোড — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১প্রায় সব প্রোগ্রামিং ভাষায় 0.1 + 0.2 একই “ভুল” উত্তর দেয় কেন? IEEE 754 মানদণ্ড কোথা থেকে এলো?
১৯৭০-এর দশকে প্রতিটি কম্পিউটার কোম্পানির দশমিক সংখ্যা রাখার নিজস্ব পদ্ধতি ছিল। IBM, DEC, Cray, CDC — প্রত্যেকের বিট-বিন্যাস, গোল করার নিয়ম, এমনকি শূন্য দিয়ে ভাগের আচরণও আলাদা। একই প্রোগ্রাম দুই মেশিনে চালালে আলাদা উত্তর আসত, আর বিজ্ঞানীরা নিজের ফলাফল অন্য ল্যাবে মিলিয়ে দেখতে পারতেন না। ঠিক যেন প্রতিটি জেলার বাজারে আলাদা মাপের সের — ব্যবসা চলে, কিন্তু কেউ কারও হিসাব বিশ্বাস করে না।
এই অবস্থা বদলাতে IEEE-র একটি কমিটি কাজ শুরু করে। এর প্রধান স্থপতি ছিলেন University of California, Berkeley-র গণিতবিদ William Kahan; Intel তখন তাদের 8087 গণিত-চিপ বানাচ্ছিল, আর Kahan সেই নকশারও পরামর্শক ছিলেন। দীর্ঘ বিতর্কের পর ১৯৮৫ সালে IEEE 754 প্রকাশিত হয়। এতে ঠিক করা হয় 32-bit (single) আর 64-bit (double) বিন্যাস, sign-exponent-mantissa ভাগ, চারটি rounding mode (ডিফল্ট “round half to even”), আর বিশেষ মান — Infinity, NaN, -0. সবচেয়ে বড় প্রতিশ্রুতি: যোগ, বিয়োগ, গুণ, ভাগ আর বর্গমূলের ফল হবে “সঠিক উত্তরকে নিয়ম মেনে গোল করা মান” — যেকোনো মেনে চলা মেশিনে হুবহু একই। এই কাজের জন্য Kahan ১৯৮৯ সালে Turing Award পান। মানদণ্ডটি ২০০৮ আর ২০১৯ সালে হালনাগাদ হয়েছে; ২০০৮-এর সংস্করণে দশমিক-ভিত্তিক বিন্যাসও যোগ হয়।
আজ প্রায় সব CPU-র hardware এই মানদণ্ড মানে। C-র double, Java-র double, Python-এর float, JavaScript-এর number — সবাই শেষ পর্যন্ত একই hardware নির্দেশ চালায়। তাই 0.1 + 0.2 সব জায়গায় একই 0.30000000000000004. এটি আসলে মানদণ্ডের সাফল্যের প্রমাণ: ফলটি “ভুল” নয়, বরং সব মেশিনে একইভাবে আনুমানিক — আর সেটা আগে থেকে জানা ও হিসাব করা যায়।
তাহলে binary কেন, দশমিক নয়? কারণ binary hardware-এ সহজ, দ্রুত আর কম জায়গা নেয়; আর বিজ্ঞান-প্রকৌশলের মাপজোখ এমনিতেই আনুমানিক — তাপমাত্রা বা দূরত্বের ১৬তম অঙ্কে কারও কিছু যায় আসে না। সমস্যা হয় শুধু সেই জায়গায়, যেখানে মানুষ দশমিকে হুবহু হিসাব আশা করে — টাকা।
মূল উপলব্ধি: একটি ভালো মানদণ্ড সবসময় “নিখুঁত” হয় না, কিন্তু সবাইকে একই নিয়মে বাঁধে — আর এই পূর্বানুমানযোগ্যতাই নির্ভরযোগ্য সফটওয়্যারের ভিত্তি। floating-point-এর ভুল জানা ভুল; তাকে এড়ানোর দায়িত্ব প্রোগ্রামারের।
প্র ০২JavaScript শুরুতে একটিমাত্র number টাইপ রেখেছিল কেন? এই সরলতার দাম কী?
১৯৯৫ সালে JavaScript-এর লক্ষ্য ছিল ওয়েব ডিজাইনার আর শখের প্রোগ্রামারদের হাতে ছোট script তুলে দেওয়া — ফর্মে নম্বর দেওয়া হয়েছে কি না দেখা, একটি দাম যোগ করা। যিনি জীবনে প্রথম কোড লিখছেন, তাঁকে যদি int, short, long, float, double-এর পার্থক্য আর কোনটিতে কোনটি রাখা যায় শিখতে হয়, শুরুতেই অনেকে হাল ছেড়ে দেবেন। তাই নকশা হলো: একটিই সংখ্যা, আর সেটি এমন হবে যাতে পূর্ণসংখ্যা আর দশমিক দুটোই চলে — 64-bit double. সে সময়ের সবচেয়ে বহুল ব্যবহৃত, hardware-সমর্থিত বিন্যাস এটিই।
এই সিদ্ধান্তের কিছু সত্যিকারের সুবিধা ছিল: integer overflow নেই (C-তে বড় সংখ্যা যোগ করলে নীরবে ঋণাত্মক হয়ে যেতে পারে), 7 / 2 দেয় 3.5 — যা নতুনরা আশা করেন, আর টাইপ রূপান্তরের ঝামেলা নেই।
দাম দিতে হলো পরে, যখন JavaScript ছোট script থেকে বিশাল অ্যাপ্লিকেশনের ভাষা হলো:
- বড় পূর্ণসংখ্যা: ৬৪-বিট ID, cryptography, নির্ভুল টাইমস্ট্যাম্প (nanosecond) — 253-এর সীমায় আটকে যায়।
- টাকা: দশমিক টাইপ নেই, তাই প্রতিটি প্রজেক্টকে নিজে পয়সার কৌশল বা library বেছে নিতে হয়।
- বিট-কাজ:
&,|,<<করার সময় engine সংখ্যাকে 32-bit integer-এ বদলায়, তারপর আবার double বানায় — 231-এর ওপরের সংখ্যায় অদ্ভুত ফল আসে। - পারফরম্যান্স: সব হিসাব double-এ করলে ধীর হতো; তাই engine-গুলোকে Smi-র মতো জটিল ভেতরের কৌশল বানাতে হয়েছে, যাতে বাইরে থেকে double দেখালেও ভেতরে integer-এর গতি পাওয়া যায়।
ভাষাটি পরে ধাপে ধাপে ফাঁক পূরণ করেছে: ২০১০-এর দিকে Typed Array (Int32Array, Float64Array) এলো binary ডেটা আর গ্রাফিক্সের জন্য, ES2020-এ BigInt. কিন্তু ওয়েবের নিয়ম হলো “পুরোনো সাইট ভাঙা যাবে না”, তাই সাধারণ number-এর আচরণ কখনো বদলানো যাবে না।
মূল উপলব্ধি: ভাষার নকশায় “আজকের সরলতা” প্রায়ই “আগামী দিনের জটিলতা” হয়ে ফিরে আসে — বিশেষ করে যখন পেছনের দিকের সামঞ্জস্য (backward compatibility) রক্ষা করতে হয়। কোনো সিদ্ধান্ত নেওয়ার সময় ভাবুন: এটি দশ গুণ বড় হলে কী হবে?
প্র ০৩BigInt আসতে ২০২০ সাল লাগল কেন? Twitter-এর ID আর JSON-এর নির্ভুলতার সমস্যা থেকে কী শেখার আছে?
২০১০ সালে Twitter (এখন X) টুইটের ID বানানোর পদ্ধতি বদলায়। আগে ডেটাবেস ক্রমিক নম্বর দিত; বিপুল ব্যবহারকারীর চাপে তারা “Snowflake” নামের একটি পদ্ধতি চালু করে, যা সময়, মেশিন নম্বর আর ক্রমিক সংখ্যা মিলিয়ে ৬৪-বিট ID তৈরি করে। এই ID দ্রুতই 253 ছাড়িয়ে যায়। ফল: JavaScript দিয়ে বানানো ওয়েব অ্যাপ আর থার্ড-পার্টি ক্লায়েন্ট API-র JSON পড়ার সাথে সাথে ID-র শেষ অঙ্ক বদলে যেত — একটি টুইটের ID দিয়ে খোঁজ করলে অন্য বা না-থাকা টুইট আসত। Twitter-এর সমাধান ছিল প্রতিটি ID-র পাশে একই মানের string সংস্করণ id_str পাঠানো, আর ডেভেলপারদের সেটিই ব্যবহার করতে বলা। বহু API এখনো এই কারণেই ID string হিসেবে পাঠায়।
এর মূল কারণ JSON-এর নকশায়। JSON-এর মানদণ্ড সংখ্যার কোনো সীমা বলে না — 1234567890123456789 বৈধ JSON. কিন্তু প্রতিটি ভাষা সেটি নিজের টাইপে পড়ে। Java বা Go একে ৬৪-বিট integer-এ পড়তে পারে, JavaScript-এর JSON.parse পড়ে double-এ। একই ফাইল, ভিন্ন অর্থ — আর কোনো error নেই। পরে আসা I-JSON (RFC 7493) মানদণ্ড এজন্যই পরামর্শ দেয়: পারস্পরিক ব্যবহারের জন্য ±(253 − 1)-এর বাইরের সংখ্যা string হিসেবে পাঠাতে।
BigInt-এর প্রস্তাব TC39-এ এগোয় কয়েক বছর ধরে। প্রশ্নগুলো সহজ ছিল না: number-এর সাথে মেশানো যাবে কি না (শেষে সিদ্ধান্ত — না, কারণ নীরব রূপান্তরে তথ্য হারায়), Math-এর function চলবে কি না, +x কী করবে, JSON-এ কীভাবে যাবে (সিদ্ধান্ত — ডিফল্টে error, যাতে কেউ নীরবে ভুল ডেটা না পাঠায়)। Chrome ২০১৮ সালে প্রথম চালু করে, আর ES2020-এ এটি মানদণ্ডের অংশ হয়। এজন্যই JSON.stringify(10n) TypeError দেয় — এটি ভুল নয়, ইচ্ছাকৃত নকশা।
মূল উপলব্ধি: ডেটা এক সিস্টেম থেকে আরেক সিস্টেমে যাওয়ার সীমানাতেই সবচেয়ে সূক্ষ্ম বাগ জন্মায়। “এই ফিল্ডে কখনো যোগ-বিয়োগ হবে কি?” — উত্তর না হলে সেটি সংখ্যা নয়, string. আর নীরবে ভুল করার চেয়ে জোরে error দেওয়া অনেক ভালো নকশা।
প্র ০৪১৯৯১ সালের Patriot ক্ষেপণাস্ত্রের ব্যর্থতা থেকে সংখ্যার নির্ভুলতা নিয়ে কী শেখা যায়?
১৯৯১ সালের ২৫ ফেব্রুয়ারি, উপসাগরীয় যুদ্ধের সময় সৌদি আরবের ধাহরানে একটি মার্কিন Patriot প্রতিরক্ষা ব্যবস্থা একটি আগত Scud ক্ষেপণাস্ত্র আটকাতে ব্যর্থ হয়। ক্ষেপণাস্ত্রটি একটি সেনা ব্যারাকে আঘাত করে, এবং ২৮ জন মার্কিন সৈন্য নিহত হন। পরে মার্কিন সরকারের হিসাব-নিরীক্ষা সংস্থা GAO-র ১৯৯২ সালের প্রতিবেদন আর পরবর্তী বিশ্লেষণে মূল কারিগরি কারণ হিসেবে একটি সংখ্যার নির্ভুলতার সমস্যা চিহ্নিত হয়।
ব্যবস্থাটি সময় গুনত এক সেকেন্ডের দশ ভাগের এক ভাগ (0.1 s) এককে, আর সেকেন্ডে রূপান্তরের জন্য 1/10 দিয়ে গুণ করত। কিন্তু 1/10 binary-তে অন্তহীন — এই পর্বের প্রথম ডায়াগ্রামের ঠিক সেই সমস্যা। যন্ত্রটি 1/10-কে একটি ২৪-বিটের fixed-point রেজিস্টারে কেটে রাখত, ফলে প্রতি গণনায় অতি সামান্য — প্রায় এক কোটি ভাগের এক ভাগ সেকেন্ড — ভুল হতো। এক মুহূর্তে এটি নগণ্য। কিন্তু ভুলটি সময়ের সাথে জমতে থাকে। ব্যবস্থাটি টানা প্রায় ১০০ ঘণ্টা চালু ছিল; তাতে জমা ভুল দাঁড়ায় প্রায় এক সেকেন্ডের এক-তৃতীয়াংশ (প্রায় 0.34 s). শব্দের চেয়ে কয়েক গুণ দ্রুতগামী Scud ওই সময়ে আধা কিলোমিটারেরও বেশি পথ পেরোয়, ফলে radar ভুল জায়গায় লক্ষ্য খুঁজেছিল।
দুঃখজনক অংশ হলো, সমস্যাটি আগেই জানা ছিল। দীর্ঘ সময় চালু থাকলে নির্ভুলতা কমে — এমন সতর্কতা দেওয়া হয়েছিল, আর পরামর্শ ছিল নিয়মিত ব্যবস্থাটি নতুন করে চালু (reboot) করা, যদিও কত ঘণ্টা পরপর, তা স্পষ্ট বলা ছিল না। সংশোধিত সফটওয়্যার পৌঁছায় ঘটনার পরদিন। আরও একটি বিষয়: কোডের কিছু অংশে সময় রূপান্তর উন্নত করা হয়েছিল, কিছু অংশে হয়নি — দুই জায়গার ভুল আলাদা হওয়ায় সমস্যা বেড়েছিল বলে বিশ্লেষকরা দেখান।
আমাদের সাধারণ অ্যাপে প্রাণের ঝুঁকি থাকে না, কিন্তু একই ধরন দেখা যায়: setInterval-এ প্রতি ধাপে 0.1 যোগ করে টাইমার চালালে কিছুক্ষণ পর ঘড়ি সরে যায়; প্রতিটি লেনদেনে float টাকা যোগ করলে মাস শেষে হিসাব মেলে না। সমাধানও একই: ভুল জমতে দেবেন না — পূর্ণসংখ্যা এককে (মিলিসেকেন্ড, পয়সা) গুনুন, বা প্রতিবার শুরুর বিন্দু থেকে নতুন করে হিসাব করুন।
মূল উপলব্ধি: একটি ক্ষুদ্র ভুল একবার ঘটলে নগণ্য, কিন্তু যে ভুল প্রতিবার একই দিকে ঘটে, সময়ের সাথে তা জমে বড় হয়। দীর্ঘ সময় চলা সিস্টেমে প্রশ্ন করুন: “এই হিসাবের ভুল কি জমছে?”
প্র ০৫JavaScript-এ কি কখনো একটি আসল Decimal টাইপ আসবে? এখন টাকার হিসাবের জন্য বিকল্পগুলো কী?
টাকার জন্য আদর্শ হলো দশমিক-ভিত্তিক সংখ্যা, যেখানে 0.1 হুবহু 0.1. অনেক ভাষায় এটি আছে: Python-এর decimal module, Java-র BigDecimal, C#-এর decimal, আর ডেটাবেসে SQL-এর DECIMAL/NUMERIC. ব্যাংক, শেয়ারবাজার, হিসাবরক্ষণ সফটওয়্যার বহু দশক ধরে এগুলোর ওপর চলে। IEEE 754-এর ২০০৮ সংস্করণেও decimal64 ও decimal128 বিন্যাস যুক্ত হয়েছে।
JavaScript-এ এমন টাইপ যোগ করার প্রস্তাব বহু বছর ধরে TC39-এ আলোচিত হচ্ছে। বর্তমান “Decimal” প্রস্তাবটি এখনো প্রাথমিক পর্যায়ে — অর্থাৎ ভাষায় যুক্ত হয়নি, আর চূড়ান্ত রূপ কী হবে (নতুন primitive টাইপ, নাকি একটি object যার method দিয়ে যোগ-গুণ হবে) তা নিয়ে আলোচনা চলছে। কঠিন প্রশ্নগুলো: + অপারেটর কি এতে কাজ করবে? number-এর সাথে মেশালে কী হবে? কত অঙ্ক পর্যন্ত নির্ভুলতা? engine-গুলোর জন্য খরচ কত? BigInt-এর অভিজ্ঞতা দেখিয়েছে, নতুন সংখ্যার টাইপ ভাষার প্রায় প্রতিটি কোণে প্রভাব ফেলে — তাই সাবধানে এগোনোই স্বাভাবিক। তাই এই প্রস্তাব কবে বা আদৌ মানদণ্ডে আসবে, আজ নিশ্চিত বলা যায় না।
এখনকার বাস্তব পথগুলো:
- পূর্ণসংখ্যা ক্ষুদ্রতম এককে: পয়সা, সেন্ট — এই পর্বের পদ্ধতি। সহজ, দ্রুত, কোনো library লাগে না; Stripe-এর মতো পেমেন্ট সার্ভিসের API-ও টাকার পরিমাণ ক্ষুদ্রতম এককে integer হিসেবে নেয়।
- BigInt-এ ক্ষুদ্রতম একক: যখন অঙ্ক safe সীমা ছাড়াতে পারে — যেমন cryptocurrency-র খুব ছোট একক বা দেশের মোট বাজেটের হিসাব।
- Decimal library:
decimal.js,big.js— সুদ, শতাংশ, মুদ্রা রূপান্তরের মতো জটিল হিসাবে, যেখানে অনেক দশমিক অঙ্ক আর নির্দিষ্ট rounding mode লাগে। - সার্ভারে হিসাব: চূড়ান্ত দাম সবসময় সার্ভার আর ডেটাবেস (
DECIMALকলাম) ঠিক করবে; ব্রাউজারের হিসাব শুধু দেখানোর জন্য, কারণ ব্যবহারকারী ব্রাউজারের কোড বদলাতে পারেন।
মূল উপলব্ধি: ভাষায় টুল না থাকলে নকশা দিয়ে সমস্যা এড়ানো যায় — সঠিক একক বেছে নেওয়া, নিয়ম লিখে রাখা, আর বিশ্বাসযোগ্য জায়গায় চূড়ান্ত হিসাব করা। ভবিষ্যতের Decimal এলে কাজ সহজ হবে, কিন্তু চিন্তার পদ্ধতিটি একই থাকবে।
অনুশীলন
বেশিরভাগ কাজ browser console-এ (F12) কয়েকটি লাইন লিখেই করা যায় — ফল আগে অনুমান করুন, তারপর মিলিয়ে নিন।
-
দশবার 0.1: একটি লুপে
let s = 0-এর সাথে দশবার0.1যোগ করুন। ফল কী আসে? এবার একই কাজ পূর্ণসংখ্যায় করুন (দশবার 1 যোগ করে শেষে ১০ দিয়ে ভাগ)। পার্থক্যের কারণ ব্যাখ্যা করুন।প্রথম পদ্ধতিতে আসে
0.9999999999999999, দ্বিতীয়তে ঠিক1. 0.1 জমা থাকে আনুমানিক মানে, আর প্রতিটি যোগের পর ফল আবার গোল হয় — দশটি ছোট ভুল জমে শেষে দৃশ্যমান হয়। পূর্ণসংখ্যার যোগ হুবহু, আর শেষে একবারই ভাগ হয় (10 / 10 = 1 নির্ভুল)। শিক্ষা: ভুল জমতে দেবেন না — integer-এ গুনুন, শেষে একবার রূপান্তর করুন। -
ফল অনুমান করুন: (ক)
Math.round(-3.5)(খ)Math.trunc(-3.7)(গ)-10 % 3(ঘ)parseInt("3.9kg", 10)(ঙ)Number(" ")(চ)9007199254740993 === 9007199254740992(ক)
-3— .5 সবসময় +∞-এর দিকে। (খ)-3— দশমিক কেটে ০-এর দিকে। (গ)-1— ভাগশেষের চিহ্ন ভাজ্যের মতো। (ঘ)3— অঙ্ক যতদূর, ততদূর; বিন্দুতে থামে। (ঙ)0— শুধু ফাঁকা string-ও খালি ধরা হয়, ফাঁদটি এখানেও। (চ)true— দুটি literal-ই একই double-এ গোল হয়, কারণ 253-এর ওপরে বিজোড় সংখ্যার ঘর নেই। -
কিস্তির হিসাব: একটি ফোনের দাম ২৫,০০০ টাকা, ৭ মাসের সমান কিস্তিতে শোধ হবে। পয়সায় হিসাব করে প্রতি মাসের কিস্তি ঠিক করুন, যাতে মোট ঠিক ২৫,০০০ টাকা হয় আর কোনো দুই কিস্তির পার্থক্য এক পয়সার বেশি না হয়।
মোট 2500000 পয়সা।
Math.trunc(2500000 / 7)= 357142, আর2500000 % 7= 6. অর্থাৎ ৭টি কিস্তির ৬টি হবে 357143 পয়সা (৩,৫৭১.৪৩ টাকা) আর ১টি 357142 পয়সা (৩,৫৭১.৪২ টাকা)। যাচাই: 6 × 357143 + 357142 = 2142858 + 357142 = 2500000. Float-এ25000 / 7= 3571.4285714285716 নিয়ে প্রতিটি দুই দশমিকে গোল করলে প্রতিটি কিস্তি ৩,৫৭১.৪৩ টাকা হয়, আর সাত কিস্তির যোগ দাঁড়ায় ২৫,০০০.০১ টাকা — এক পয়সা বেশি। -
AI অডিট: যেকোনো AI-কে বলুন “JavaScript-এ একটি ফাংশন লেখো যা দাম আর ডিসকাউন্ট শতাংশ নিয়ে চূড়ান্ত দাম দুই দশমিকে ফেরত দেয়।” তারপর ১.০০৫ টাকা, ঋণাত্মক দাম, খালি string আর
"abc"দিয়ে পরীক্ষা করুন এবং চেকলিস্ট মেলান।সাধারণত AI লেখে
return (price - price * discount / 100).toFixed(2);বাMath.round(x * 100) / 100. যা দেখা যায়: ফল string (পরে যোগ করলে জোড়া লাগে); ১.০০৫-এর মতো মান ভুল দিকে গোল হয়; খালি string-এ"0.00"আসে (কারণ"" * 1= 0) — ভুল ইনপুট ধরা পড়ে না;"abc"-এ"NaN"লেখা ফেরত আসে; ঋণাত্মক দাম চুপচাপ মেনে নেয়। ভালো সংস্করণ ইনপুট যাচাই করবে, পয়সায় হিসাব করে নির্দিষ্ট নিয়মে গোল করবে, number (পয়সা) ফেরত দেবে, আর দেখানোর কাজ আলাদা ফাংশনে রাখবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমদোকানের চেকআউটে টাকার হিসাবের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ইনপুট যাচাই — খালি, NaN বা ঋণাত্মক দাম বাদ
- সব দাম পূর্ণসংখ্যা পয়সায় আনা
- পয়সায় যোগ আর পরিমাণ দিয়ে গুণ
- VAT বা ডিসকাউন্ট হিসাব করে নির্দিষ্ট নিয়মে পূর্ণ পয়সায় গোল
- শেষে Intl.NumberFormat দিয়ে টাকায় সাজিয়ে দেখানো
JavaScript-এ 0.1 + 0.2 === 0.3 লিখলে কী ফল আসে, আর কেন?
true, কারণ গণিতে দুটি সমানfalse, কারণ দশমিকগুলো IEEE 754 double-এ আনুমানিক মানে থাকে- SyntaxError
- NaN
একটি API থেকে {"id": 1234567890123456789} এলো, আর JSON.parse দিয়ে পড়া হলো। কী ঘটবে?
- RangeError আসবে
- id স্বয়ংক্রিয়ভাবে BigInt হবে
- id-র শেষের অঙ্কগুলো নীরবে বদলে যাবে
- id হবে undefined
ফর্মের একটি খালি ঘরের মান "". Number("") কী দেয়?
0NaNundefined- TypeError
Math.round(-2.5)-এর মান কত?
-3-2.53-2
10n + 1 চালালে কী হয়?
11n- TypeError — BigInt আর number মেশানো যায় না
11"101"
একটি ওয়েবসাইটে পাসওয়ার্ড রিসেটের token বানাতে কোনটি ব্যবহার করা উচিত?
Math.random().toString(36)Date.now()-এর মানcrypto.getRandomValuesবাcrypto.randomUUID- ডেটাবেসের ক্রমিক ID
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- JavaScript-এর number একটি 64-bit IEEE 754 double: ১ বিট sign, ১১ বিট exponent, ৫২ বিট mantissa — নির্ভুলতা ৫৩ বিটের।
- বেশিরভাগ দশমিক আনুমানিক; তুলনায় সহনসীমা (
Number.EPSILON) নিন,===নয়। - 253 − 1-এর ওপরে পূর্ণসংখ্যাও ফসকায় — বড় ID string-এ, বড় হিসাব BigInt-এ (মেশানো নিষেধ, JSON-এ সরাসরি নয়)।
Number("")= 0,parseInt-এ radix দিন,Number.isNaN/Object.isদিয়ে বিশেষ মান চিনুন;Math.round.5-এ +∞-এর দিকে।- টাকা পূর্ণসংখ্যা পয়সায়, rounding-এর নিয়ম লিখে রাখুন, আর একদম শেষে
Intl.NumberFormat("bn-BD"); নিরাপত্তায়Math.randomনয়,crypto.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১১ · সিদ্ধান্ত — if, switch আর ternaryপরবর্তী পর্বশর্ত, শাখা আর পরিষ্কার সিদ্ধান্তের কোড।
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python · সংখ্যা ও গণিতপাশের ট্র্যাকসীমাহীন int, float আর decimal — একই সমস্যার আরেক ভাষার সমাধান।
- Computer Architecture & Digital Logicগভীরে যানCPU-র ভেতরে binary গণিত আর floating-point unit কীভাবে কাজ করে।
- Discrete Mathematicsপাশের ট্র্যাকসংখ্যা-পদ্ধতি, ভাগশেষ গণিত আর গণনার ভিত্তি।