blockulator

The ladder measures a slope. This measures the cliffs: ln within a per-second rate of one, exp(−42), a probability of 10−18, a year of seconds as an exponent, the normal tail where an option's price lives. A cell has its correct digits, or says why there are none: zero (below the format's floor), range (the argument does not fit the word), or the name of the error it reverts with. An empty cell is an op the library does not offer. BL127's own walls are here too: exp past ±88, the word at 1.7 × 1038, erfc past 9.3.

BL127's and BL19's deployed contracts checked live on Sepolia: 276 results at block 12,017,760, 0 mismatches. The other libraries' results reproduce locally with make ladder. 2026-10-29, runner 0xad11E2a6…. Re-run: uv run scripts/ladder/liveedges.py

How this was measured
digits
digits = floor(-log10 |v - t| / |t|), capped at 100 (reported as exact); errUlps is the signed error in units of the representation's last place (none for the floats).
gas
gas is measured inside the adapter around the op, after the adapter and the BL127 object are warm. The BL127 (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); scripts/ladder/liveedges.py sends every 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 BL127, 2^63 for 64.64, 2^128 as a bit pattern for binary128; BL19 holds every probe) 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 BL127 and BL19 have 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 BL127's, below 2^-128.
versions
Generated 2026-10-28; aave aave-v3-origin@8305565ae342 (MathUtils, transcribed), abdk v3.2, balancer balancer-v3-monorepo@2b9ce4b1b8ad (LogExpMath.sol, vendored), bl127 6c2eeaf, euler evk-periphery@af436991e135 (IRMAdaptiveCurve, ExpLib, transcribed), forge 1.7.1, fraxlend fraxlend@2bed49d4dcc6 (VariableInterestRate V2, transcribed), huff sepolia:0xfae694D0c2c44181791F838c54Ed64C3151FfE30@11857616, maker dss pot.sol rpow via solady v0.1.26, morpho morpho-blue@8e26ca6a8dbc (MathLib, transcribed), openzeppelin openzeppelin-contracts v5.7.0 (Math, SignedMath, vendored), prb-math v4.2.0, snekmate v0.1.2 (math module, vyper 0.4.3), solady v0.1.26, solmate solmate@89365b8 (no tag: v6 predates SignedWadMath), uniswap v4-core v4.0.0 (FullMath, vendored).

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.

Floating pointFixed point
probetruthBL19QuadBL12764.64SoladySolmatePRB SDPRB UDsnekBalancerOZUniswapMakerAaveMorphoFraxEuler
ln(1+1e-6)0.0000009999…65283413121211111212———————
ln(1+1e-9)
a per-second rate
0.0000000009…62253010998899———————
ln(1+1e-12)0.0000000000…5922267665565———————
ln(1+1e-15)9.9999999999…5619244151522153———————
ln(1+1e-18)
the last digit of an 18-decimal grid
9.9999999999…53162111818zerozero18zero———————
ln(1-1e-12)-0.000000000…592426612125revert1212———————
ln(1e-18)-41.44653167…7133223191919revert1920———————
ln(1e-30)-69.07755278…713310revertrevertrevertrevertrevertzerorevert———————
ln(1e30)69.077552789…713341range202018182020———————
ln(1e38)87.498233533…713340range202018182020———————
fewest digits5316100000000———————
Revert reasons
  • PRB UD: PRBMath_UD60x18_Log_InputTooSmall
  • 64.64: bare revert (require without a reason, or out of gas)
  • Solady: Solady LnWadUndefined()
  • Solmate: Error(string): a require with a message (Solmate's wadLn and snekmate's wad_ln below zero)
  • PRB SD: PRBMath_SD59x18_Log_InputTooSmall
  • Balancer: Balancer OutOfBounds()

A cell is the correct significant digits against the exact value; hover it for the value and its error in ULP. zero: below the format's floor; range: the argument does not fit the word; any other red word is a revert, named by its error (hover for the full reason). A — is an op the library does not offer. BL19's digits are shown to 71, its precision: a 72 or 73 is where the truth happens to sit near the grid. The last row is each library's fewest digits on the op, with a zero, revert or range counted as 0. Full dataset: edges.json. Regenerate with make edges.