blockulator

Terminal precision

Nine scenarios, 64 inputs, four libraries, N from 1 to 10,000, every cell against 200-digit arithmetic and checkable on Sepolia.

Each scenario is N identical steps run through each library in its native representation, for several inputs, against the same loop in exact arithmetic. It is measured twice: against the exact inputs, which is what a protocol sees, and against the inputs as each format rounds them, which isolates the library's arithmetic. The chart is the summary over all inputs; the table is the reference input in full, and every cell of it can be re-run on Sepolia. "How this was measured" defines every number.

How this was measured
truth
For every scenario, input and N the same loop is run in exact real arithmetic with mpmath at 200 decimal digits (scripts/ladder/ladder.py), from the exact rational parameters in scenarios.json. Closed forms are used where the loop has one (compounding, the cumulative product, the geometric mean, the round trip); the bonding curve, the amortising loan and the Black-Scholes chain are stepped.
rounding
Each library receives the parameters floored into its own representation (2^-128, 2^-64 or 10^-18 units; scripts/ladder/inputs.json). truthFormat is the exact loop started from those floored parameters, so digitsFormat measures the library's arithmetic alone, while digits measures what a protocol sees: the format's rounding of the inputs plus the arithmetic.
digits
digits = floor(-log10 |v - t| / |t|), the number of correct leading significant digits by relative error, capped at 60, which is reported as exact. It is not a string comparison: a value can be correct to 36 digits while its 37th printed digit happens to agree with the truth, or while a carry makes the printed prefix differ earlier.
agree
agree is the count of leading significant digits of the printed value (42 significant digits) that are literally equal to the printed truth. The demo page colours the rest of the value with it. It is a reading aid, not a measurement: a carry across 0.999… / 1.000… gives 0.
gas
gasTotal is measured inside the adapter around the scenario function (the parameter reads and the loop), after the runner has warmed the adapter and the FP127 object, so the call into the adapter and the ABI decoding are excluded. The FP127 (deployed) form includes one staticcall per op; FP127Lib and the other libraries are internal calls. gasPerStep = gasTotal / N. Reverted cells carry no gas.
live
LadderRunner.run(scenario, form, n, p) on Sepolia executes any cell with one eth_call and returns (ok, raw, reason, gasPerStep, gasTotal). The demo's verify button sends the cell's floored parameters p and compares the returned raw word with the committed one bit for bit (or the revert selector for a reverted cell). liveMaxN is the largest N per form the public RPC completed within its gas limit, measured by scripts/ladder/livecap.py.
reproduce
make ladder regenerates ladder.json and ladder.md from the Foundry harness (test/ladder/Ladder.t.sol) and this script; make ladder-check fails CI when the committed dataset differs. Versions of every library and of forge are recorded in the dataset. Generated 2026-10-06; versions: abdk v3.2, forge forge Version: 1.7.1, fp127 f8e9bb1, huff sepolia:0xfae694D0c2c44181791F838c54Ed64C3151FfE30@11857616, prb-math v4.2.0, solady v0.1.26.

Daily compound interest

Step: b = b * (1 + r). Result: balance after N days.

Per-block or per-day accrual in every lending protocol: one mul per step, N steps.

Over all 10 inputs

Line: median correct digits over the inputs; band: the range; capped at 40 on the axis. FP127Lib coincides with FP127 and is not drawn. A missing point means every input reverted at that N. FP127 emerald, Solady gold, ABDK sapphire, PRBMath red, labelled at the line end; hover a point for the numbers.
NFP127FP127LibABDKSoladyPRBMath
1 38 (35–39) 38 (35–39) 19 (16–20) 18 (15–19) 18 (15–19)
2 38 (35–38) 38 (35–38) 19 (15–19) 18 (14–19) 18 (14–19)
5 38 (35–38) 38 (35–38) 18.5 (15–19) 18 (14–19) 18 (14–19)
10 37 (34–38) 37 (34–38) 18 (15–19) 17 (14–18) 17 (14–18)
20 37 (34–37) 37 (34–37) 18 (15–18) 17 (13–18) 17 (13–18)
50 37 (34–37) 37 (34–37) 17.5 (14–18) 17 (13–18) 17 (13–18)
100 36 (33–37) 36 (33–37) 17 (14–18) 16 (13–17) 16 (13–17)
365 36 (33–36) 36 (33–36) 16.5 (14–17) 16 (12–17) 16 (12–17)
1000 35 (32–36) 35 (32–36) 16 (13–17) 15 (12–16) 15 (12–16)
10000 34 (32–35) 34 (32–35) 15 (13–16) 14 (11–15) 14 (11–15)

