← Math|90 of 100
Numerical Methods

Error Analysis

Understand truncation, roundoff, stability, and condition number in numerical computations.

📂 Errors📖 Lesson 90 of 100🎓 Free Course

Advertisement

Error Analysis

ℹ️ Why It Matters

Error analysis is the theoretical foundation of numerical computing. Without understanding where errors come from, how they propagate, and how to control them, it is impossible to write reliable numerical software. Every algorithm has inherent limitations — truncation error from approximation, roundoff error from finite precision, and sensitivity to problem conditioning. Mastering error analysis transforms a programmer into a numerical analyst, capable of predicting when algorithms will fail and designing robust solutions.

💡 Real-World Impact

Ariane 5's destruction was caused by an overflow in an inertial reference system. The Patriot missile failure in 1991 was caused by accumulated roundoff error in a 24-bit register tracking time. In ML, poorly conditioned weight matrices cause training to diverge silently. Understanding error analysis prevents these catastrophic failures.


Definitions

DfAbsolute Error

The absolute error of an approximation x̃ for a true value x is |x̃ − x|. It measures the magnitude of the difference in the same units as the quantity itself.

DfRelative Error

The relative error is |x̃ − x| / |x| (for x ≠ 0). It measures the error as a fraction of the true value, providing a scale-independent measure of accuracy. A relative error of 10⁻⁶ means about 6 correct digits.

DfTruncation Error

Truncation error arises from approximating an infinite process with a finite one. Examples: truncating a Taylor series after n terms, approximating a derivative with a finite difference, or using a numerical integration rule with finite step size. It exists even with exact arithmetic.

DfRoundoff Error

Roundoff error arises from representing real numbers with有限 precision in floating point arithmetic. Every arithmetic operation introduces a rounding error of at most ε_mach/2 per operation. It is present even when the mathematical algorithm is exact.

DfCondition Number

The condition number κ measures how sensitive a problem is to perturbations in the input. For a function f: if the input has a small perturbation δ, the output changes by approximately κ·δ. A large κ means the problem is ill-conditioned — small input changes cause large output changes.

DfForward Error

Forward error is the difference between the computed output and the true output: |f̃(x) − f(x)|. It measures how far the answer is from the correct answer.

DfBackward Error

Backward error is the smallest perturbation to the input that would produce the observed output: find δ such that f(x + δ) = f̃(x). An algorithm is backward stable if the backward error is on the order of machine epsilon. Backward stability is the gold standard for numerical algorithms.

DfStability

An algorithm is forward stable if small input changes cause small output changes. It is backward stable if the computed result is the exact result for a slightly perturbed input. Backward stability implies forward stability but not vice versa.

DfCatastrophic Cancellation

Catastrophic cancellation occurs when subtracting two nearly equal numbers. The significant digits cancel, leaving mostly noise from less significant bits. The relative error of the result can be much larger than the relative error of the operands.

DfStiff System

A system of ODEs is stiff if the ratio of the largest to smallest eigenvalue of the Jacobian is very large. Stiff systems require implicit solvers with step sizes determined by stability rather than accuracy considerations.


Formulas

Condition Number (Matrix)

κ(A)=AA1\kappa(A) = \|A\| \cdot \|A^{-1}\|

Here,

  • κ(A)κ(A)=Condition number of matrix A
  • A‖A‖=Matrix norm (typically 2-norm or ∞-norm)

Condition Number in Terms of Singular Values

κ2(A)=σmaxσmin\kappa_2(A) = \frac{\sigma_{\max}}{\sigma_{\min}}

Here,

  • σmaxσ_max=Largest singular value of A
  • σminσ_min=Smallest singular value of A

Error Amplification in Linear Systems

δxxκ(A)δbb\frac{\|\delta x\|}{\|x\|} \leq \kappa(A) \cdot \frac{\|\delta b\|}{\|b\|}

Here,

  • δxδx=Perturbation in solution
  • δbδb=Perturbation in right-hand side
  • κ(A)κ(A)=Condition number of coefficient matrix

Taylor Series Truncation Error

Rn(x)=f(n+1)(ξ)(n+1)!xn+1R_n(x) = \frac{f^{(n+1)}(\xi)}{(n+1)!} x^{n+1}

