Numerical Analysis Basics

Numerical Analysis Basics

Overview

Numerical analysis studies methods for approximately solving mathematical problems using computers. We will learn about floating-point representation, error analysis, numerical differentiation and integration, which form the foundation of simulation.


1. Floating-Point Representation

1.1 IEEE 754 Standard

import numpy as np
import struct

# Check floating-point bit representation
def float_to_bits(f):
    """Convert float64 to bit string"""
    packed = struct.pack('>d', f)
    bits = ''.join(f'{b:08b}' for b in packed)
    return f"Sign: {bits[0]} | Exponent: {bits[1:12]} | Mantissa: {bits[12:]}"

print(float_to_bits(1.0))
print(float_to_bits(-1.0))
print(float_to_bits(0.1))

# Machine epsilon
print(f"\nfloat64 machine epsilon: {np.finfo(np.float64).eps}")
print(f"float32 machine epsilon: {np.finfo(np.float32).eps}")

1.2 Numerical Limits

# Overflow and underflow
print("float64 range:")
print(f"  Minimum: {np.finfo(np.float64).min}")
print(f"  Maximum: {np.finfo(np.float64).max}")
print(f"  Smallest positive: {np.finfo(np.float64).tiny}")

# Precision loss example
a = 1e16
b = 1.0
print(f"\n1e16 + 1 - 1e16 = {(a + b) - a}")  # 0.0 (precision loss)
print(f"1 + 1e16 - 1e16 = {b + (a - a)}")    # 1.0 (correct result)

1.3 Rounding Error

# 0.1 cannot be represented exactly
x = 0.1
print(f"0.1 actual value: {x:.20f}")
print(f"0.1 + 0.2 = 0.3? {0.1 + 0.2 == 0.3}")  # False

# Use tolerance for comparison
print(f"np.isclose: {np.isclose(0.1 + 0.2, 0.3)}")

2. Error Analysis

2.1 Error Types

def analyze_error(true_value, approx_value):
    """Calculate absolute and relative error"""
    abs_error = abs(true_value - approx_value)
    rel_error = abs_error / abs(true_value) if true_value != 0 else float('inf')
    return abs_error, rel_error

# Example: π approximation
import math
approximations = [
    ("22/7", 22/7),
    ("355/113", 355/113),
    ("3.14159", 3.14159),
]

print("π approximation error analysis:")
for name, approx in approximations:
    abs_e, rel_e = analyze_error(math.pi, approx)
    print(f"  {name:10}: Absolute error={abs_e:.2e}, Relative error={rel_e:.2e}")

2.2 Numerical Stability

# Unstable computation example: small difference from large number
def unstable_subtract(x):
    """Numerically unstable subtraction"""
    return (1 + x) - 1

def stable_subtract(x):
    """Numerically stable form"""
    return x

x_values = [1e-15, 1e-16, 1e-17]
print("Small number subtraction comparison:")
for x in x_values:
    print(f"  x={x}: Unstable={unstable_subtract(x):.2e}, Stable={stable_subtract(x):.2e}")

2.3 Condition Number

# Matrix condition number
def analyze_condition_number():
    # Well-conditioned matrix
    A_good = np.array([[1, 0], [0, 1]])

    # Poorly-conditioned matrix
    A_bad = np.array([[1, 1], [1, 1.0001]])

    print("Condition number analysis:")
    print(f"  Identity matrix: {np.linalg.cond(A_good):.2f}")
    print(f"  Nearly singular matrix: {np.linalg.cond(A_bad):.2f}")

analyze_condition_number()

3. Numerical Differentiation

3.1 Finite Difference Method

def numerical_derivatives(f, x, h=1e-5):
    """Various finite difference formulas"""
    # Forward difference
    forward = (f(x + h) - f(x)) / h

    # Backward difference
    backward = (f(x) - f(x - h)) / h

    # Central difference - more accurate
    central = (f(x + h) - f(x - h)) / (2 * h)

    return forward, backward, central

# Test: f(x) = sin(x), f'(x) = cos(x)
x = np.pi / 4
true_deriv = np.cos(x)

forward, backward, central = numerical_derivatives(np.sin, x)

print(f"Derivative of sin(x) at x = π/4:")
print(f"  True value: {true_deriv:.10f}")
print(f"  Forward difference: {forward:.10f}, Error: {abs(forward - true_deriv):.2e}")
print(f"  Backward difference: {backward:.10f}, Error: {abs(backward - true_deriv):.2e}")
print(f"  Central difference: {central:.10f}, Error: {abs(central - true_deriv):.2e}")

3.2 Higher-Order Derivatives

def second_derivative(f, x, h=1e-5):
    """Second derivative (central difference)"""
    return (f(x + h) - 2*f(x) + f(x - h)) / h**2

# f(x) = sin(x), f''(x) = -sin(x)
x = np.pi / 4
true_second = -np.sin(x)
approx_second = second_derivative(np.sin, x)

print(f"\nSecond derivative:")
print(f"  True value: {true_second:.10f}")
print(f"  Approximation: {approx_second:.10f}")
print(f"  Error: {abs(approx_second - true_second):.2e}")

3.3 Effect of Step Size

