stdNormPDF
MathComputes the standard normal probability density function of x in 18-decimal fixed-point.
Avg. gas
320
Max abs. error
3.0e-16
Signature
function stdNormPDF(int256 x) internal pure returns (uint256 y)Parameters
| Name | Type | Description |
|---|---|---|
| x | int256 | Signed input in 18-decimal fixed-point format (1e18 = 1.0). |
Returns
| Name | Type | Description |
|---|---|---|
| y | uint256 | φ(x) in 18-decimal fixed-point format, in range [0, 398942280401432677] — the upper end is the peak 1/√(2π) at x = 0. |
Bounds
| Bound | Value |
|---|---|
| STD_NORM_PDF_BOUND | 9.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_E36 | 398942280401432677939946059934381868 — 1/√(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
0for|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 to0. - 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 x² 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
| Error | Trigger |
|---|---|
| None | Never reverts. Accepts the full int256 domain and saturates to 0 at the bound above. |
Example
import "defimath-lib/contracts/math/Math.sol";
int256 x = 1e18; // one standard deviation
uint256 y = Math.stdNormPDF(x); // y ≈ 0.24197072e18