Here,

  • Rn(x)R_n(x)=Remainder after n terms
  • ξξ=Some point between 0 and x

Finite Difference Error

f(x+h)f(x)h=f(x)+h2f(ξ)\frac{f(x+h) - f(x)}{h} = f'(x) + \frac{h}{2}f''(\xi)

Here,

  • hh=Step size
  • f(ξ)f''(ξ)=Second derivative (truncation error term)

Optimal Step Size for Finite Differences

hoptεmachxh_{\text{opt}} \approx \sqrt{\varepsilon_{\text{mach}}} \cdot |x|

Here,

  • εmachε_mach=Machine epsilon
  • xx=Point at which derivative is evaluated

Welford's Online Variance

M2,n=M2,n1+(xnxˉn)(xnxˉn1)M_{2,n} = M_{2,n-1} + (x_n - \bar{x}_n)(x_n - \bar{x}_{n-1})

Here,

  • M2M₂=Running sum of squared deviations
  • xˉnx̄ₙ=Running mean after n observations

Relative Error in Floating Point Arithmetic

fl(ab)(ab)εmach2ab|\text{fl}(a \circ b) - (a \circ b)| \leq \frac{\varepsilon_{\text{mach}}}{2} |a \circ b|

Here,

  • =Any basic operation: +, −, ×, ÷

Types of Errors

Truncation Error

ℹ️ Truncation Error Sources

Truncation error is the price we pay for simplifying a mathematical problem. Every numerical method introduces truncation error: finite differences approximate derivatives, quadrature rules approximate integrals, and iterative methods stop after finitely many steps. The key insight is that truncation error can be controlled by refining the discretization — smaller step sizes, more iterations, or higher-order methods.

Roundoff Error

ℹ️ Roundoff Error Behavior

Roundoff error is random and unbiased in a statistical sense (for well-conditioned computations). It accumulates as O(√n·ε) for n independent operations (random walk model) or O(n·ε) in the worst case. For most practical computations, roundoff is not the dominant error source unless the problem is ill-conditioned.

Combined Error Model

The total error is approximately: total_error ≈ truncation_error + roundoff_error

For a method with truncation error O(h^p) and step size h:

  • Decreasing h reduces truncation error but increases roundoff error (more operations)
  • Optimal h balances both error sources
  • Too small h leads to subtractive cancellation and roundoff dominates

Theorems

ThCondition Number Bounds Error Amplification

For the linear system Ax = b, if A is nonsingular and b has a perturbation δb, then: ‖δx‖/‖x‖ ≤ κ(A) · ‖δb‖/‖b‖

The relative error in x is at most κ(A) times the relative error in b. Similarly, for a perturbation δA in A: ‖δx‖/‖x‖ ≤ κ(A) · ‖δA‖/‖A‖

ThBackward Error of Gaussian Elimination with Partial Pivoting

For Gaussian elimination with partial pivoting solving Ax = b, the computed solution x̃ satisfies: (A + δA)x̃ = b + δb where ‖δA‖/‖A‖ ≤ f(n)·ε_mach and ‖δb‖/‖b‖ ≤ f(n)·ε_mach, for a modest function f(n) (typically O(n) for partial pivoting, O(n^(2/3)) for rook pivoting).

ThWilkinson's Error Bound for Linear Systems

The backward error bound for Gaussian elimination with partial pivoting is: ‖δA‖∞ / ‖A‖∞ ≤ n · ρ · ε_mach where ρ is the growth factor (ratio of largest element during elimination to largest element of A). For most practical problems, ρ is O(1), but in the worst case ρ = 2^(n−1).

ThFloating Point Error Growth in Summation

For naive sequential summation of n floating point numbers: |S_computed − S_exact| ≤ (n−1) · (ε_mach/2) · Σ|x_i| + O(ε²)

For Kahan summation: |S_computed − S_exact| ≤ 2 · (ε_mach/2) · Σ|x_i| + O(ε²)

ThCondition Number of Eigenvalue Problem

