পাঠ ০৩ · ৫৭-এর মধ্যে · মডিউল ১
Home / Courses / Numerical Methods / এররের উৎস

এররের উৎস — রাউন্ড-অফ, ট্রাংকেশন ও প্রোপাগেশন

Sources of error — round-off, truncation & propagation
১১ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • রাউন্ড-অফ এরর ও ট্রাংকেশন এররের মধ্যে পার্থক্য এবং প্রতিটির উৎস
  • কীভাবে রাউন্ড-অফ এরর বহু পুনরাবৃত্তিতে জমা হয় — একটি সত্যিকারের পরিমাপযোগ্য ডেমো
  • টেলর সিরিজ কেটে নেওয়ার ফলে সৃষ্ট ট্রাংকেশন এরর, এবং পদ বাড়ানোর সাথে এরর কীভাবে কমে
  • এরর প্রোপাগেশন ও catastrophic cancellation — কেন দুটি কাছাকাছি সংখ্যা বিয়োগ করা বিপজ্জনক

১ · রাউন্ড-অফ এরর জমা হওয়া — সত্যিকারের পরিমাপ

L02-এ আমরা দেখেছি 0.1 + 0.2 != 0.3 — একটি একক অপারেশনের ছোট ভুল। কিন্তু বাস্তব প্রোগ্রামে একটি অপারেশন প্রায়ই হাজার-লক্ষ বার পুনরাবৃত্ত হয়। প্রতিবার সামান্য একটি রাউন্ড-অফ এরররাউন্ড-অফ এরর (Round-off error)একটি সসীম-নির্ভুলতার সংখ্যা পদ্ধতিতে (যেমন ফ্লোটিং পয়েন্ট) একটি প্রকৃত মানকে নিকটতম প্রকাশযোগ্য মানে রাউন্ড করার ফলে সৃষ্ট ভুল। যোগ হলে, মোট জমা হওয়া ভুল ধীরে ধীরে বড় হতে থাকে। নিচের কোড সেলে 0.1-কে N বার লুপে যোগ করে (sum(0.1 for _ in range(N))) সরাসরি গণনা করা মান 0.1 * N-এর সাথে তুলনা করা হয়েছে — এই দুটির গাণিতিকভাবে সমান হওয়ার কথা, কিন্তু বাস্তবে নয়।

Python
for N in [10, 1000, 100000, 1000000]:
    summed = sum(0.1 for _ in range(N))
    direct = 0.1 * N
    diff = summed - direct
    print(f"N={N:>8d}  loop-sum={summed:.15f}  0.1*N={direct:.15f}  diff={diff:.3e}")

    
বাস্তব আউটপুট দেখায় N = 10-এ পার্থক্য মাত্র -1.110e-16 (প্রায় অদৃশ্য), কিন্তু N = 1,000,000-এ পৌঁছে পার্থক্য বেড়ে হয় 1.333e-06 — অর্থাৎ প্রতিটি একক যোগের ভুল অত্যন্ত ছোট হলেও, ১০ লক্ষ বার পুনরাবৃত্তিতে সেটি জমে একটি স্পষ্ট, পরিমাপযোগ্য পার্থক্যে পরিণত হয়েছে। লক্ষ্য করুন পার্থক্যটি সরল রৈখিকভাবে বাড়েনি (N ১০০ গুণ বাড়লে পার্থক্য ১০০ গুণের বেশি বেড়েছে) — কারণ প্রতিটি নতুন যোগ আগের (ইতিমধ্যে সামান্য ভুল) যোগফলের উপর ঘটে, তাই ভুল ধীরে ধীরে বেড়ে চলা একটি "running total"-এর সাথেও যোগ হতে থাকে।

২ · ট্রাংকেশন এরর — একটি অসীম সিরিজ কেটে নেওয়া

ট্রাংকেশন এররট্রাংকেশন এরর (Truncation error)একটি গাণিতিকভাবে অসীম প্রক্রিয়া (যেমন একটি অসীম সিরিজ বা একটি লিমিট) সসীম সংখ্যক ধাপে "কেটে" আনুমানিক করার ফলে সৃষ্ট ভুল — ফ্লোটিং-পয়েন্ট প্রতিনিধিত্বের সাথে এর কোনো সম্পর্ক নেই, এটি অ্যালগরিদমেরই একটি সহজাত সীমাবদ্ধতা। রাউন্ড-অফ এরর থেকে সম্পূর্ণ ভিন্ন একটি সমস্যা — এটি ফ্লোটিং-পয়েন্ট প্রতিনিধিত্বের কারণে হয় না, বরং অ্যালগরিদম নিজেই একটি অসীম গাণিতিক প্রক্রিয়াকে সসীম ধাপে থামিয়ে দেয় বলে হয়। ক্লাসিক উদাহরণ: $e^x$-এর টেলর সিরিজ:

