stdNormPDF

Math

Computes the standard normal probability density function of x in 18-decimal fixed-point.

Avg. gas

320

Max abs. error

3.0e-16

Signature

solidity
function stdNormPDF(int256 x) internal pure returns (uint256 y)

Parameters

NameTypeDescription
xint256Signed input in 18-decimal fixed-point format (1e18 = 1.0).

Returns

NameTypeDescription
yuint256φ(x) in 18-decimal fixed-point format, in range [0, 398942280401432677] — the upper end is the peak 1/√(2π) at x = 0.

Bounds

BoundValue
STD_NORM_PDF_BOUND9.1e18 — saturation magnitude. At |x| ≥ STD_NORM_PDF_BOUND, φ(x) is below 1e-18 and so is already 0 in 18-decimal fixed-point; the function short-circuits rather than running the exponential. It also bounds the x · x squaring away from overflow.
INV_SQRT_2PI_E363989422804014326779399460599343818681/√(2π) at 36 decimals, so a single divide by the 18-decimal exponential yields an 18-decimal result.

Behavior

  • Even: φ(−x) = φ(x), bit-for-bit. The sign is folded away before any arithmetic, so both halves run the identical code path.
  • Saturates gracefully (no revert): returns 0 for |x| ≥ 9.1. The density has already underflowed below one wei by |x| ≈ 9.0024, so the cap introduces no discontinuity.
  • Strictly decreasing in |x| until it underflows to 0.
  • Unlike stdNormCDF and erf, no rational approximation is involved — the density is one exponential, which is why it costs about half of Φ(x).
  • Pure assembly hot path; no external calls or storage.

How it works

The standard normal density has a closed form with no special function in it at all:

φ(x) = e^(−x²/2) / √(2π)

That makes it structurally cheaper than its integral. stdNormCDF needs West's rational approximation — two degree-5 polynomials — plus an exponential. The density needs only the exponential, so the whole function is a fold, a guard, one exp, and one divide.

Three details carry the gas number. First, the sign folds away for free. Because is sign-free, squaring happens in signed space and the absolute value is never needed on the value path — only the saturation guard looks at |x|. That removes the mirrored positive/negative branches that stdNormCDF and erf both carry; measured against a two-branch implementation, folding the sign is ~6 gas cheaper.

Second, the negative exponent is inverted rather than evaluated. Instead of computing e^(−x²/2), DeFiMath computes e^(+x²/2) through expPositive — the internal unguarded fast path — and divides. The reciprocal costs nothing extra because a divide was needed anyway for the 1/√(2π) normalisation.

Third, the normalising constant is carried at 36 decimals, not 18:

INV_SQRT_2PI_E36 = 398942280401432677939946059934381868

y = INV_SQRT_2PI_E36 / e^(x²/2)

Since the exponential is an 18-decimal value, dividing a 1e36-scaled numerator by it lands directly on an 18-decimal result — one DIV, no rescaling multiply, and the 18 extra digits keep the quotient exact to the wei. The halving rides along in the same descale (x · x / 2e18 rather than a divide followed by a shift), which floor-division makes exactly equivalent.

The tail is a plain guard: at |x| = 9.1 the true density is ≈ 4.2e-19, already under one wei, so the function returns 0 without running the exponential. That guard doubles as the overflow check on the squaring. Net cost on the hot path: ~320 gas, roughly half of Φ(x) at 618.

Errors

ErrorTrigger
NoneNever reverts. Accepts the full int256 domain and saturates to 0 at the bound above.

Example

solidity
import "defimath-lib/contracts/math/Math.sol";

int256  x = 1e18;                 // one standard deviation
uint256 y = Math.stdNormPDF(x);   // y ≈ 0.24197072e18