def analyze_step_size():
    """Error analysis based on step size"""
    f = np.sin
    x = 1.0
    true_deriv = np.cos(x)

    h_values = np.logspace(-1, -15, 15)
    errors = []

    for h in h_values:
        central = (f(x + h) - f(x - h)) / (2 * h)
        errors.append(abs(central - true_deriv))

    return h_values, errors

h_values, errors = analyze_step_size()

import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.loglog(h_values, errors, 'bo-')
plt.xlabel('Step size h')
plt.ylabel('Error')
plt.title('Central Difference Error vs Step Size')
plt.grid(True)
plt.axvline(x=1e-8, color='r', linestyle='--', label='Near optimal')
plt.legend()
plt.show()
# Too small h: increased rounding error
# Too large h: increased truncation error

4. Numerical Integration

4.1 Trapezoidal Rule

def trapezoidal(f, a, b, n):
    """Integration using trapezoidal rule"""
    h = (b - a) / n
    x = np.linspace(a, b, n + 1)
    y = f(x)

    # Trapezoidal rule
    integral = h * (0.5 * y[0] + np.sum(y[1:-1]) + 0.5 * y[-1])
    return integral

# Test: ∫₀^π sin(x) dx = 2
result = trapezoidal(np.sin, 0, np.pi, 100)
print(f"∫₀^π sin(x) dx:")
print(f"  True value: 2.0")
print(f"  Trapezoidal (n=100): {result:.10f}")
print(f"  Error: {abs(result - 2.0):.2e}")

4.2 Simpson's Rule

def simpson(f, a, b, n):
    """Simpson's 1/3 rule (n must be even)"""
    if n % 2 != 0:
        n += 1

    h = (b - a) / n
    x = np.linspace(a, b, n + 1)
    y = f(x)

    # Simpson's rule: (h/3) * [y₀ + 4y₁ + 2y₂ + 4y₃ + ... + yₙ]
    integral = h/3 * (y[0] + 4*np.sum(y[1:-1:2]) + 2*np.sum(y[2:-1:2]) + y[-1])
    return integral

result_trap = trapezoidal(np.sin, 0, np.pi, 10)
result_simp = simpson(np.sin, 0, np.pi, 10)

print(f"\nComparison at n=10:")
print(f"  Trapezoidal: {result_trap:.10f}, Error: {abs(result_trap - 2.0):.2e}")
print(f"  Simpson: {result_simp:.10f}, Error: {abs(result_simp - 2.0):.2e}")

4.3 SciPy Integration

from scipy import integrate

# 1D integration
result, error = integrate.quad(np.sin, 0, np.pi)
print(f"\nscipy.integrate.quad:")
print(f"  Result: {result:.15f}")
print(f"  Estimated error: {error:.2e}")

# 2D integration
def f_2d(y, x):
    return x * y

result_2d, error_2d = integrate.dblquad(f_2d, 0, 1, 0, 1)
print(f"\n∫∫ xy dxdy (0~1):")
print(f"  Result: {result_2d:.10f}")  # 0.25

4.4 Convergence Analysis

def convergence_analysis():
    """Integration convergence analysis"""
    true_value = 2.0  # ∫₀^π sin(x) dx
    n_values = [4, 8, 16, 32, 64, 128, 256]

    trap_errors = []
    simp_errors = []

    for n in n_values:
        trap_errors.append(abs(trapezoidal(np.sin, 0, np.pi, n) - true_value))
        simp_errors.append(abs(simpson(np.sin, 0, np.pi, n) - true_value))

    # Estimate convergence order
    print("Convergence analysis:")
    print(f"{'n':>6} {'Trapezoidal':>12} {'Simpson':>12}")
    for i, n in enumerate(n_values):
        print(f"{n:>6} {trap_errors[i]:>12.2e} {simp_errors[i]:>12.2e}")

    # Trapezoidal: O(h²), Simpson: O(h⁴)
    return n_values, trap_errors, simp_errors

convergence_analysis()

5. Practice Problems

Problem 1: Numerical Differentiation

Calculate the derivative of f(x) = e^(-x²) at x=0.5 with various step sizes and analyze the error.

def exercise_1():
    f = lambda x: np.exp(-x**2)
    f_prime = lambda x: -2*x * np.exp(-x**2)  # Analytical derivative

    x = 0.5
    true_value = f_prime(x)

    # Solution
    h_values = np.logspace(-1, -12, 12)
    for h in h_values:
        approx = (f(x + h) - f(x - h)) / (2 * h)
        print(f"h={h:.0e}: Error={abs(approx - true_value):.2e}")

exercise_1()

Problem 2: Numerical Integration

Calculate ∫₀^1 e^(-x²) dx using trapezoidal and Simpson's rules.

def exercise_2():
    f = lambda x: np.exp(-x**2)

    # scipy reference value
    true_val, _ = integrate.quad(f, 0, 1)

    # Solution
    for n in [10, 50, 100]:
        trap = trapezoidal(f, 0, 1, n)
        simp = simpson(f, 0, 1, n)
        print(f"n={n}: Trapezoidal={trap:.8f}, Simpson={simp:.8f}")

    print(f"True value: {true_val:.8f}")

exercise_2()

Summary

Concept Key Content
Floating-point IEEE 754, machine epsilon, precision limits
Error types Truncation error, rounding error, condition number
Numerical differentiation Forward/backward/central difference, step size selection
Numerical integration Trapezoidal (O(h²)), Simpson (O(h⁴))
to navigate between lessons