Median (min–max) correct digits over the inputs, capped at 40; "rev" counts inputs that reverted at that N and are excluded.

The inputs and the ops
inputparameterswhy
reference referenceprincipal = 10000, rate = 0.1/365$10,000 at 10% APR, the headline cell
tiny-lowprincipal = 1, rate = 0.01/365one unit at 1%: the integer part is small, the fraction carries everything
tiny-highprincipal = 1, rate = 0.5/365one unit at 50%
mid-1pctprincipal = 10000, rate = 0.01/365a slow rate: the per-step factor is close to 1
mid-5pctprincipal = 10000, rate = 0.05/365a typical lending rate
mid-50pctprincipal = 10000, rate = 0.5/365a high rate: the balance grows 150x by N = 10,000
largeprincipal = 1000000, rate = 0.1/365a million-unit position
hugeprincipal = 100000000, rate = 0.1/365a hundred-million-unit pool
huge-highprincipal = 100000000, rate = 0.5/365the largest balance the ladder reaches, near 10^14
fractionprincipal = 0.001, rate = 0.1/365a sub-unit balance: leading zeros in WAD

Library calls per step: FP127 mul; FP127Lib mul; ABDK mul; Solady sMulWad; PRBMath mul. Parameters reach each adapter floored into its representation (ladder-inputs.json).

The reference input in full: principal = 10000, rate = 0.1/365

Each cell: the value with the digits that agree with the truth in ink and the rest in red; below it, correct digits against the exact truth, against the format-rounded truth, the signed error in units of the last place, and gas per step.