$$e^x = \sum_{n=0}^{\infty} \frac{x^n}{n!} = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots$$

এই সিরিজ গাণিতিকভাবে অসীম পদ পর্যন্ত চললে ঠিক $e^x$-এর সমান, কিন্তু বাস্তবে আমরা একে একটি নির্দিষ্ট অর্ডারে কেটে নিতে বাধ্য। নিচের কোড সেলে $x = 1$-এর জন্য এই সিরিজ বিভিন্ন অর্ডারে কেটে নিয়ে, প্রতিটি অর্ডারে math.exp(1)-এর প্রকৃত মানের বিপরীতে সত্যিকারের এরর মাপা হয়েছে।

Python
import math

x = 1.0
true_val = math.exp(x)
print(f"math.exp({x}) = {true_val:.10f}   (রেফারেন্স মান)")
print()
print(f"{'অর্ডার':>6} | {'টেলর আনুমানিক মান':>18} | {'প্রকৃত এরর':>14}")

term = 1.0     # n = 0 পদ: x^0 / 0! = 1
approx = 0.0
for n in range(0, 11):
    approx += term
    err = abs(true_val - approx)
    print(f"{n:6d} | {approx:18.10f} | {err:14.3e}")
    term *= x / (n + 1)   # পরবর্তী পদ = আগের পদ * x / (n+1)

    
বাস্তব আউটপুট দেখায় অর্ডার 0-এ (শুধু 1.0 পদ রেখে) এরর 1.718 — অর্থাৎ প্রায় সম্পূর্ণ ভুল। অর্ডার বাড়ার সাথে সাথে এরর দ্রুত কমে: অর্ডার 5-এ এরর 1.615e-03, আর অর্ডার 10-এ পৌঁছে এরর কমে দাঁড়ায় মাত্র 2.731e-08 — মাত্র ১১টি পদ ব্যবহার করে ৭ দশমিক স্থান পর্যন্ত সঠিক উত্তর। এটাই ট্রাংকেশন এররের সংজ্ঞাগত বৈশিষ্ট্য: আরও পদ রাখলে এরর কমে, কিন্তু কম্পিউটেশনাল খরচও বাড়ে — এই ট্রেড-অফ M5 (নিউমেরিক্যাল ইন্টিগ্রেশন)-এ ধাপ-সাইজ h-এর সাথেও একইভাবে ফিরে আসবে।

৩ · একটি দ্বিতীয় উদাহরণ — sin(x)-এর টেলর সিরিজ