For a symmetric matrix A, the sensitivity of eigenvalue λ_i to perturbation in A satisfies: |δλ_i| ≤ ‖δA‖₂ For non-symmetric matrices, eigenvalue sensitivity depends on the condition number of the eigenvector matrix.


Python Implementation

import numpy as np
from typing import Callable, Tuple


class ErrorAnalyzer:
    """Comprehensive error analysis toolkit."""

    @staticmethod
    def condition_number_matrix(A: np.ndarray) -> float:
        """Compute 2-norm condition number of a matrix."""
        return np.linalg.cond(A, ord=2)

    @staticmethod
    def relative_error(computed: float, exact: float) -> float:
        """Compute relative error."""
        if exact == 0:
            return abs(computed)
        return abs(computed - exact) / abs(exact)

    @staticmethod
    def finite_difference_error(f: Callable, df: Callable, x: float,
                                h_range: Tuple[float, float] = (1e-16, 1)):
        """Analyze truncation vs roundoff error in finite differences."""
        h_values = np.logspace(np.log10(h_range[0]), np.log10(h_range[1]), 200)
        errors = []

        exact = df(x)
        for h in h_values:
            # Central difference
            approx = (f(x + h) - f(x - h)) / (2 * h)
            error = abs(approx - exact)
            errors.append(error)

        errors = np.array(errors)
        optimal_h = h_values[np.argmin(errors)]

        return h_values, errors, optimal_h

    @staticmethod
    def demonstrate_cancellation():
        """Demonstrate catastrophic cancellation."""
        print("\n--- Catastrophic Cancellation Demo ---")
        x = 1.0
        h_values = np.logspace(-1, -16, 16)

        print(f"Computing derivative of f(x) = sin(x) at x = 1")
        exact = np.cos(1.0)

        for h in h_values:
            # Forward difference (suffers from cancellation)
            fwd = (np.sin(1 + h) - np.sin(1)) / h
            # Central difference (better but still has issues)
            cen = (np.sin(1 + h) - np.sin(1 - h)) / (2 * h)

            fwd_err = abs(fwd - exact)
            cen_err = abs(cen - exact)

            print(f"  h={h:.0e}: Forward err={fwd_err:.2e}, "
                  f"Central err={cen_err:.2e}")

    @staticmethod
    def welford_online(data: np.ndarray) -> Tuple[float, float]:
        """Welford's online algorithm for mean and variance."""
        n = 0
        mean = 0.0
        M2 = 0.0

        for x in data:
            n += 1
            delta = x - mean
            mean += delta / n
            delta2 = x - mean
            M2 += delta * delta2

        variance = M2 / n if n > 1 else 0.0
        return mean, variance

    @staticmethod
    def naive_mean_variance(data: np.ndarray) -> Tuple[float, float]:
        """Naive two-pass mean and variance (unstable)."""
        n = len(data)
        mean = np.mean(data)
        variance = np.mean((data - mean) ** 2)
        return mean, variance

    @staticmethod
    def demonstrate_stability():
        """Compare stable vs unstable algorithms."""
        print("\n--- Algorithm Stability Demo ---")

        # Generate data with large mean and small variance
        np.random.seed(42)
        data = np.random.uniform(1e7, 1e7 + 1, 1_000_000)
        true_mean = np.mean(data)
        true_var = 1/12  # Variance of uniform(0,1)

        # Naive approach
        naive_mean, naive_var = ErrorAnalyzer.naive_mean_variance(data)

        # Welford
        welford_mean, welford_var = ErrorAnalyzer.welford_online(data)

        print(f"True mean:       {true_mean:.10f}")
        print(f"Naive mean:      {naive_mean:.10f}")
        print(f"Welford mean:    {welford_mean:.10f}")
        print(f"Mean error (naive):   {abs(naive_mean - true_mean):.2e}")
        print(f"Mean error (Welford): {abs(welford_mean - true_mean):.2e}")

        # Variance comparison
        naive_var_pass1 = np.mean(data**2) - np.mean(data)**2  # One-pass (unstable)
        print(f"\nNaive one-pass var: {naive_var_pass1:.6f}")
        print(f"Naive two-pass var: {naive_var:.6f}")
        print(f"Welford var:        {welford_var:.6f}")
        print(f"True var:           {true_var:.6f}")

    @staticmethod
    def demonstrate_condition_number():
        """Show effect of condition number on linear system solving."""
        print("\n--- Condition Number Effect on Linear Systems ---")

        # Well-conditioned
        A1 = np.array([[1, 0], [0, 2]])
        b1 = np.array([1, 1])
        x1 = np.linalg.solve(A1, b1)

        # Ill-conditioned
        A2 = np.array([[1, 1], [1, 1.0001]])
        b2 = np.array([2, 2.0001])
        x2 = np.linalg.solve(A2, b2)

        print(f"Well-conditioned (κ={np.linalg.cond(A1):.1f}):")
        print(f"  Solution: {x1}")

        print(f"Ill-conditioned (κ={np.linalg.cond(A2):.0f}):")
        print(f"  Solution: {x2}")

        # Perturbation experiment
        print(f"\nPerturbation experiment:")
        delta_b = np.array([1e-10, -1e-10])

        x1_pert = np.linalg.solve(A1, b1 + delta_b)
        x2_pert = np.linalg.solve(A2, b2 + delta_b)

        print(f"  Well-conditioned: Δx = {np.linalg.norm(x1_pert - x1):.2e}")
        print(f"  Ill-conditioned:  Δx = {np.linalg.norm(x2_pert - x2):.2e}")

    @staticmethod
    def demonstrate_backward_error():
        """Show backward error interpretation."""
        print("\n--- Backward Error Demonstration ---")

        # Solve Ax = b with known solution
        A = np.array([[1, 2], [3, 4]], dtype=float)
        x_true = np.array([1, 1])
        b = A @ x_true

        # Solve with perturbed matrix
        delta_A = np.array([[1e-10, 0], [0, 1e-10]])
        A_pert = A + delta_A
        x_computed = np.linalg.solve(A_pert, b)

        # Backward error: what perturbation to b would give this answer?
        delta_b = b - A @ x_computed
        backward_error = np.linalg.norm(delta_b) / np.linalg.norm(b)

        print(f"True solution:     {x_true}")
        print(f"Computed solution: {x_computed}")
        print(f"Forward error:     {np.linalg.norm(x_computed - x_true):.2e}")
        print(f"Backward error:    {backward_error:.2e}")
        print(f"Condition number:  {np.linalg.cond(A):.1f}")