NtruthFP127 (deployed, staticcall)FP127Lib (inline)ABDKMath64x64Solady FixedPointMathLibPRBMath SD59x18
1 10002.7397260273972602739726027397260273973 10002.7397260273972602739726027397260273831
38 digits · exact vs format · -4810.96 ULP · 1871 gas/step
10002.7397260273972602739726027397260273831
38 digits · exact vs format · -4810.96 ULP · 928 gas/step
10002.7397260273972600785191700012433102529
19 digits · exact vs format · -3605.48 ULP · 934 gas/step
10002.73972602739726
19 digits · exact vs format · -273.973 ULP · 752 gas/step
10002.73972602739726
19 digits · exact vs format · -273.973 ULP · 1112 gas/step
2 10005.4802026646650403452805404391067742541 10005.4802026646650403452805404391067742258
38 digits · 42 vs format · -9625.4 ULP · 1606 gas/step
10005.4802026646650403452805404391067742258
38 digits · 42 vs format · -9625.4 ULP · 653 gas/step
10005.4802026646650399542451230494588543252
19 digits · 23 vs format · -7213.33 ULP · 609 gas/step
10005.480202664665039797
19 digits · 22 vs format · -548.281 ULP · 494 gas/step
10005.480202664665039797
19 digits · 22 vs format · -548.281 ULP · 854 gas/step
5 10013.7061382924386210524857642965155424808 10013.7061382924386210524857642965155424101
38 digits · 42 vs format · -24083.1 ULP · 1447 gas/step
10013.7061382924386210524857642965155424101
38 digits · 42 vs format · -24083.1 ULP · 488 gas/step
10013.7061382924386200740608440784562560566
19 digits · 23 vs format · -18048.8 ULP · 414 gas/step
10013.706138292438619679
18 digits · 21 vs format · -1373.49 ULP · 340 gas/step
10013.706138292438619679
18 digits · 21 vs format · -1373.49 ULP · 700 gas/step
10 10027.4310624075663873328585783270357249574 10027.4310624075663873328585783270357248157
37 digits · 41 vs format · -48232.5 ULP · 1394 gas/step
10027.4310624075663873328585783270357248157
37 digits · 41 vs format · -48232.5 ULP · 433 gas/step
10027.4310624075663853733086060271428863189
18 digits · 22 vs format · -36147.3 ULP · 349 gas/step
10027.431062407566384582
18 digits · 21 vs format · -2750.86 ULP · 288 gas/step
10027.431062407566384582
18 digits · 21 vs format · -2750.86 ULP · 648 gas/step
20 10054.9373711336135548503369243222913751056 10054.9373711336135548503369243222913748213
37 digits · 41 vs format · -96731.2 ULP · 1367 gas/step
10054.9373711336135548503369243222913748213
37 digits · 41 vs format · -96731.2 ULP · 405 gas/step
10054.937371133613550920398935217825453492
18 digits · 22 vs format · -72494.6 ULP · 317 gas/step
10054.937371133613549333
18 digits · 21 vs format · -5517.34 ULP · 262 gas/step
10054.937371133613549333
18 digits · 21 vs format · -5517.34 ULP · 622 gas/step
50 10137.9098421416552959332725454243516547326 10137.909842141655295933272545424351654016
37 digits · 41 vs format · -243824.0 ULP · 1351 gas/step
10137.909842141655295933272545424351654016
37 digits · 41 vs format · -243824.0 ULP · 389 gas/step
10137.9098421416552860271844023476450757926
18 digits · 21 vs format · -182735.0 ULP · 297 gas/step
10137.909842141655282022
17 digits · 20 vs format · -13911.3 ULP · 247 gas/step
10137.909842141655282022
17 digits · 20 vs format · -13911.3 ULP · 607 gas/step
100 10277.721596739264220164675884991779643531 10277.7215967392642201646758849917796420782
36 digits · 40 vs format · -494375.0 ULP · 1346 gas/step
10277.7215967392642201646758849917796420782
36 digits · 40 vs format · -494375.0 ULP · 383 gas/step
10277.7215967392642000792805576092625585716
17 digits · 21 vs format · -370510.0 ULP · 291 gas/step
10277.721596739264191958
17 digits · 20 vs format · -28206.7 ULP · 242 gas/step
10277.721596739264191958
17 digits · 20 vs format · -28206.7 ULP · 602 gas/step
365 11051.5578161626437393801159668054461177946 11051.5578161626437393801159668054461120925
36 digits · 40 vs format · -1.94032e+6 ULP · 1343 gas/step
11051.5578161626437393801159668054461120925
36 digits · 40 vs format · -1.94032e+6 ULP · 379 gas/step
11051.5578161626436605492209384882240641446
17 digits · 21 vs format · -1.45417e+6 ULP · 286 gas/step
11051.557816162643628692
16 digits · 19 vs format · -110688.0 ULP · 238 gas/step
11051.557816162643628692
16 digits · 19 vs format · -110688.0 ULP · 598 gas/step
1000 13151.2942008121758666272801482974151894245 13151.2942008121758666272801482974151708344
35 digits · 39 vs format · -6.32587e+6 ULP · 1343 gas/step
13151.2942008121758666272801482974151708344
35 digits · 39 vs format · -6.32587e+6 ULP · 378 gas/step
13151.294200812175609621539323218497585799
16 digits · 20 vs format · -4.74092e+6 ULP · 285 gas/step
13151.294200812175505839
16 digits · 19 vs format · -360788.0 ULP · 237 gas/step
13151.294200812175505839
16 digits · 19 vs format · -360788.0 ULP · 597 gas/step
10000 154769.34069144112806190764064318746174622 154769.340691441128061907640643187459558592
34 digits · 39 vs format · -7.44411e+8 ULP · 1360 gas/step
154769.340691441128061907640643187459558592
34 digits · 39 vs format · -7.44411e+8 ULP · 378 gas/step
154769.340691441097818583337575315539424992
15 digits · 20 vs format · -5.57891e+8 ULP · 285 gas/step
154769.340691441085644663
15 digits · 18 vs format · -4.24172e+7 ULP · 237 gas/step
154769.340691441085644663
15 digits · 18 vs format · -4.24172e+7 ULP · 597 gas/step

Verify calls run(scenario, form, n, p) on 0x798C37A3E5B421D480AC850258466b5598A1A555 through the public Sepolia RPC with the input's floored parameters and compares the raw word with the committed cell. liveMaxN is the largest N on the ladder that the public Sepolia RPC's eth_call completed for every input of that form. Above it the runner reports a bare revert because the adapter ran out of gas under the RPC's eth_call limits (calls needing more than about 40M gas, or a few seconds of execution, fail); the demo page greys out verify above it. Anyone with their own node can run every cell. The whole dataset is ladder.json; the generator is make ladder.