বাইনারি ফ্লোটিং পয়েন্টে কাজ করে এমন প্রায় যেকোনো ক্যালকুলেটরে 0.1 + 0.2 টাইপ করলে আসল উত্তর আসে 0.30000000000000004, 0.3 নয়। এই ক্যালকুলেটরও ভেতরে ভেতরে একই মান তৈরি করে, তারপর সেটাকে প্রদর্শনের জন্য রাউন্ড করে, তাই আপনি দেখেন 0.3। এর পেছনে আসল কারণ হলো ভিত্তির (base) অমিল: মানুষ সংখ্যা লেখে দশমিক (base 10) পদ্ধতিতে, কিন্তু কম্পিউটার সেগুলো সংরক্ষণ করে বাইনারি (base 2) পদ্ধতিতে, আর 0.1 বাইনারিতে হুবহু প্রকাশ করা যায় না।
মূল বিষয়গুলো
- প্রতিটি ভাষায়, বাইনারি ফ্লোটিং পয়েন্টে
0.1 + 0.2এর ফল হয়0.30000000000000004।- বাইনারিতে 0.1 একটি পুনরাবৃত্ত প্যাটার্ন,
0.0001100110011…।- এই ক্যালকুলেটর ফলাফলকে 12 সংখ্যায় (significant digits) রাউন্ড করে দেখায়, তাই কাঁচা ডাবল মানের বদলে দেখায়
0.3।- সংরক্ষিত অবস্থায় 0.1 আসলে হয়ে দাঁড়ায়
0.10000000000000000555111512312578…।
অতিরিক্ত অঙ্কগুলো কোথা থেকে আসে
আপনি নিশ্চয়ই জানেন যে কিছু ভগ্নাংশ দশমিক পদ্ধতিতে পরিষ্কারভাবে বসে না। এক-তৃতীয়াংশ হলো 0.333…, যা অনন্তকাল ধরে চলতে থাকে, কারণ 3, 10-এর গুণনীয়ক নয়। কম্পিউটার সংখ্যা সংরক্ষণ করে বাইনারিতে, যেখানে শুধু সেই ভগ্নাংশগুলোই পরিষ্কারভাবে বসে যাদের হরে থাকে দুইয়ের ঘাত: অর্ধেক, চতুর্থাংশ, অষ্টমাংশ। এক-দশমাংশ সেই তালিকায় নেই। বাইনারিতে 0.1 হলো 0.0001100110011…, যা অনন্তকাল ধরে পুনরাবৃত্ত হতে থাকে, ঠিক যেমন দশমিকে এক-তৃতীয়াংশ পুনরাবৃত্ত হয়।
একটি ডাবল-প্রিসিশন সংখ্যায় অঙ্কের জন্য থাকে 53 বিট, তাই অসীম বাইনারি বিস্তৃতিটি মাঝপথে কেটে ফেলা হয়। আপনি যখন 0.1 টাইপ করেন, তখন যে মান সংরক্ষিত হয় তা হলো 0.1000000000000000055511151231257827021181583404541015625। এটি 0.1 এর কাছাকাছি, কিন্তু ঠিক তার সমান নয়। এমন সামান্য ভুল দুটি মান যোগ করলে ত্রুটিগুলো একে অপরকে কাটাকাটি করে না, বরং জমা হয়, আর যোগফল গিয়ে দাঁড়ায় 0.3-এর ঠিক এক ধাপ পরে। এ কারণেই আসে 0.30000000000000004। এটি IEEE 754 স্ট্যান্ডার্ড, যা কার্যত প্রতিটি প্রসেসর ও ভাষায় বাস্তবায়িত, তাই একই উত্তর দেখা যায় জাভাস্ক্রিপ্ট, পাইথন, সি এবং এক্সেলে।
এই ক্যালকুলেটর এর জন্য কী করে
এই সাইটের কাজের নিয়ম বলা আছে ফ্লোটিং পয়েন্ট গণিত গাইড-এ: প্রতিটি ফলাফল প্রদর্শনের আগে 12 সংখ্যায় (significant digits) রাউন্ড করা হয়। একটি ডাবলে প্রায় 15 থেকে 17টি নির্ভরযোগ্য অঙ্ক থাকে, তাই 12-এ রাউন্ড করলে যেসব অঙ্কে ভরসা করা যায় সেগুলো থেকে যায় এবং শেষের কিছু অঙ্ক বাদ পড়ে, ঠিক যেখানে জমে থাকা ত্রুটিগুলো লুকিয়ে থাকে।
উদাহরণ ১: নিজে টাইপ করে দেখুন। মূল ক্যালকুলেটরে 0.1+0.2 টাইপ করুন। প্রদর্শনে ফলাফল আসবে 0.3। ভেতরের প্রকৃত ডাবল মান হলো 0.30000000000000004, কিন্তু প্রদর্শনের নিয়ম সেটা লুকিয়ে রাখে। একই কৌশল উল্টো দিক থেকেও দেখুন 0.3-0.2 দিয়ে: কাঁচা ফলাফল হয় 0.09999999999999998, আর প্রদর্শনে দেখা যায় 0.1। গুণেও একই ঘটনা ঘটে: 1.1*3 ভেতরে ধরে রাখে 3.3000000000000003, আর দেখায় 3.3।
যখন উত্তর সত্যিই নির্দিষ্টভাবে বলা সম্ভব নয়, তখন ক্যালকুলেটর ভান করে না। 1/3 টাইপ করলে দেখায় 0.333333333333, বারোটি তিন, তারপর থেমে যায়। সিগনিফিক্যান্ট ফিগার্স গাইড-এ ব্যাখ্যা করা আছে এই রাউন্ডিং ও প্রদর্শনের সীমা ঠিক কীভাবে কাজ করে।
এই পার্থক্য কখন গুরুত্বপূর্ণ হয়ে ওঠে
দৈনন্দিন গণিত, হোমওয়ার্ক এবং ইঞ্জিনিয়ারিং অনুমানের ক্ষেত্রে, 12টি নির্ভরযোগ্য অঙ্ক যেকোনো পরিমাপের প্রয়োজনের চেয়ে অনেক বেশি নির্ভুল। এটি মূলত তিনটি সংকীর্ণ পরিস্থিতিতে গুরুত্বপূর্ণ, যেগুলো সবই বর্ণিত আছে ফ্লোটিং পয়েন্ট গণিত গাইড-এ:
- টাকাপয়সা। মুদ্রার মান ফ্লোটিং-পয়েন্ট সংখ্যা হিসেবে সংরক্ষণ করবেন না। পয়সা বা সেন্টকে পূর্ণসংখ্যা হিসেবে রাখাই নিরাপদ পথ, আর ছোট ছোট ত্রুটির জমে ওঠা হিসাব-নিকাশের একটি সুপরিচিত বাগ।
- কোডে সমতা যাচাই। দুটি গণনা করা ফ্লোট সমান কিনা তা পরীক্ষা করা অনির্ভরযোগ্য; তার বদলে সামান্য একটা সহনশীলতা (tolerance) রেখে তুলনা করুন।
- দীর্ঘ ধারাবাহিক গণনা। ত্রুটি জমতে থাকে, আর প্রায় সমান দুটি সংখ্যা বিয়োগ করলে সবচেয়ে বেশি নির্ভুলতা হারায়।
উদাহরণ ২: হুবহু নির্ভুল ভগ্নাংশ। যখন আপনার সম্পূর্ণ নির্ভুল একটি ভগ্নাংশ দরকার, তখন ফ্র্যাকশন ক্যালকুলেটর লব ও হরকে পূর্ণসংখ্যা হিসেবে রাখে এবং কখনো রাউন্ড করে না। সেখানে 1/3 + 1/3 + 1/3 টাইপ করলে ফল আসে হুবহু 1। একই যোগফল প্রদর্শিত দশমিক দিয়ে করলে, 0.333333333333 + 0.333333333333 + 0.333333333333, ফলাফল হয় 0.999999999999। একই ধারণা, দুটি ভিন্ন জগৎ।
উদাহরণ ৩: বর্গমূল ও বর্গ। sqrt(2)^2 একটি ধ্রুপদী ফাঁদ। যুক্তিগতভাবে, 2-এর বর্গমূলের বর্গ হওয়া উচিত 2। ক্যালকুলেটরে কাঁচা ফলাফল হয় 2.0000000000000004, যা প্রদর্শনে রাউন্ড হয়ে দেখায় 2। যখন আপনার একদম নির্ভুল মান দরকার, তখন মূল আকারেই রেখে দিন এবং একেবারে শেষে একবার মূল্যায়ন করুন, যা ক্যালকুলেটর ফাংশন রেফারেন্স-এ √ কী-এর অধীনে আলোচনা করা হয়েছে।
এখানে জেনে রাখার মতো একটি পূর্ণসংখ্যার সীমাও আছে। 2^53 − 1 = 9,007,199,254,740,991 এর নিচে প্রতিটি পূর্ণসংখ্যা হুবহু প্রকাশযোগ্য, আর ক্যালকুলেটর তার সব অঙ্ক ছাপিয়ে দেয়। এই সীমার ওপরে, 19! এর মতো মান বৈজ্ঞানিক নোটেশনে চলে যায়, এমন নির্ভুলতা দাবি করার বদলে যা আসলে নেই।
সচরাচর জিজ্ঞাসিত প্রশ্ন
0.1 আর 0.2 যোগ করলে কি সত্যিই 0.3 হয়, নাকি হয় না?
সঠিক দশমিক গণিতে, হ্যাঁ, হয়। কিন্তু বাইনারি ফ্লোটিং পয়েন্টে, সংরক্ষিত ফলাফল হলো 0.30000000000000004। এই ক্যালকুলেটর ভেতরে ভেতরে সেই একই মান বের করে, তারপর প্রদর্শনের জন্য 12 সংখ্যায় রাউন্ড করে, তাই আপনি দেখেন 0.3।
এটা কি ক্যালকুলেটরের কোনো বাগ?
না। এটি IEEE 754 স্ট্যান্ডার্ড, যা প্রায় প্রতিটি প্রধান প্রসেসর ও ভাষা বাস্তবায়ন করে। যেসব প্রোগ্রাম বা ক্যালকুলেটর এই সমস্যা এড়িয়ে গেছে বলে মনে হয়, তারা আসলে প্রদর্শনের রাউন্ডিং দিয়ে সেটা লুকিয়ে রাখে, ঠিক যেমন এই ক্যালকুলেটরও করে।
একদম নির্ভুল উত্তর কীভাবে পাব?
মূলদ সংখ্যার (rational) গণিতের জন্য ফ্র্যাকশন ক্যালকুলেটর ব্যবহার করুন, যা কখনো রাউন্ড করে না, অথবা sqrt(2) ও pi-এর মতো প্রতীকী (symbolic) রূপগুলো এক্সপ্রেশনে শেষ ধাপ পর্যন্ত অক্ষত রাখুন।
1 কে 3 দিয়ে ভাগ করলে কেন শুধু বারোটি তিন দেখায়?
কারণ এক-তৃতীয়াংশের কোনো ভিত্তিতেই (base) হুবহু প্রকাশ নেই। ক্যালকুলেটর বারোটি নির্ভরযোগ্য অঙ্ক দেখিয়ে থেমে যায়, মান যে শেষ হয়ে যাচ্ছে এমন ভান না করে।