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,449
powWadBlockulatorWad.powWad58%01.72e+641,2754,796
lambertW0WadBlockulatorWad.lambertW0Wad28%01.54e+092,82012,130

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,449
SD59x18.log2BlockulatorWad.log2Wad93%0196,6602,194
SD59x18.log10BlockulatorWad.log10Wad88%0148,7442,402
SD59x18.powBlockulatorWad.powWad64%09.75e+649,9134,796
PRBMath UD60x18Blockulatoroff by a wei: theirsoursworst, weigas: theirsours
PRBMath UD60x18, WAD
UD60x18.expBlockulatorWad.expWad93%02.08e+592,6352,357
UD60x18.lnBlockulatorWad.lnWad98%0276,4382,449
UD60x18.log2BlockulatorWad.log2Wad90%0186,3062,194
UD60x18.log10BlockulatorWad.log10Wad90%0148,3922,402
UD60x18.powBlockulatorWad.powWad80%02.55e+599,3234,796

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 BL127: 127.128 fixed point, with 64 more fractional bits. x << 64 converts.
  • ABDKMathQuad's binary128 moves to BL19: a 256-bit binary float, with 237 bits of significand against 113.
ABDKMath64x64BL127Libgas: theirsours
ABDKMath64x64, 64.64 fixed point
exp(x)BL127Lib.exp3,3802,835
exp_2(x)BL127Lib.exp23,2252,712
ln(x)BL127Lib.ln7,0801,944
log_2(x)BL127Lib.log26,9901,723
sqrt(x)BL127Lib.sqrt828469
inv(x)BL127Lib.inv202242
abs(x)BL127Lib.abs120140
neg(x)BL127Lib.neg76116
add(a, b)BL127Lib.add162128
sub(a, b)BL127Lib.sub162140
mul(a, b)BL127Lib.mul171267
div(a, b)BL127Lib.div221413
avg(a, b)BL127Lib.avg31114
gavg(a, b)BL127Lib.gavg8331,003
pow(x, n), n a uint256 (n >= 0)BL127Lib.powu3,1335,252
ABDKMathQuadBL19Libgas: theirsours
ABDKMathQuad, IEEE binary128
exp(x)BL19Lib.exp5,6683,278
pow_2(x)BL19Lib.exp25,0313,096
ln(x)BL19Lib.ln16,3563,106
log_2(x)BL19Lib.log215,5532,885
sqrt(x)BL19Lib.sqrt7601,205
div(fromUInt(1), x) (composed)BL19Lib.inv1,253747
abs(x)BL19Lib.abs21133
neg(x)BL19Lib.neg9127
sign(x)BL19Lib.sign181139
add(a, b)BL19Lib.add734662
sub(a, b)BL19Lib.sub1,230745
mul(a, b)BL19Lib.mul709530
div(a, b)BL19Lib.div818800
exp(mul(y, ln(x))) (composed)BL19Lib.pow23,4795,964
cmp(a, b)BL19Lib.cmp476248
eq(a, b)BL19Lib.eq108176
cmp(a, b) < 0 (composed)BL19Lib.lt488233
toUInt(x) (truncates: floor for x >= 0)BL19Lib.toUintDown240405
toInt(x) (truncates: floor for x >= 0, the rows kept)BL19Lib.toIntDown278466
to128x128(x) (truncates; toBL127 rounds to nearest)BL19Lib.toBL127292343
from128x128(x) (truncates; fromBL127 rounds to nearest)BL19Lib.fromBL127539440
fromUInt(x) (truncates the significand)BL19Lib.fromUintDown500487

Gas is the inline library on both sides. BL127 and BL19 are also deployed as contracts you can staticcall; the library pages have the addresses.