def full_error_analysis():
    """Run comprehensive error analysis demonstrations."""
    print("=" * 60)
    print("NUMERICAL ERROR ANALYSIS")
    print("=" * 60)

    analyzer = ErrorAnalyzer()

    # 1. Condition numbers
    print("\n--- Condition Number Examples ---")
    matrices = {
        "Identity": np.eye(3),
        "Hilbert (5×5)": np.linalg.inv(np.vander(np.arange(5), increasing=True)),
        "Random 3×3": np.random.randn(3, 3),
        "Near-singular": np.array([[1, 1], [1, 1.0001]]),
    }

    for name, A in matrices.items():
        kappa = analyzer.condition_number_matrix(A)
        print(f"  {name:<20}: κ = {kappa:.4e}")

    # 2. Finite difference error
    print("\n--- Finite Difference Error Analysis ---")
    f = np.sin
    df = np.cos
    x = 1.0

    h_vals, errors, optimal_h = analyzer.finite_difference_error(f, df, x)
    print(f"  Optimal step size h: {optimal_h:.2e}")
    print(f"  Minimum error:       {np.min(errors):.2e}")
    print(f"  Theoretical optimal: {np.sqrt(np.finfo(float).eps):.2e}")

    # 3. Cancellation
    analyzer.demonstrate_cancellation()

    # 4. Stability comparison
    analyzer.demonstrate_stability()

    # 5. Condition number effect
    analyzer.demonstrate_condition_number()

    # 6. Backward error
    analyzer.demonstrate_backward_error()


full_error_analysis()

Applications in AI/ML

ℹ️ Error Analysis in Machine Learning

Error analysis in ML distinguishes between bias (truncation-like — model too simple), variance (sensitivity to training data — conditioning-like), and noise (irreducible error). Understanding numerical error helps diagnose training failures, design robust optimizers, and choose appropriate precision for inference.

