Skip to content

The error function

@ac-kit/math-analysisPortable

import { erf, erfc } from "@ac-kit/math-analysis";

Where you meet it. “Three sigma” is a claim about the error function. So is every p-value from a normal test, every process-capability index on a factory floor, and the confidence interval on any measurement assumed to be normally distributed. It also turns up far from statistics — heat diffusion and signal attenuation are described by the same integral.

Formally, erf() computes erf(x) = 2/√π ∫₀ˣ e^(−t²) dt, which is the probability that a standard normal measurement lands within x√2 of its mean.

erfc() computes 1 − erf(x) — the area outside the band rather than inside it. That is the quantity you want for a tail probability: “how unlikely is a result at least this extreme?”

It exists as its own function because computing it as a subtraction destroys it. erf(x) reaches exactly 1 in floating point at about x = 6, and 1 - 1 is 0. Everything past that point — every tail probability smaller than about 1e-17 — is gone.

Computed directly, erfc keeps full relative precision all the way down to the underflow limit near x = 26.5, which is what makes a tail probability of 1e-100 a number rather than a zero.

EvidencePast x ≈ 6 the naive 1 − erf(x) returns exactly zero, while erfc(x) stays positive below 1e-100.

Why erfc is not 1 − erf

Both curves are the same mathematical quantity, plotted on a logarithmic axis. The naive 1 − erf(x) dies at x ≈ 6, where erf(x) rounds to exactly 1 and the subtraction returns exactly 0 — there is nothing left to take a logarithm of, so its line simply ends. erfc(x) routes the positive branch through the upper incomplete gamma instead and keeps full relative precision down to the underflow limit near x = 26.5. That gap is what makes a normal tail probability of 1e-100 computable at all.

math-analysis/erf-erfc.example.ts
const xs = linspace(0, upper, samples);
const frame = curveFrame(
xs,
[
{ name: "erfc(x)", values: xs.map((x) => erfc(x)) },
{ name: "1 − erf(x)", values: xs.map((x) => 1 - erf(x)) },
],
{ x: "x", y: "tail", series: "method" },
);

The rule generalises across this whole package:

Reach for the upper form when the answer is small.

The same pairing appears in the incomplete gamma family, for exactly the same reason.

Further reading