Finite Differences
Derivatives without differentiating: slope from neighbouring values. Central beats one-sided, and shrinking the step eventually backfires.
Why Does This Exist?
Autodiff is unavailable more often than textbooks admit: black-box simulators, legacy Fortran, hardware in the loop, quick sanity checks of hand-derived gradients. Finite differences estimate from neighbouring values of alone, no symbolic work. Forward, backward and central formulas trade one extra evaluation for an order of accuracy, and the central difference's error makes it the default checker behind every gradient verification routine.
The method is also the seed of numerical differential equations: replace derivatives with differences and a differential equation becomes algebra on a grid. Derivatives and partial derivatives define the target; Taylor series supply the error analysis; gradients shows the checking workflow this enables.
The one idea you need first
Taylor's expansion rearranges into slope estimates: each formula below is Taylor solved for with the remainder kept as the error term. The order of the first surviving remainder term names the method's accuracy.
Think of It Like This
Speed from two photographs
A car's speed at noon cannot be photographed, but two timestamped photos give average speed over the gap. Both photos after noon (forward difference) measures where the car is heading, including its acceleration over the gap: biased when speed changes. One photo before and one after (central difference) straddles noon, and the acceleration contaminates both sides nearly equally, cancelling most of the bias.
That cancellation is the whole accuracy gap: symmetric sampling kills the leading error term for free. The price is one extra "past" evaluation, and the limit is photo resolution: timestamps closer than the clock's precision subtract into noise.
How It Actually Works
The three formulas with their errors:
- Forward: . One-sided, first-order. The only option at domain boundaries.
- Backward: . Mirror image, same accuracy.
- Central: . Symmetric, second-order: the terms cancel, leaving .
Halving halves forward error but quarters central error. Total error is U-shaped: truncation falls as shrinks while rounding (catastrophic cancellation in the subtraction) rises, so floats bottom out near for forward and -ish for central in doubles. Smaller is not better.
Worked example
Differentiate at (truth ) with . Values: , .
- Forward: . Error .
- Central: . Error .
Same step, sixty times less error, one extra evaluation. The versus labels predict exactly this ratio: against .
Code
The point is the check: confirm the accuracy gap and the U-shaped failure at tiny .
def diffs(f: list[float], h: float) -> tuple[float, float]: fwd = (f[1] - f[0]) / h # forward needs f(x), f(x+h) cen = (f[2] - f[0]) / (2 * h) # central needs f(x-h), f(x+h) return (fwd, cen)
vals = [8.0, 9.261, 6.859] # f(2), f(2.1), f(1.9) for x cubedprint([round(v, 2) for v in diffs(vals, 0.1)]) # -> [12.61, 12.01]Watch Out For
Shrinking h past the rounding floor
Below in doubles, and share their first eight digits and the subtraction keeps mostly rounding noise, amplified by division with . The check then fails on correct gradients and misleads the debugging that follows. Park forward checks at , central at , and compare relatively.
Checking in float32
Single precision carries 7 digits, so the rounding floor arrives a thousand times sooner and no is simultaneously small enough for truncation and large enough for rounding. Gradient checks belong in float64, period. Cast the function up for the check even when production runs in float32 or below.
The Quick Version
- Forward and backward differences err at ; central differences at for one extra evaluation.
- On at 2 with : forward 12.61, central 12.01, truth 12.
- Total error is U-shaped in : truncation versus cancellation, bottoming near .
- Never gradient-check in float32; cast up to float64 for the check.
- Symmetric sampling cancels the leading error: straddle the point whenever values exist both sides.