Error Sources in ML

Error TypeML EquivalentMitigation
TruncationModel bias (underfitting)Increase model complexity
RoundoffFP16 overflow in gradientsLoss scaling, FP32 accumulators
ConditioningIll-conditioned loss landscapeNormalization, adaptive learning rates
StabilityTraining divergenceGradient clipping, learning rate scheduling
CancellationVanishing gradientsSkip connections, careful initialization

Condition Numbers in Optimization

The condition number of the Hessian matrix determines convergence speed of gradient descent:

  • κ(H) = 1: All directions have equal curvature → fastest convergence
  • κ(H) ≫ 1: Elongated loss landscape → slow convergence, oscillation
  • Preconditioning reduces effective κ, accelerating convergence

Numerical Precision in Deep Learning

TechniquePrecisionTrade-off
FP32 training32-bitBaseline accuracy
Mixed precisionFP16 + FP322× speed, memory savings
Quantization (INT8)8-bit4× memory reduction, possible accuracy loss
Quantization (INT4)4-bit8× memory, significant accuracy risk
PruningSparseMemory reduction, hardware-dependent speedup

Gradient Clipping as Stability Control

Gradient clipping (max norm or value clipping) is the ML equivalent of step-size control in ODE solvers. It prevents the instability that arises when the effective condition number of the optimization problem becomes too large during training.


Common Mistakes

MistakeProblemSolution
Ignoring condition numberAmplification of errors invisible until failureAlways check κ(A) before solving linear systems
Using forward difference with small hRoundoff dominates for h < √εUse central differences; choose h ≈ √ε ·
Naive variance computationCatastrophic cancellation for large meanUse Welford's algorithm or two-pass method
Trusting FP16 gradientsOverflow/underflow in accumulationKeep gradient accumulators in FP32
Not checking algorithm stabilitySilent corruption of resultsPrefer backward-stable algorithms (e.g., QR over normal equations)
Using more precision than neededWasted memory and computeMatch precision to problem requirements
Ignoring error propagationSmall errors compound in iterationsAnalyze error growth; use compensated summation
Assuming double precision is always sufficientSome problems require arbitrary precisionCheck if κ(A) × ε_mach > tolerance

Interview Questions

Q1: What is the difference between forward and backward error? A: Forward error measures how far the computed answer is from the true answer: |x̃ − x|. Backward error measures the smallest perturbation to the input that would produce the computed answer: find δ such that f(x + δ) = x̃. Backward error is often more meaningful because it tells you whether the computed answer is correct for a slightly different problem. A backward-stable algorithm produces the exact answer for a nearby problem.

Q2: Explain condition number and its practical significance. A: The condition number κ measures problem sensitivity. For a linear system Ax = b, κ(A) = ‖A‖·‖A⁻¹‖. If κ = 10^k, you lose about k digits of accuracy in the solution. For example, κ = 10^6 in double precision (15 digits) leaves about 9 correct digits. Large κ means the problem is ill-conditioned — no algorithm can produce accurate results without extra precision.

Q3: Why is Gaussian elimination with partial pivoting backward stable but the normal equations are not? A: Gaussian elimination with partial pivoting has a small backward error bound: ‖δA‖/‖A‖ ≤ n·ρ·ε. The normal equations (A^T A)x = A^T b square the condition number: κ(A^T A) = κ(A)². This means solving via normal equations loses twice as many digits, making it numerically unstable for ill-conditioned problems. Use QR factorization instead.

Q4: How do you choose the optimal step size for finite differences? A: The error has two components: truncation error O(h) and roundoff error O(ε/h). The total error is minimized when h_opt ≈ √(ε·|x|) for forward differences, or h_opt ≈ ε^(1/3)·|x| for central differences. For float64 (ε ≈ 10⁻¹⁶), h_opt ≈ 10⁻⁸ for forward and h_opt ≈ 10⁻⁵·|x| for central differences.

Q5: What is catastrophic cancellation and how do you detect it? A: Catastrophic cancellation occurs when subtracting nearly equal numbers, causing significant digits to cancel and amplifying relative error. Detect it by computing the condition number of the subtraction: κ = (|a| + |b|)/|a − b|. If κ ≫ 1, cancellation is occurring. Solutions: reformulate to avoid the subtraction, use higher precision, or apply algebraic identities (e.g., a² − b² = (a−b)(a+b)).

