blockulator

Edges

One call at the end of each library's domain: where a result goes to zero, reverts, or runs out of digits, through nine libraries against 200-digit arithmetic, checkable on Sepolia.

The ladder measures a slope: digits lost one step at a time. This page measures the cliffs. Each probe is one op evaluated once where a domain ends: a logarithm within a per-second rate of one, an exponential of −42, a probability of one in 10^18, a base a hair above one raised to a year of seconds, the tail of the normal distribution where an option's price lives. Every library runs the probe in its own representation and the result is compared with the exact value; a cell either has digits or says how the library had no answer: zero where the result fell below the format's floor, revert with the reason, range when the argument does not fit the word, n/a when the library has no way to compute the op. FP127's own ends are on the page too: exp at ±88, the word at 1.7 × 10^38, the tail of erfc at 9.3.

How this was measured
digits
digits = floor(-log10 |v - t| / |t|), capped at 60 (reported as exact); errUlps is the signed error in units of the representation's last place (none for binary128).
gas
gas is measured inside the adapter around the op, after the adapter and the FP127 object are warm. The FP127 (deployed) form includes the staticcall; the others are internal calls.
live
EdgeRunner.run(op, form, args) on Sepolia evaluates any cell with one eth_call and returns (ok, raw, reason, gas); the edges page's verify button sends the probe's encoded arguments and compares the raw word bit for bit.
range
RANGE means the argument does not fit the representation at all (above 2^127 for FP127, 2^63 for 64.64, 2^128 as a bit pattern for binary128) and nothing was called.
reproduce
make edges regenerates edges.json and edges.md from test/ladder/Edges.t.sol and this script; make edges-check fails CI when the committed dataset differs.
reverted
A revert is recorded with its selector and name. Unsupported means the library has no way to compute the op (the accrual libraries have no general ops; only FP127 has the secondary Lambert branch; the libraries without erf, erfc or normCdf run the same series and continued fraction through their own ops instead, so those cells are not Unsupported).
truth
Every probe is one op evaluated once. The truth is mpmath at 200 decimal digits from the exact arguments; truthFormat is the same op from the arguments as the representation holds them (floored on a grid, nearest for binary128), so digitsFormat measures the op alone and digits what a caller sees.
zero
A cell is marked zero when the library returned 0 and the truth is not 0: the result fell below the representation's floor. On an 18-decimal grid that is anything below 1e-18; on FP127's, below 2^-128.
versions
Generated 2026-10-15; aave aave-v3-origin@8305565ae342 (MathUtils, transcribed), abdk v3.2, balancer balancer-v3-monorepo@2b9ce4b1b8ad (LogExpMath.sol, vendored), forge forge Version: 1.7.1, fp127 2b291a7, huff sepolia:0xfae694D0c2c44181791F838c54Ed64C3151FfE30@11857616, maker dss pot.sol rpow via solady v0.1.26, morpho morpho-blue@8e26ca6a8dbc (MathLib, transcribed), prb-math v4.2.0, solady v0.1.26.

ln(x)

The logarithm near one is where a per-second rate lives: ln(1 + r) for r = 1e-9 has eight digits on an 18-decimal grid.

probetruthFP127ABDKSoladyPRBMathQuadBalancerAaveMorphoMaker
ln(1+1e-6) 0.0000009999995000003333… 34 -0.00841701 ULP
13 -1.66367 ULP
12 -0.333333 ULP
11 -8.33333 ULP
28
12 -0.333333 ULP
n/a
n/a
n/a
ln(1+1e-9)
a per-second rate
0.0000000009999999995000… 30 -0.323137 ULP
10 -1.48618 ULP
9 -0.5 ULP
8 -4.5 ULP
25
9 -0.5 ULP
n/a
n/a
n/a
ln(1+1e-12) 0.0000000000009999999999… 26 -0.627492 ULP
7 -1.0737 ULP
6 -1.0 ULP
5 -5.0 ULP
22
5 -2.0 ULP
n/a
n/a
n/a
ln(1+1e-15) 9.9999999999999950000000… 24 -0.146963 ULP
4 -1.74407 ULP
15 5.0e-13 ULP
2 -4.0 ULP
19
3 -1.0 ULP
n/a
n/a
n/a
ln(1+1e-18)
the last digit of an 18-decimal grid
9.9999999999999999950000… 21 -0.233424 ULP
1 -1.44674 ULP
18 5.0e-19 ULP
zero
16
zero
n/a
n/a
n/a
ln(1-1e-12) -0.000000000001000000000… 26 -0.909044 ULP
6 -1.92628 ULP
12 5.0e-7 ULP
5 4.0 ULP
24
12 5.0e-7 ULP
n/a
n/a
n/a
ln(1e-18) -41.44653167389282231232… 22 -3.74607e+17 ULP
3 -4.52243e+17 ULP
19 -0.676154 ULP
19 1.32385 ULP
33
20 0.323846 ULP
n/a
n/a
n/a
ln(1e-30) -69.07755278982137052053… 10 -9.20938e+29 ULP
revert bare revert (require without a reason, or out of gas)
revert unknown selector
revert unknown selector
33
revert Balancer OutOfBounds()
n/a
n/a
n/a
ln(1e30) 69.077552789821370520539… 41 -0.201036 ULP
range 20 -0.539744 ULP
18 8.46026 ULP
33
20 -0.539744 ULP
n/a
n/a
n/a
ln(1e38) 87.498233533773735992683… 40 0.478688 ULP
range 20 -0.683675 ULP
18 12.3163 ULP
33
20 -0.683675 ULP
n/a
n/a
n/a

Cells show correct significant digits against the exact value (hover for the value), or zero, revert, range, n/a as defined above. Verify calls run(op, form, args) on 0x4A32B0f99FC1e888bB9e5cb85399aDd7890B0957 through the public Sepolia RPC with the probe's encoded arguments and compares the raw word with the committed cell. Every cell was re-run through the live EdgeRunner on Sepolia by scripts/ladder/liveedges.py; the verify button repeats that call. The whole dataset is edges.json; the generator is make edges.