blockulator

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 FixedPointMathLibBlockulatoroff by a wei: theirsoursworst, weigas: theirsours
Solady FixedPointMathLib, WAD
mulWadBlockulatorWad.mulWad13%0086107
mulWadUpBlockulatorWad.mulWadUp13%00110128
divWadBlockulatorWad.divWad33%0087103
divWadUpBlockulatorWad.divWadUp33%00113128
sMulWadBlockulatorWad.sMulWad11%00126142
sDivWadBlockulatorWad.sDivWad34%0093133
sqrtWadBlockulatorWad.sqrtWad000393388
cbrtWadBlockulatorWad.cbrtWad000619760
expWadBlockulatorWad.expWad48%06.16e+564302,357
lnWadBlockulatorWad.lnWad24%015562,415
powWadBlockulatorWad.powWad58%01.72e+641,2754,762
lambertW0WadBlockulatorWad.lambertW0Wad28%01.54e+092,82012,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 UD60x18Blockulatoroff by a wei: theirsoursworst, weigas: theirsours
PRBMath SD59x18, WAD
UD60x18.mulBlockulatorWad.mulWad000218107
UD60x18.divBlockulatorWad.divWad000242103
SD59x18.mulBlockulatorWad.sMulWad000481142
SD59x18.divBlockulatorWad.sDivWad000494133
UD60x18.sqrtBlockulatorWad.sqrtWad23%00800388
SD59x18.expBlockulatorWad.expWad72%02.08e+592,7312,357
SD59x18.lnBlockulatorWad.lnWad98%0277,0042,415
SD59x18.log2BlockulatorWad.log2Wad93%0196,6602,160
SD59x18.log10BlockulatorWad.log10Wad88%0148,7442,368
SD59x18.powBlockulatorWad.powWad64%09.75e+649,9134,762
PRBMath UD60x18Blockulatoroff by a wei: theirsoursworst, weigas: theirsours
PRBMath UD60x18, WAD
UD60x18.expBlockulatorWad.expWad93%02.08e+592,6352,357
UD60x18.lnBlockulatorWad.lnWad98%0276,4382,415
UD60x18.log2BlockulatorWad.log2Wad90%0186,3062,160
UD60x18.log10BlockulatorWad.log10Wad90%0148,3922,368
UD60x18.powBlockulatorWad.powWad80%02.55e+599,3234,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 << 64 converts.
  • ABDKMathQuad's binary128 moves to FP19: a 256-bit binary float, with 237 bits of significand against 113.
ABDKMath64x64FP127Libgas: theirsours
ABDKMath64x64, 64.64 fixed point
exp(x)FP127Lib.exp3,3802,716
exp_2(x)FP127Lib.exp23,2252,668
ln(x)FP127Lib.ln7,0801,944
log_2(x)FP127Lib.log26,9901,723
sqrt(x)FP127Lib.sqrt828469
inv(x)FP127Lib.inv202242
abs(x)FP127Lib.abs120140
neg(x)FP127Lib.neg76116
add(a, b)FP127Lib.add162128
sub(a, b)FP127Lib.sub162140
mul(a, b)FP127Lib.mul171267
div(a, b)FP127Lib.div221412
avg(a, b)FP127Lib.avg31114
gavg(a, b)FP127Lib.gavg8331,079
pow(x, n), n a uint256 (n >= 0)FP127Lib.powu3,1335,189
ABDKMathQuadFP19Libgas: theirsours
ABDKMathQuad, IEEE binary128
exp(x)FP19Lib.exp5,6683,278
pow_2(x)FP19Lib.exp25,0253,096
ln(x)FP19Lib.ln16,3563,043
log_2(x)FP19Lib.log215,5532,822
sqrt(x)FP19Lib.sqrt7601,205
div(fromUInt(1), x) (composed)FP19Lib.inv1,253747
abs(x)FP19Lib.abs21133
neg(x)FP19Lib.neg9127
sign(x)FP19Lib.sign181139
add(a, b)FP19Lib.add734662
sub(a, b)FP19Lib.sub1,230745
mul(a, b)FP19Lib.mul709530
div(a, b)FP19Lib.div818800
exp(mul(y, ln(x))) (composed)FP19Lib.pow23,4675,901
cmp(a, b)FP19Lib.cmp476248
eq(a, b)FP19Lib.eq108176
cmp(a, b) < 0 (composed)FP19Lib.lt488233
toUInt(x) (truncates: floor for x >= 0)FP19Lib.toUintDown240405
toInt(x) (truncates: floor for x >= 0, the rows kept)FP19Lib.toIntDown278466
to128x128(x) (truncates; toFP127 rounds to nearest)FP19Lib.toFP127292343
from128x128(x) (truncates; fromFP127 rounds to nearest)FP19Lib.fromFP127539440
fromUInt(x) (truncates the significand)FP19Lib.fromUintDown500487

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.