Q6: Explain the connection between condition number and floating point precision. A: If a problem has condition number κ, and the input has relative error ε, the output has relative error approximately κ·ε. In float64 with ε ≈ 10⁻¹⁶, a problem with κ = 10^10 loses 10 digits, leaving about 5 correct digits. If κ > 10^16, no digits are correct. This is why checking κ before solving is essential — it determines whether double precision is sufficient.


Practice Problems

📝Problem 1: Analyze Error Propagation in a Formula

Analyze the condition number of computing f(x) = (1 − cos(x))/x² near x = 0 and find a numerically stable reformulation.

💡Solution

import numpy as np


def naive_f(x):
    """Naive implementation — suffers from cancellation."""
    return (1 - np.cos(x)) / x**2


def stable_f(x):
    """Stable implementation using half-angle identity:
    1 - cos(x) = 2 sin²(x/2)
    """
    return 2 * np.sin(x / 2)**2 / x**2


# Test near x = 0 where cancellation is severe
print("f(x) = (1 - cos(x)) / x² near x = 0")
print(f"{'x':<14} {'Naive':<18} {'Stable':<18} {'True (Taylor)':<18}")
print("-" * 68)

# True value: f(0) = 1/2 (from Taylor expansion: cos(x) ≈ 1 - x²/2 + x⁴/24)
for x in [1, 0.1, 0.01, 1e-5, 1e-8, 1e-12, 1e-15]:
    naive = naive_f(x)
    stable = stable_f(x)
    # Taylor series: (1 - cos(x))/x² ≈ 1/2 - x²/24 + x⁴/720 - ...
    true_val = 0.5 - x**2 / 24 + x**4 / 720

    print(f"{x:<14.0e} {naive:<18.12f} {stable:<18.12f} {true_val:<18.12f}")

# Condition number of subtraction 1 - cos(x)
print("\nCondition number of 1 - cos(x):")
for x in [1, 0.1, 0.01, 1e-5, 1e-8]:
    a, b = 1, np.cos(x)
    kappa = (abs(a) + abs(b)) / abs(a - b)
    print(f"  x = {x:.0e}: κ = {kappa:.2e}")

📝Problem 2: Design a Numerically Stable Polynomial Evaluation

Compare naive polynomial evaluation with Horner's method and analyze their error growth.

💡Solution

import numpy as np


def naive_polynomial(coeffs, x):
    """Naive evaluation: Σ c_i * x^i."""
    result = 0.0
    for i, c in enumerate(coeffs):
        term = c * x**i  # Computing x**i from scratch each time
        result += term
    return result


def horner(coeffs, x):
    """Horner's method: (...((c_n * x + c_{n-1}) * x + c_{n-2}) * x + ...)"""
    result = 0.0
    for c in reversed(coeffs):
        result = result * x + c
    return result


# Test polynomial: (x-1)^10 expanded
coeffs = np.poly([1]*10)  # Coefficients of (x-1)^10

x = 1.00001  # Near the root
print("Polynomial: (x-1)^10 evaluated at x = 1.00001")
print(f"Naive:    {naive_polynomial(coeffs, x):.10f}")
print(f"Horner:   {horner(coeffs, x):.10f}")
print(f"Direct:   {(x - 1)**10:.10f}")

# Error analysis for large n
print(f"\nError growth analysis for x = 1 + h:")
for h in [1e-2, 1e-4, 1e-6, 1e-8, 1e-10]:
    x = 1 + h
    naive_val = naive_polynomial(coeffs, x)
    horner_val = horner(coeffs, x)
    true_val = h**10
    print(f"  h={h:.0e}: Naive err={abs(naive_val - true_val):.2e}, "
          f"Horner err={abs(horner_val - true_val):.2e}")

📝Problem 3: Analyze Stability of Recurrence Relations

The recurrence a₀ = 0, a₁ = 1, aₙ = 2.25aₙ₋₁ − 0.5aₙ₋₂ should produce bounded oscillating values, but naive computation diverges. Find a stable reformulation.

