Every number on this page is measured. The share of results off by a wei or more, and the worst error, come from the WAD results; the gas comes from the per-op benchmarks. Hover a row for the exact call.
From Solady
BlockulatorWad has the names, signatures, rounding directions and error selectors of Solady's FixedPointMathLib, so the move is one import:
import {BlockulatorWad as FixedPointMathLib} from "@blockulator/contracts/wad/BlockulatorWad.sol";
| Solady FixedPointMathLib | Blockulator | off by a wei: theirs | ours | worst, wei | gas: theirs | ours |
|---|---|---|---|---|---|---|
| Solady FixedPointMathLib, WAD | ||||||
mulWad | BlockulatorWad.mulWad | 13% | 0 | 0 | 86 | 107 |
mulWadUp | BlockulatorWad.mulWadUp | 13% | 0 | 0 | 110 | 128 |
divWad | BlockulatorWad.divWad | 33% | 0 | 0 | 87 | 103 |
divWadUp | BlockulatorWad.divWadUp | 33% | 0 | 0 | 113 | 128 |
sMulWad | BlockulatorWad.sMulWad | 11% | 0 | 0 | 126 | 142 |
sDivWad | BlockulatorWad.sDivWad | 34% | 0 | 0 | 93 | 133 |
sqrtWad | BlockulatorWad.sqrtWad | 0 | 0 | 0 | 393 | 388 |
cbrtWad | BlockulatorWad.cbrtWad | 0 | 0 | 0 | 619 | 760 |
expWad | BlockulatorWad.expWad | 48% | 0 | 6.16e+56 | 430 | 2,357 |
lnWad | BlockulatorWad.lnWad | 24% | 0 | 1 | 556 | 2,415 |
powWad | BlockulatorWad.powWad | 58% | 0 | 1.72e+64 | 1,275 | 4,762 |
lambertW0Wad | BlockulatorWad.lambertW0Wad | 28% | 0 | 1.54e+09 | 2,820 | 12,097 |
Solady's multiply and divide functions return exact values. Their share wrong is all reverts where x · y (or x · 1018 for a quotient) passes 2256 though the result fits, so their worst error is 0. The worst error is absolute: near 1074, one part in 1018 is many wei.
BlockulatorWad returns the exact value rounded once, and its domains only widen: mulWad and divWad revert only when the result does not fit, powWad(0, y) is defined, and expWad reaches one wei further (full list).
It needs the CLZ opcode: compile with evm_version = "osaka" and solc 0.8.31 or later. Keep Solady's integer helpers (abs, min, avg and the rest); they have no WAD meaning.
From PRBMath
PRBMath's values are user-defined types over the same 18-decimal integers, so a call moves by unwrapping. exp, ln and pow keep PRBMath's names. mul, div and sqrt become mulWad or sMulWad, divWad or sDivWad, and sqrtWad. log2 and log10 become log2Wad and log10Wad.
// SD59x18 x, y;
int256 r = BlockulatorWad.pow(x.unwrap(), y.unwrap()); // was x.pow(y)
| PRBMath SD59x18 and UD60x18 | Blockulator | off by a wei: theirs | ours | worst, wei | gas: theirs | ours |
|---|---|---|---|---|---|---|
| PRBMath SD59x18, WAD | ||||||
UD60x18.mul | BlockulatorWad.mulWad | 0 | 0 | 0 | 218 | 107 |
UD60x18.div | BlockulatorWad.divWad | 0 | 0 | 0 | 242 | 103 |
SD59x18.mul | BlockulatorWad.sMulWad | 0 | 0 | 0 | 481 | 142 |
SD59x18.div | BlockulatorWad.sDivWad | 0 | 0 | 0 | 494 | 133 |
UD60x18.sqrt | BlockulatorWad.sqrtWad | 23% | 0 | 0 | 800 | 388 |
SD59x18.exp | BlockulatorWad.expWad | 72% | 0 | 2.08e+59 | 2,731 | 2,357 |
SD59x18.ln | BlockulatorWad.lnWad | 98% | 0 | 27 | 7,004 | 2,415 |
SD59x18.log2 | BlockulatorWad.log2Wad | 93% | 0 | 19 | 6,660 | 2,160 |
SD59x18.log10 | BlockulatorWad.log10Wad | 88% | 0 | 14 | 8,744 | 2,368 |
SD59x18.pow | BlockulatorWad.powWad | 64% | 0 | 9.75e+64 | 9,913 | 4,762 |
| PRBMath UD60x18 | Blockulator | off by a wei: theirs | ours | worst, wei | gas: theirs | ours |
|---|---|---|---|---|---|---|
| PRBMath UD60x18, WAD | ||||||
UD60x18.exp | BlockulatorWad.expWad | 93% | 0 | 2.08e+59 | 2,635 | 2,357 |
UD60x18.ln | BlockulatorWad.lnWad | 98% | 0 | 27 | 6,438 | 2,415 |
UD60x18.log2 | BlockulatorWad.log2Wad | 90% | 0 | 18 | 6,306 | 2,160 |
UD60x18.log10 | BlockulatorWad.log10Wad | 90% | 0 | 14 | 8,392 | 2,368 |
UD60x18.pow | BlockulatorWad.powWad | 80% | 0 | 2.55e+59 | 9,323 | 4,762 |
From ABDK
Each of ABDK's two formats has a Blockulator counterpart of the same kind, and both use one int256 per value:
- ABDKMath64x64 moves to FP127: 127.128 fixed point, with 64 more fractional bits.
x << 64converts. - ABDKMathQuad's binary128 moves to FP19: a 256-bit binary float, with 237 bits of significand against 113.
| ABDKMath64x64 | FP127Lib | gas: theirs | ours |
|---|---|---|---|
| ABDKMath64x64, 64.64 fixed point | |||
exp(x) | FP127Lib.exp | 3,380 | 2,716 |
exp_2(x) | FP127Lib.exp2 | 3,225 | 2,668 |
ln(x) | FP127Lib.ln | 7,080 | 1,944 |
log_2(x) | FP127Lib.log2 | 6,990 | 1,723 |
sqrt(x) | FP127Lib.sqrt | 828 | 469 |
inv(x) | FP127Lib.inv | 202 | 242 |
abs(x) | FP127Lib.abs | 120 | 140 |
neg(x) | FP127Lib.neg | 76 | 116 |
add(a, b) | FP127Lib.add | 162 | 128 |
sub(a, b) | FP127Lib.sub | 162 | 140 |
mul(a, b) | FP127Lib.mul | 171 | 267 |
div(a, b) | FP127Lib.div | 221 | 412 |
avg(a, b) | FP127Lib.avg | 31 | 114 |
gavg(a, b) | FP127Lib.gavg | 833 | 1,079 |
pow(x, n), n a uint256 (n >= 0) | FP127Lib.powu | 3,133 | 5,189 |
| ABDKMathQuad | FP19Lib | gas: theirs | ours |
|---|---|---|---|
| ABDKMathQuad, IEEE binary128 | |||
exp(x) | FP19Lib.exp | 5,668 | 3,278 |
pow_2(x) | FP19Lib.exp2 | 5,025 | 3,096 |
ln(x) | FP19Lib.ln | 16,356 | 3,043 |
log_2(x) | FP19Lib.log2 | 15,553 | 2,822 |
sqrt(x) | FP19Lib.sqrt | 760 | 1,205 |
div(fromUInt(1), x) (composed) | FP19Lib.inv | 1,253 | 747 |
abs(x) | FP19Lib.abs | 21 | 133 |
neg(x) | FP19Lib.neg | 9 | 127 |
sign(x) | FP19Lib.sign | 181 | 139 |
add(a, b) | FP19Lib.add | 734 | 662 |
sub(a, b) | FP19Lib.sub | 1,230 | 745 |
mul(a, b) | FP19Lib.mul | 709 | 530 |
div(a, b) | FP19Lib.div | 818 | 800 |
exp(mul(y, ln(x))) (composed) | FP19Lib.pow | 23,467 | 5,901 |
cmp(a, b) | FP19Lib.cmp | 476 | 248 |
eq(a, b) | FP19Lib.eq | 108 | 176 |
cmp(a, b) < 0 (composed) | FP19Lib.lt | 488 | 233 |
toUInt(x) (truncates: floor for x >= 0) | FP19Lib.toUintDown | 240 | 405 |
toInt(x) (truncates: floor for x >= 0, the rows kept) | FP19Lib.toIntDown | 278 | 466 |
to128x128(x) (truncates; toFP127 rounds to nearest) | FP19Lib.toFP127 | 292 | 343 |
from128x128(x) (truncates; fromFP127 rounds to nearest) | FP19Lib.fromFP127 | 539 | 440 |
fromUInt(x) (truncates the significand) | FP19Lib.fromUintDown | 500 | 487 |
Gas is the inline library on both sides. FP127 and FP19 are also deployed as contracts you can staticcall; the library pages have the addresses.