একই ধারণা $\sin(x)$-এর টেলর সিরিজেও প্রযোজ্য: $\sin(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots$। নিচের কোড সেলে $x = 1.2$ রেডিয়ানের জন্য এই সিরিজ ভিন্ন সংখ্যক পদে কেটে নিয়ে math.sin(1.2)-এর বিপরীতে এরর মাপা হয়েছে।

Python
import math

x2 = 1.2
true_sin = math.sin(x2)
print(f"math.sin({x2}) = {true_sin:.10f}   (রেফারেন্স মান)")
print()
print(f"{'পদসংখ্যা k':>10} | {'টেলর আনুমানিক মান':>18} | {'প্রকৃত এরর':>14}")

approx = 0.0
for k in range(0, 8):
    power = 2 * k + 1
    term = ((-1) ** k) * (x2 ** power) / math.factorial(power)
    approx += term
    err = abs(true_sin - approx)
    print(f"{k:10d} | {approx:18.10f} | {err:14.3e}")

    
k = 0-এ (শুধু x পদ) এরর 2.680e-01, আর k = 3-এ (৪টি পদ) এরর কমে দাঁড়ায় 1.403e-05 — মাত্র চারটি পদে ৫ দশমিক স্থান পর্যন্ত সঠিক। k = 7-এ এরর নেমে যায় 6.217e-14-এ, যা মেশিন এপসিলনের কাছাকাছি — অর্থাৎ এই বিন্দুর পর আর কোনো বাস্তব উন্নতি সম্ভব নয়, কারণ ট্রাংকেশন এরর ইতিমধ্যে রাউন্ড-অফ এররের মাত্রার নিচে নেমে গেছে।

৪ · এরর প্রোপাগেশন ও Catastrophic Cancellation

যখন একটি হিসাবের ফলাফল পরবর্তী হিসাবের ইনপুট হয়, প্রথম হিসাবের এরর দ্বিতীয় হিসাবে বহন (propagate) হয়ে যায়। সবচেয়ে বিপজ্জনক ক্ষেত্র হলো যখন দুটি কাছাকাছি মানের সংখ্যা একে অপরের থেকে বিয়োগ করা হয় — ফলাফলের সিগনিফিক্যান্ট অঙ্কের সংখ্যা নাটকীয়ভাবে কমে যায়, কারণ দুই সংখ্যার প্রথম কয়েকটি মিলে যাওয়া অঙ্ক বাদ পড়ে যায় এবং শুধু তাদের রাউন্ড-অফ ভুলটুকুই "ভেসে ওঠে"। এটিই L02-এর a - b = 5.551115123125783e-17 উদাহরণের পেছনের একই ঘটনা — a ও b প্রায় সমান হওয়ায় তাদের বিয়োগফলে শুধু রাউন্ড-অফ ভুলটুকুই অবশিষ্ট থাকে। L04-এ আমরা দেখব কীভাবে এই ধরনের সংবেদনশীলতা একটি সম্পূর্ণ অ্যালগরিদমকে অস্থিতিশীল (unstable) করে তুলতে পারে।

মূল কথা · Key takeaway

রাউন্ড-অফ এরর ফ্লোটিং-পয়েন্ট প্রতিনিধিত্বের সীমা থেকে আসে এবং পুনরাবৃত্তিতে জমা হয়; ট্রাংকেশন এরর একটি অসীম প্রক্রিয়াকে সসীম ধাপে কেটে নেওয়ার সহজাত ফল এবং পদ/ধাপ বাড়ালে কমে। বাস্তব নিউমেরিক্যাল অ্যালগরিদম এই দুই ধরনের এররের মধ্যে একটি ভারসাম্য খোঁজে — খুব কম পদ/ইটারেশন মানে বড় ট্রাংকেশন এরর, খুব বেশি মানে (কিছু ক্ষেত্রে) রাউন্ড-অফ এরর জমে ওঠার ঝুঁকি। এই ট্রেড-অফটি এই পুরো কোর্স জুড়ে বারবার ফিরে আসবে, বিশেষত M2 (রুট-ফাইন্ডিং) ও M5 (ইন্টিগ্রেশন)-এ।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ প্রথম ডেমোতে N = 100,000-এ পার্থক্য ছিল 1.885e-08, আর N = 1,000,000 (১০ গুণ বেশি)-এ পার্থক্য বেড়ে হয়েছে 1.333e-06 (প্রায় ৭০ গুণ বেশি)। এই বৃদ্ধি কি সরল রৈখিক (N-এর সমানুপাতিক)? না হলে কেন?

না, ঠিক সরল রৈখিক নয়। প্রতিটি একক যোগের রাউন্ড-অফ এরর নির্ভর করে সেই মুহূর্তে "running total"-টি কত বড় তার উপর (কারণ বড় সংখ্যায় প্রতিটি নতুন ফ্লোটের নির্ভুলতার সীমা তুলনামূলকভাবে বড় হয়, ঠিক L02-এর 1e16 + 1 উদাহরণের মতো)। যেহেতু "running total" নিজেই N-এর সাথে বাড়তে থাকে, প্রতিটি যোগের ভুলও একটু একটু করে বাড়তে থাকে — ফলে মোট জমা হওয়া এরর N-এর সাথে সরল রৈখিকের চেয়ে কিছুটা দ্রুত বাড়ে। এটি সংখ্যাগতভাবে অনুমানযোগ্য, কিন্তু ধ্রুব-অনুপাতিক নয়।

প্র ০২ exp(x)-এর টেলর সিরিজে অর্ডার ৫ থেকে ১০-এ যেতে এরর 1.615e-03 থেকে 2.731e-08-এ নেমেছে — বিশাল উন্নতি। তাহলে কেন আমরা সবসময় খুব বেশি পদ ব্যবহার করি না?

প্রতিটি অতিরিক্ত পদ গণনা করতে বাড়তি কম্পিউটেশনাল খরচ লাগে (একটি গুণ ও একটি ফ্যাক্টোরিয়াল/পাওয়ার হিসাব), এবং বাস্তব প্রোগ্রামে এই ফাংশনটি হয়তো লক্ষ লক্ষ বার কল হয় — তাই সামান্য বাড়তি নির্ভুলতার জন্য অনেক বাড়তি সময় খরচ করা অপ্রয়োজনীয়। উপরন্তু, একটি নির্দিষ্ট বিন্দুর পর ট্রাংকেশন এরর ইতিমধ্যে রাউন্ড-অফ এররের মাত্রার (মেশিন এপসিলনের কাছাকাছি) নিচে নেমে যায় — sin(x) ডেমোতে k = 7-এ ঠিক এটাই ঘটেছে — তাই আরও পদ যোগ করলে বাস্তব নির্ভুলতায় কোনো লাভ হয় না।

প্র ০৩ x = 1.2 (মূল বিন্দু থেকে দূরে) হওয়ায় sin(x)-এর টেলর সিরিজে একই নির্ভুলতা পেতে exp(1)-এর চেয়ে বেশি পদ লাগতে পারে কিনা — আপনার কী মনে হয়, x-এর মান বড় হলে (যেমন x = 10) কী হবে?

x বড় হলে টেলর সিরিজের প্রতিটি পদ (xⁿ/n!) শুরুতে দ্রুত বড় হতে থাকে (ফ্যাক্টোরিয়াল x-এর পাওয়ারকে ছাড়িয়ে যাওয়ার আগ পর্যন্ত), তাই একই নির্ভুলতায় পৌঁছাতে অনেক বেশি পদ প্রয়োজন হয় — এবং বড় ও ছোট মধ্যবর্তী পদের মধ্যে যোগ-বিয়োগ চলাকালীন রাউন্ড-অফ এররও বেড়ে যেতে পারে (এক ধরনের catastrophic cancellation)। এই কারণেই বাস্তব লাইব্রেরি (যেমন math.sin) সরাসরি কাঁচা টেলর সিরিজ ব্যবহার না করে, প্রথমে x-কে একটি ছোট পরিসরে (যেমন [-π, π]) নিয়ে আসার কৌশল (range reduction) ব্যবহার করে।

অনুশীলন

  1. চিন্তা করুন: প্রথম কোড সেলে 0.1-এর বদলে 0.5 ব্যবহার করলে (যা বাইনারিতে সঠিকভাবে প্রকাশযোগ্য, কারণ $0.5 = 2^{-1}$) N = 1,000,000-এ পার্থক্য কেমন হবে বলে আপনার ধারণা?

    পার্থক্য ঠিক শূন্য হবে (অথবা তার কাছাকাছি, শুধু খুব বড় N-এ সামান্য যোগ-ক্রম সংক্রান্ত ভুল)। কারণ 0.5 বাইনারিতে সসীম অঙ্কে ঠিকভাবে লেখা যায় ($0.5 = 2^{-1}$, একটি সরল বাইনারি ভগ্নাংশ), তাই এতে কোনো রাউন্ড-অফ এরর জমা হওয়ার সুযোগই নেই — এই ডেমোটি নিশ্চিত করে যে সমস্যাটি "যোগ করা" নয়, বরং নির্দিষ্টভাবে 0.1-এর বাইনারি অপ্রকাশযোগ্যতা।

  2. পরীক্ষা করুন: দ্বিতীয় কোড সেলে x = 1.0-কে x = 2.0-এ পরিবর্তন করে Run চেপে দেখুন অর্ডার 10-এ এরর x=1-এর চেয়ে বড় না ছোট।

    x = 2.0-এ অর্ডার ১০-এ এরর অনেক বড় হয় (আনুমানিক ৪.৩ × ১০⁻৫ মাত্রার, x=1-এর 2.731e-08-এর তুলনায় হাজার গুণেরও বেশি) — কারণ $x$ বড় হলে টেলর সিরিজের পদগুলো ($x^n/n!$) শুরুতে ধীরে কমে, তাই একই সংখ্যক পদে কম নির্ভুলতা পাওয়া যায়। এটি ঠিক উপরের প্র-০৩-এর আলোচনার প্রত্যক্ষ প্রমাণ।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
কম্পিউটারে সংখ্যা উপস্থাপন — ফ্লোটিং পয়েন্ট