Ask a computer for the derivative of at . Give it the familiar finite-difference formula and a very small step, say . In ordinary double precision, the answer is zero.
Now make that same step imaginary. The answer becomes , matching the usual double-precision value of .
You can try it with a few lines of Python:
import cmath
import math
x = 1.0
h = 1e-30
finite_difference = (math.exp(x + h) - math.exp(x)) / h
complex_step = cmath.exp(x + 1j * h).imag / h
print(finite_difference) # 0.0
print(complex_step) # 2.718281828459045This is complex-step differentiation. For a suitable function, a single evaluation at carries the derivative in its imaginary part. That small change can recover digits that a real finite difference has already lost.
The same idea can work when the function is an entire simulation: perturb a parameter, run the calculation, and extract the imaginary part of the output. The interesting questions are why such a tiny step survives, and what can erase it along the way.
Why a smaller step isn’t always better
The familiar starting point is
For a smooth function, a smaller reduces the error from approximating the derivative by a finite step. But the computer also rounds its calculations. Eventually, the difference we want becomes comparable to the rounding errors in the two function values.
In the opening example, the failure happens even earlier: adding to produces exactly in double precision. Both function evaluations receive the same input, so their difference is zero.
Using a central difference improves the approximation:
Its truncation error is proportional to , compared with for the forward difference. Yet it still subtracts nearly equal function values. A useful model for its total error is
where measures floating-point precision and the constants depend on the function and its scale. Decreasing reduces the first term while increasing the second. This is why finite-difference error curves often have a valley: there is a useful range of steps, followed by a region where taking smaller steps makes things worse.
A derivative estimate that changes wildly when you reduce the step may be running out of numerical precision. Smaller steps do not guarantee a better answer.
Put the step in the imaginary direction
Suppose is real-valued for real inputs and has an analytic extension near the point of interest, so we can expand it in a complex Taylor series:
The even powers contribute to the real part; the odd powers contribute to the imaginary part. Taking the imaginary part and dividing by gives
There is our derivative estimate:
Like the central difference, it is second-order accurate. Its numerical advantage comes from extracting a component of one function value, avoiding the subtraction between neighboring evaluations.
Complex arithmetic stores the real and imaginary components separately. In , the tiny perturbation occupies the imaginary component, where it does not have to compete with the real number for significant digits. A compatible implementation can carry that perturbation through the calculation and return an imaginary part approximately equal to .
Complex step still has truncation error. Its advantage is that you can often make that error negligible with a tiny step, without triggering the usual finite-difference cancellation.
For many well-behaved calculations, a step such as works in double precision. It is not a universal prescription: underflow, internal numerical errors, and the scale of the problem still matter. The code evaluating must preserve the small imaginary response accurately.
Watch the error curves
For a less trivial test, consider
We can check every numerical estimate against its exact derivative,
evaluated at . The relative error is
where is the numerical estimate.
The comparison includes forward and central differences, complex step, and two ways to improve the real finite-difference approximation. A fourth-order central stencil uses four function evaluations:
Richardson extrapolation instead combines central differences at two step sizes:
For sufficiently smooth functions, both cancel the leading second-order truncation error. They can therefore reach high accuracy with larger steps. Both also retain subtraction between nearby function values.

Read from right to left as the step decreases. The finite-difference curves reach a minimum and then rise. The complex-step curve settles near floating-point precision over a broad range of small steps.
The distinction is clearest on the left side of the plot. Higher-order formulas shift the useful range of finite-difference steps, but their errors eventually grow too. Complex step has already reached an accuracy plateau and stays there. This makes it particularly convenient when we want an accurate first derivative without searching for a narrow optimal step.
How one abs can ruin it
There is a catch worth seeing before applying the trick to a large code.
Consider at . The function is perfectly smooth in a neighborhood of that point, and . But Python's abs applied to a complex number returns its magnitude:
That result is real. Its imaginary part is zero, and the complex-step estimate is zero for every nonzero :
def g(z):
return abs(z)
h = 1e-30
print(g(0.5 + 1j * h).imag / h) # 0.0; the derivative is 1.0
The solid green complex-step curve stays at a relative error of one: the estimate is zero, while the true derivative is one. Central differences give accurate estimates over a range of step sizes.
The problem here is the extension used by the code. Near a positive real point, , whose analytic extension is simply . Complex magnitude computes a different function of and does not satisfy the assumptions behind the formula.
Accepting complex inputs is not enough. Operations such as complex magnitude, conjugation, extracting .real, or casting to a real data type can invalidate the derivative even when the original real function is smooth.
Branches, min/max, thresholds, and event detection also need attention. A fixed smooth branch can sometimes be handled correctly, but a change of branch may introduce a point where the ordinary derivative does not exist. Finite differences remain useful when only real evaluations are available; they still need care near such points.
When the function is a simulation
Suppose a parameter controls the growth rate in the logistic equation:
Our output is the solution at , , and we evaluate its derivative at . The numerical solution uses fourth-order Runge–Kutta with 4,000 fixed time steps. To apply complex step, we give the solver and compute
The imaginary perturbation now passes through every time step. The solver's state arrays and arithmetic must support complex values throughout.
This example has a closed-form solution, so we can check the numerical derivative independently:
Differentiating that expression gives the reference used in the plot:

The ODE comparison shows a broad region of small steps where complex step remains accurate, while the finite-difference errors grow.
Complex step differentiates the implemented calculation. Agreement with its derivative does not guarantee agreement with the exact differential equation: time steps, solver tolerances, and other approximation errors still matter.
This distinction is useful in practice. First check whether the differentiation method behaves consistently for a fixed calculation. Then refine the solver to see whether the derivative converges to the quantity you actually want.
Even when the mathematics supports complex step, numerical routines can introduce imaginary rounding noise that overwhelms the derivative signal at tiny steps. The implementation must preserve the small imaginary response, not merely accept complex inputs.
What about second derivatives?
Everything above concerns first derivatives. For a second derivative, the natural extension is
But now the information we need sits in the real part, alongside . Extracting it requires subtracting nearly equal numbers again: the small-step advantage is lost.
One way forward is to give the derivative another component to occupy. You may know quaternions as an extension of complex numbers. The multicomplex-step method uses a different algebra, with commuting imaginary units. Multicomplex numbers are defined recursively by
where and . The first new case is a bicomplex number:
For a suitable analytic function, the coefficient of now carries the second derivative:
where means “take the coefficient of .” No subtraction of the function value is needed. Additional commuting units extend the idea to higher derivatives; quaternion units do not obey the multiplication rules this construction needs.
The implementation is a separate challenge. Standard complex-number support is insufficient: arithmetic, elementary functions, and the solver must all handle multicomplex values. In practice, this may mean using or writing specialized libraries. The paper below develops the method; a working implementation deserves a post of its own.
Further reading
- J. R. R. A. Martins, P. Sturdza, and J. J. Alonso, “The Complex-Step Derivative Approximation”, ACM Transactions on Mathematical Software 29(3), 245–262 (2003).
- G. Lantoine, R. P. Russell, and T. Dargent, “Using Multicomplex Variables for Automatic Computation of High-Order Derivatives”, ACM Transactions on Mathematical Software 38(3), Article 16 (2012).