💡Solution

import numpy as np


def unstable_recurrence(n_terms):
    """Naive recurrence — unstable."""
    a = np.zeros(n_terms)
    a[0] = 0
    a[1] = 1

    for i in range(2, n_terms):
        a[i] = 2.25 * a[i-1] - 0.5 * a[i-2]

    return a


def stable_recurrence(n_terms):
    """Stable recurrence using correct characteristic roots.
    Roots of r² - 2.25r + 0.5 = 0 are r₁ = 2, r₂ = 0.25.
    General solution: aₙ = c₁·2ⁿ + c₂·(0.25)ⁿ
    With a₀=0, a₁=1: c₁ = 4/7, c₂ = -4/7
    """
    a = np.zeros(n_terms)
    c1 = 4/7
    c2 = -4/7

    for i in range(n_terms):
        a[i] = c1 * 2**i + c2 * (0.25)**i

    return a


print("Recurrence Relation Stability Analysis")
print(f"{'n':<6} {'Unstable':<18} {'Stable':<18} {'True':<18}")
print("-" * 60)

unstable = unstable_recurrence(20)
stable = stable_recurrence(20)

for i in range(min(15, len(unstable))):
    true_val = (4/7) * 2**i - (4/7) * (0.25)**i
    print(f"{i:<6} {unstable[i]:<18.6f} {stable[i]:<18.6f} {true_val:<18.6f}")

print(f"\nAt n=19: Unstable = {unstable[19]:.6f}, Stable = {stable[19]:.6f}")
print(f"True n=19: {(4/7)*2**19 - (4/7)*(0.25)**19:.6f}")

Quick Reference

📋Error Analysis Quick Reference

Error TypeSourceControl Method
TruncationFinite approximationHigher-order methods, smaller step size
RoundoffFinite precisionCompensated summation, higher precision
CancellationSubtracting nearly equal valuesReformulate, use higher precision
ConditioningProblem sensitivityPreconditioning, reformulation
StabilityAlgorithm amplificationUse backward-stable algorithms

Condition Number Guide:

κ(A)InterpretationDigits Lost (float64)
1–10Well-conditioned0–1
10²–10⁵Moderate2–5
10⁶–10¹⁰Ill-conditioned6–10
10¹¹–10¹⁵Severely ill-conditioned11–15
>10¹⁶Singular (in double precision)All

Optimal Finite Difference Steps:

  • Forward: h_opt ≈ √(ε_mach) · |x| ≈ 10⁻⁸ · |x|
  • Central: h_opt ≈ ε_mach^(1/3) · |x| ≈ 6×10⁻⁶ · |x|

Key Python Functions:

  • np.linalg.cond(A) — condition number
  • np.linalg.norm(A) — matrix/vector norm
  • np.linalg.solve(A, b) — linear system (uses LU with partial pivoting)
  • np.linalg.lstsq(A, b) — least squares (uses SVD)
  • np.linalg.eig(A) — eigenvalues (may be ill-conditioned)
  • np.linalg.svd(A) — singular value decomposition

Stability Hierarchy:

  1. Backward stable (best): QR, SVD, Householder
  2. Forward stable: LU with partial pivoting (usually)
  3. Conditionally stable: Some explicit ODE solvers
  4. Unstable: Naive normal equations, naive recurrence relations

Cross-References

  • 086-Floating-Point — Roundoff error is defined by IEEE 754 properties; machine epsilon determines achievable accuracy
  • 087-Root-Finding — Condition numbers determine sensitivity of root locations; Newton's method convergence depends on f''(x*)/f'(x*)
  • 088-Integration — Quadrature error analysis uses truncation error bounds; adaptive quadrature controls error via subdivision
  • 089-Interpolation — Interpolation error depends on derivatives of the function and node distribution
  • Linear Algebra — Condition numbers of matrices determine accuracy of linear system solutions, eigenvalue computations, and least squares
  • Optimization — Condition number of the Hessian determines convergence speed of gradient descent; preconditioning reduces effective condition number
  • ODE Solvers: Stability regions determine maximum step size; stiff systems require implicit solvers
Lesson Progress90 / 100