blockulator

FP19 is a float, so its digits move with the value: about 71 at any size, where FP127 keeps 38 at 1 and 8 at 10−30. Every op is correctly rounded: none of 360,590 random inputs was misrounded (sweep).

The format

A value is one int256: the sign in bit 255, a 19-bit exponent (bias 262,143) and a 236-bit fraction with an implicit leading one. That is 237 significant bits, about 71 digits, from 10−78,913 to 1078,913.

value = (-1)^s (1 + F / 2^236) 2^(E - 262143), with s the sign, E the exponent and F the fraction.

There is no NaN, infinity, subnormal or negative zero. Zero is all zero bits, any other value with exponent 0 reverts with OutOfRange, and two values are equal exactly when their bits are. Each op rounds once, ties away from zero. Overflow reverts with Overflow, underflow rounds to zero, and division by zero reverts with DivisionByZero. ONE is 0x3ffff << 236.

digitsrangeaddmuldivexplnpowsqrt
FP197110±78,9136625308003,2783,0435,9011,205
FP12738 at 12−128 to 2127 (±1.7 × 1038)1282554122,7051,9343,409404
ABDKMathQuad3410±4,9327347098185,66816,35623,467760

Inline library, median gas per call on the same inputs (full table). FP19 carries twice binary128's digits and costs less than Quad on every op here but sqrt. FP127 costs less than FP19 on every op here, with fewer digits.

The deployed contracts

network
sepolia (chain id 11155111)
address
0xAa1f71B7B08d568379b28E1488c6B737a1C6eb52
verified
Sourcify, Blockscout. Etherscan has no Yul path.
Deployment record
deployer
0x4e59b44847b379578588920cA78FbF26c0B4956C (the CREATE2 proxy)
salt
"FP256 v5" = 0x4650323536207635000000000000000000000000000000000000000000000000
init code hash
0x476685947058e5d24adf3a1c1cdff1e30c4e5fee3f31cb82939ab3368881ce7c
runtime
10333 bytes, keccak 0xc8e37d8828b81656310c3f66ce7615a0cb14d464c6bc601459b368e379eaed95
compiler
Yul, solc 0.8.38+commit.1835d247, optimizer 200 runs, osaka
deployed
block 11974969, source commit d37c8b1
record
FP19.md, fp19.json

FP19 v5 (Blockulator 2.2) is four contracts from one source. The record above is the FP19 contract's; FP19.md has all four.

contractwhat it holdsaddressops
FP19the number0xAa1f71B7B08d568379b28E1488c6B737a1C6eb52add sub mul div inv sqrt cbrt hypot gavg; neg abs sign; cmp lt eq min max clamp; floor ceil round frac; avg dist zeroFloorSub lerp; mulDown mulUp divDown divUp; every conversion
FP19Ethe elementary functions, on one exp/log kernel0x017F68A57dD53a6dF2043c24B712b287F9549AE2exp exp2 exp10 expm1 ln log2 log10 log2Up log1p pow powu compound
FP19Hthe hyperbolic functions0xEfefa2B63A173d56F9db79C5dd8A607F4aA859A5sinh cosh tanh atanh
FP19Sthe special functions0x73E8809F163d4358773cc5F25c134997B4b842F4erf erfc normCdf lambertW0 lambertWm1 factorial

FP19H has its own copy of the exp/log kernel because FP19E with the hyperbolics was over the 24,576-byte limit. pi is the constant FP19Lib.PI, and gcd was removed in v5. The inline library holds all 65 ops.

The contracts need the CLZ opcode (EIP-7939, evmVersion osaka), so they do not run on zkSync Era, Avalanche C-Chain or Polygon zkEVM (the list). FP19 v4 (Blockulator 2.1: FP19, FP19X, FP19X2 and FP19X3) and v3 (Blockulator 2.0) stay live at the addresses in the record.

Install

The import path is the same however you install it:

# Foundry, from git
forge install securedataresearch/blockulator
echo "@blockulator/contracts/=lib/blockulator/contracts/src/" >> remappings.txt

# npm, yarn or pnpm (Hardhat needs no remapping; Foundry reading node_modules needs this one)
npm install @blockulator/contracts
echo "@blockulator/contracts/=node_modules/@blockulator/contracts/" >> remappings.txt

# Soldeer
forge soldeer install blockulator~<version>
echo "@blockulator/contracts/=dependencies/blockulator-<version>/" >> remappings.txt

Compile FP19Lib.sol with via_ir = true in foundry.toml. Through the IR, add drops from 677 to 545 gas and exp from 3,277 to 2,848 (profile.md).

import {FP19Lib} from "@blockulator/contracts/fp19/FP19Lib.sol";

// a per-second rate over a year, with the rate itself held to 71 digits
int256 r = FP19Lib.div(FP19Lib.fromFixed18(0.05e18), FP19Lib.fromUint(31536000));
int256 g = FP19Lib.compound(r, 31536000);   // (1 + r)^n without ever rounding 1 + r
int256 balanceWad = FP19Lib.toFixed18(FP19Lib.mul(FP19Lib.fromFixed18(principalWad), g));

fromFixed18 and toFixed18 convert to and from WAD. fromFP127 is exact below 2109 and rounds once above; toFP127 rounds to the nearest 2−128.

Typed API

FP19Type.sol is opt-in: type FP19 is int256, with arithmetic and comparison operators bound to the library and every other op attached (x.exp(), a.mulUp(b)). fp19FromWad, fp19FromInt and fp19FromUint convert in, toWadDown, toUintUp and the rest convert out, toFP127 and toFP19 cross to FP127's type, and ONE and PI are typed constants. The wrapper only re-types, so results and reverts are FP19Lib's, and through the IR the gas is the same.

import {FP19, ONE, fp19FromWad, fp19FromInt} from "fp19/FP19Type.sol";
import {FP127} from "fp127/FP127Type.sol";

FP19 b = fp19FromWad(1.5e18) * ONE.exp();  // 1.5e; * is FP19Lib.mul
bool between = b > fp19FromInt(4) && b < fp19FromInt(5);
uint256 charged = b.toUintUp();              // 5, ceiled
FP127 n = b.toFP127();                       // the nearest FP127 value

Integrating

Two rules, each from the replay of a real exploit (integrating has the replays and code).

  1. Store the state in the format. Keep a running balance, index or accumulator as an FP19 value, and round to token units only on the way out. In Bunni v2's replay this keeps a balance the attack drained from 28 wei to 4 at 35.6 wei, as exact arithmetic does.
  2. Round toward the protocol at the token edge. Round down what you pay out and round up what you take in: toUintDown and toUintUp, toFixed18Down and toFixed18Up, mulDown and mulUp, divDown and divUp. Under this rule Resupply's borrow against 1 wei of collateral reverts.

The ops

Each op's worst-case precision against its gas. Hover a dot for the op.

opwhatdomainrevertsbits (min/mean)gas, objectgas, inlinebytes inline
add(int256,int256)
0xa5f3c23b
a + b, rounded once (half up on the magnitude). The result is exact whenever it is representable; a - a is +0. any two numbers Overflow, OutOfRange CR 1,126 662632
sub(int256,int256)
0xadefc37b
a - b, rounded once. Exact whenever representable (Sterbenz: always for b/2 <= a <= 2b). any two numbers Overflow, OutOfRange CR 1,190 745633
mul(int256,int256)proven
0xbbe93d91
a * b: the 474-bit product of the significands, rounded once. any two numbers Overflow, OutOfRange CR 1,064 530337
div(int256,int256)proven
0x43509138
a / b: floor(ma 2^255 / mb) by one exact 512-by-256 division, rounded once. b != 0 DivisionByZero, Overflow, OutOfRange CR 1,328 800444
sqrt(int256)
0x665df460
sqrt(x)sqrt(x): the significand shifted to an even exponent, its integer square root at 2^136 (Newton: the word root of the top 256 bits, one residual step in a word, corrected by squaring), rounded once. Never overflows or underflows.
x >= 0 OutOfRange CR 1,685 1,205531
exp(int256)
0xe46751e3
e^x = 2^(x log2 e)e^x = 2^(x log2 e): the 512-bit product of the significand and log2 e in two words (2^-508), split into the integer part and a fraction at 2^-254, then the 2^f kernel (within 6.1 units of 2^-254) and one rounding when that bound clears the rounding boundary, else e^r - 1 in two words for r = x - k ln 2: correctly rounded. Below 2^-118, 1 + x + x^2/2 floored exactly on the 2^-254 grid (the cubic only breaks ties) and rounded once; a positive x whose lowest bit is 2^-118, below 2^-84, makes that sum an exact tie the kernel cannot see past, so it takes the same path. Exactly one below 2^-238; zero below about -181,700.
x < 2^18 Overflow, OutOfRange CR 3,319 3,2786,232
ln(int256)
0x8e6f2353
ln(x) = (E - bias + log2(M / 2^236)) ln 2ln(x) = (E - bias + log2(M / 2^236)) ln 2: the exponent exactly, the mantissa through the log2 kernel at 2^-254, the sum scaled to fill a word, one product with ln 2 and one rounding when its 5 units clear the rounding boundary, else u ln 2 + ln(mantissa) in two words (one Newton step on the kernel's value): correctly rounded. Within 2^-8 of one, d = x - 1 is exact and ln(1 + d) = d * (ln 2/2)(1 - y) P(y^2), y = d/(2 + d), at 2^-254 (in two words when needed), so the relative precision holds all the way down.
x > 0 OutOfRange CR 3,274 3,0436,518
pow(int256,int256)
0x92b0c5b2
x^y = 2^(y log2 x)x^y = 2^(y log2 x): log2 x as a scaled word from the kernel (or the near-one series), its exact 512-bit product with y's significand split into the integer part and a fraction at 2^-254, then the 2^f kernel; one rounding when the error bound (which grows with |y| or |t|) clears the rounding boundary, else an exact tie when x^y has at most 238 bits, else log2 x in two words (wln) times y and 2^f by wexpm1: correctly rounded. 0^0 = 1. Shortcuts for y in {1, 2, 1/2, -1}.
x >= 0 OutOfRange, DivisionByZero, Overflow CR 6,011 5,90112,512
log1p(int256)
0x8552a5b0
ln(1 + x) without forming a rounded 1 + xln(1 + x) without forming a rounded 1 + x. Below 2^-4, x times ln(1 + x)/x = (ln 2/2)(1 - y) P(y^2), y = x/(2 + x), at 2^-254 (P the kernel's minimax polynomial below 2^-8, a wider one above), so the relative precision holds all the way down; above, 1 + x exactly on a 2^-254 grid under its leading bit, the log2 kernel and one product with ln 2. Each fast value carries its error bound; when it reaches the rounding boundary the series or ln(1 + x) is recomputed in two words. Correctly rounded.
x > -1 OutOfRange CR 2,846 2,5586,315
expm1(int256)
0x973fe005
e^x - 1 without cancellatione^x - 1 without cancellation. Below 2^-8, x times (e^x - 1)/x from its minimax polynomial at 2^-254; above, exp's split of x log2 e and its unrounded 2^f, with the one subtracted on the same 2^-254 grid; each within its bound, recomputed in two words when that reaches the rounding boundary. Correctly rounded; -1 below about -176.
x < 2^18 Overflow, OutOfRange CR 2,780 2,3957,802
erf(int256)
0x7a959e36
The error functionThe error function. Below 1/2, x (2/sqrt pi) e^(-x^2) times the all-positive series sum (2x^2)^k / (2k+1)!!; above, 1 - erfc with erfc = e^(-x^2) g(x) from the anchors or the continued fraction (see erfc). e^(-x^2) comes from the exact square. One rounding when the fast value's error bound clears the rounding boundary, else the series in two words below 7 and 1 - erfc by the continued fraction in two words above: correctly rounded. +-1 from |x| >= 13.
any number OutOfRange CR 26,598 31,09912,261
erfc(int256)
0xa79456da
The complementary error function as a product, never a difference, for x >= 1/2The complementary error function as a product, never a difference, for x >= 1/2: erfc(x) = e^(-x^2) g(x), g = e^(x^2) erfc from a Taylor series around 45 anchors on [1/2, 6] and the continued fraction above, e^(-x^2) from the exact 474-bit square, so the tail keeps 71 digits until it underflows 2^-262142 near x = 427. 1 - erf below 1/2, 2 - erfc(-x) below zero. One rounding, or the wide second stage (the series and 1 - erf below 7, the continued fraction above, in two words) when the fast value's error bound reaches the rounding boundary: correctly rounded.
any number OutOfRange CR 28,099 29,03712,336
normCdf(int256)
0x7b9745a0
The standard normal distribution function Phi(x) = erfc(-x / sqrt 2) / 2, with e…The standard normal distribution function Phi(x) = erfc(-x / sqrt 2) / 2, with e^(-x^2/2) from the exact square, so the left tail keeps 71 digits until it underflows near x = -603 (the 18-decimal grid is empty from about -8.3); one rounding, or erf's wide second stage at y = |x| / sqrt 2: correctly rounded; exactly one from x >= 13 sqrt 2.
any number OutOfRange CR 13,090 14,12112,389
lambertW0(int256)
0x6de23d34
The principal branch of Lambert W, W(x) e^W(x) = x, W >= -1The principal branch of Lambert W, W(x) e^W(x) = x, W >= -1. Below 1 + e x = 1/8 (x < -0.322) the unknown is q = 1 + W on (q - 1) e^q + 1 = 1 + e x, with 1 + e x exact: the seed p S(p), p = sqrt(2 (1 + e x)), S a degree-16 fit (2^-44.8), and one sixth-order step with e^q - 1 from the wide exp kernel, whose error is 5 units of 2^-254 whatever q, so the branch point costs no precision; one rounding when that clears the rounding boundary. Elsewhere x (1 - x + 3x^2/2) or Winitzki's seed from a crude logarithm, fourth-order log-form steps at 2^-126 (one or two, with a table-driven logarithm) and one at word precision with a bound (no e^w, so the whole range up to W = 181,700 works), tested the same way. On a miss one Newton step at 2^-510 (the residual in three words) and one rounding: correctly rounded. W rounds to x below 2^-238.
x >= -1/e OutOfRange CR 9,616 10,60910,726
lambertWm1(int256)
0xae94abc8
The secondary real branch of Lambert W, W(x) <= -1The secondary real branch of Lambert W, W(x) <= -1. Below 1 + e x = 1/8 (x < -0.322) as lambertW0 in q = 1 + W (negative here); above, the asymptotic seed L - ln(-L) + ln(-L)/L, L = ln(-x), from the crude logarithm, lambertW0's fourth-order log-form steps (one to three at 2^-126, one at word precision; no e^w: W reaches about -181,700 at the bottom of the format), its rounding test and, on a miss, its Newton step at 2^-510: correctly rounded.
-1/e <= x < 0 OutOfRange CR 9,862 10,69610,460
powu(int256,int256)
0xb8846d69
x^n for |n| below 2^16 by binary exponentiation on a 256-bit significand with it…x^n for |n| below 2^16 by binary exponentiation on a 256-bit significand with its own exponent, each product keeping its top 256 bits, rounded once at the end: exact whenever x^n fits 256 bits (so every exactly representable power and every exact tie), else within (2n + 8) units, and through pow's second stage when that reaches the rounding boundary. n < 0 inverts the final significand once. From 2^16 on, pow(|x|, n) with the sign restored. Correctly rounded for |n| < 2^237 (n is a number from there). 0^0 = 1.
any number x, any plain integer n (not a number); the result must fit Overflow, DivisionByZero, OutOfRange CR 6,624 6,63113,272
compound(int256,int256)
0xa31e7db8
(1 + rate)^n without forming a rounded 1 + rate (a per-second rate needs 266 bits for it)(1 + rate)^n without forming a rounded 1 + rate (a per-second rate needs 266 bits for it): log2(1 + rate) unrounded, as log1p takes it (the series below 2^-4, 1 + rate exact under its leading bit above), its exact product with n, then pow's 2^t; one rounding when its error bound clears the rounding boundary, else an exact tie when the power has at most 238 bits, else log2(1 + rate) in two words times n exactly: correctly rounded.
rate >= -1, any plain integer n (not a number); the result must fit OutOfRange, DivisionByZero, Overflow CR 5,762 5,95111,735
neg(int256)
0x54a53dc1
-x: the sign bit flipped; -0 is 0. any number OutOfRange exact 696 127108
abs(int256)
0x1b5ac4b5
|x|: the sign bit cleared. any number OutOfRange exact 726 133108
sign(int256)
0xae610b48
-1, 0 or 1 as numbers. any number OutOfRange exact 764 139116
cmp(int256,int256)
0xc688366a
-1, 0 or 1 as plain integers: the order of a and b. any two numbers OutOfRange exact 803 248178
lt(int256,int256)
0x30880038
1 if a < b, else 0, as a plain integer. any two numbers OutOfRange exact 819 233173
eq(int256,int256)
0xaa2c45bf
1 if a == b, else 0. Representations are unique, so this is bit equality. any two numbers OutOfRange exact 830 176129
fromUint(uint256)proven
0xe3814b19
The number x, exact below 2^237, rounded once above. any x never CR 937 413234
fromFP127(int256)
0x736c53c3
The number x / 2^128The number x / 2^128: exact whenever |x| has at most 237 significant bits (every |x| below 2^237, and larger ones with trailing zeros), rounded once otherwise.
any 127.128 word never CR 950 440244
toFP127(int256)
0x7c90b7f9
The 127.128 word nearest to x, half up on the magnitude: zero below 2^-129, one raw unit from there to 2^-128. |x| < 2^127 Overflow, OutOfRange CR 914 343313
fromFixed18(int256)proven
0x9aea0a13
The number x / 10^18: the integer as a number (exact below 2^237), divided by 10^18 with one rounding. any 18-decimal word never CR 1,147 554325
toFixed18(int256)
0xa2745c74
The 18-decimal word nearest to x, half up on the magnitude, from the exact 512-bit product M 10^18. |x| 10^18 < 2^255 Overflow, OutOfRange CR 1,029 480351
mulDown(int256,int256)proven
0x4b8d10e0
a * b rounded toward minus infinitya * b rounded toward minus infinity: the largest number at or below the exact product (the 474-bit product of the significands and a sticky bit). A product below the smallest number is zero when positive and minus the smallest number when negative.
any two numbers Overflow, OutOfRange CR 1,263 727449
mulUp(int256,int256)proven
0x1aabb2f7
a * b rounded toward plus infinitya * b rounded toward plus infinity: the smallest number at or above the exact product. A positive product below the smallest number is the smallest number, never zero.
any two numbers Overflow, OutOfRange CR 1,244 728450
divDown(int256,int256)proven
0xddb29e92
a / b rounded toward minus infinitya / b rounded toward minus infinity: div's exact floor quotient and a sticky bit from the remainder, mulmod(ma, 2^255, mb).
b != 0 DivisionByZero, Overflow, OutOfRange CR 1,463 987544
divUp(int256,int256)proven
0xe2bec1fe
a / b rounded toward plus infinity: the smallest number at or above the exact quotient. b != 0 DivisionByZero, Overflow, OutOfRange CR 1,530 988545
toUintDown(int256)proven
0x5fe0506c
floor(x) as a plain uint256, exact for every number in range: what a protocol pays out. 0 <= x < 2^256 OutOfRange, Overflow exact 872 405284
toUintUp(int256)proven
0x67d4ca8d
ceil(x) as a plain uint256, exact: what a protocol charges. 0 <= x <= 2^256 - 2^19 OutOfRange, Overflow exact 938 406285
toIntDown(int256)proven
0x64f06660
floor(x) as a plain int256 (toward minus infinity), exact. -2^255 <= x < 2^255 Overflow, OutOfRange exact 1,076 466304
toIntUp(int256)proven
0x5ed6bfec
ceil(x) as a plain int256 (toward plus infinity), exact. -2^255 <= x < 2^255 Overflow, OutOfRange exact 1,124 467305
floor(int256)proven
0xda3ff648
The largest integral number at or below x, exact (x itself from 2^236 on, where every number is an integer). any number OutOfRange exact 1,130 522376
ceil(int256)proven
0x42fba7ae
The smallest integral number at or above x, exact. any number OutOfRange exact 1,150 523377
toFixed18Down(int256)
0x32154abe
The 18-decimal word at or below x (toward minus infinity), from the exact 512-bi…The 18-decimal word at or below x (toward minus infinity), from the exact 512-bit product M 10^18: what a protocol pays out in 18-decimal units.
|x| 10^18 < 2^255 Overflow, OutOfRange CR 1,307 723447
toFixed18Up(int256)
0x27066ea4
The 18-decimal word at or above x (toward plus infinity)The 18-decimal word at or above x (toward plus infinity): what a protocol charges; a positive x below 10^-18 is one unit, never zero.
|x| 10^18 < 2^255 Overflow, OutOfRange CR 1,334 724448
fromUintDown(uint256)proven
0xfebfc540
The largest number at or below x: x itself below 2^237, x truncated to 237 significant bits above. any x never CR 912 487302
fromUintUp(uint256)proven
0x35e13636
The smallest number at or above xThe smallest number at or above x: x itself below 2^237, x rounded up to 237 significant bits above (2^256 for the largest).
any x never CR 959 488303
min(int256,int256)
0x29aa9cbe
The smaller of a and b: one of the two words, exact. any two numbers OutOfRange exact 837 251184
max(int256,int256)
0x81fe5786
The larger of a and b: one of the two words, exact. any two numbers OutOfRange exact 826 250184
clamp(int256,int256,int256)
0x7b8d0f0c
min(hi, max(lo, x)), exact. When lo > hi the result is hi, as FP127's. any three numbers OutOfRange exact 1,131 491242
avg(int256,int256)
0xebf274be
(a + b) / 2 rounded once from the exact sum: never overflows, and below the format it rounds as every op does. any two numbers OutOfRange CR 1,117 663633
dist(int256,int256)
0x2110b689
|a - b|, rounded once (sub's rounding is symmetric, so its magnitude is the rounded distance). any two numbers Overflow, OutOfRange CR 1,230 819660
zeroFloorSub(int256,int256)
0xff78cf2d
max(0, a - b), rounded once. any two numbers Overflow, OutOfRange CR 1,294 829663
inv(int256)proven
0x3a02c983
1 / x: div's exact quotient floor(2^491 / m), rounded once. x != 0 DivisionByZero, Overflow, OutOfRange CR 1,271 747401
lerp(int256,int256,int256)
0x84e13b60
a + t (b - a), rounded once from the exact valuea + t (b - a), rounded once from the exact value: a, t b and -t a (computed once, in memory) summed exactly in a 1,024-bit window (terms far under it enter as their floor or their sign), so cancellation costs nothing and no intermediate can overflow. t is not clamped.
any three numbers; the result must fit Overflow, OutOfRange CR 6,850 6,5152,741
frac(int256)
0xae2785ac
x - floor(x), rounded oncex - floor(x), rounded once: in [0, 1), and exact for x >= 0; a negative x within 2^-238 of an integer gives exactly 1, the nearest word to 1 - |x - ceil(x)|.
any number OutOfRange CR 1,050 428980
round(int256)proven
0x58b4370b
The nearest integral number, halves away from zero (FP19's tie rule; FP127's round takes halves up), exact. any number OutOfRange exact 1,122 474343
log2Up(int256)
0xa1985bb6
ceil(log2 x) as a number: the exponent, plus one unless x is a power of two. Exact. x > 0 OutOfRange exact 1,169 679381
factorial(int256)
0x5ad78e8a
floor(n)! rounded once, from a table of the 169 correctly rounded words for 2 <=…floor(n)! rounded once, from a table of the 169 correctly rounded words for 2 <= n <= 170 (exact to 64!), checked against the exact factorial for every n (make sweep). 0! = 1.
0 <= n < 171 (170! is the largest factorial a binary64 holds) OutOfRange, Overflow CR 2,649 2,4278,211
cbrt(int256)
0xd4b6188c
The cube root, cbrt(-x) = -cbrt(x)The cube root, cbrt(-x) = -cbrt(x): floor(cbrt(N)) of the significand scaled to N = m 2^(477 + rr), 714 to 716 bits, by Newton with precision doubling (a seed from the top bits, four steps in one word to 85 bits, two residual steps to 170 and 239) and an exact three-word cube at the end, then rounded once. Exact for perfect cubes; never overflows.
any number OutOfRange CR 3,195 2,734960
gavg(int256,int256)
0xa13ed853
The geometric mean sqrt(a b) of the exact 474-bit product, rounded once; never overflowsThe geometric mean sqrt(a b) of the exact 474-bit product, rounded once; never overflows. Two negatives give the positive root.
a b >= 0 OutOfRange CR 2,569 2,099713
hypot(int256,int256)
0xc479210c
sqrt(a^2 + b^2) rounded once from the exact sum of squares (floored only at a gr…sqrt(a^2 + b^2) rounded once from the exact sum of squares (floored only at a grid far under the root's), so ties of Pythagorean triples round away from zero; |a| when b is more than 2^120 smaller. No intermediate overflows.
any two numbers; the result must fit Overflow, OutOfRange CR 3,574 3,556978
exp2(int256)
0xdbbb06d2
2^x2^x: the integer part of x to the exponent, its fraction (exact at 2^-254 from 2^-18 up) through the 2^f kernel, one rounding when the bound clears the rounding boundary, else e^(f ln 2) by wexpm1 in two words: correctly rounded, exact for integral x. Exactly one below 2^-238; zero below -262,142.
x < 262145 Overflow, OutOfRange CR 3,297 3,0965,825
exp10(int256)
0x4c0dafa2
10^x = 2^(x log2 10)10^x = 2^(x log2 10): the 512-bit product with log2 10 split as exp's, the 2^f kernel and one rounding when the bound clears the rounding boundary, else x log2 10 in three words and 2^f by wexpm1: correctly rounded, so 10^n is exact for n = 0 to 102. Exactly one below 2^-240.
x below about 78,913.4 Overflow, OutOfRange CR 3,349 3,1775,846
log2(int256)
0xe0d68737
log2(x) = (E - bias) + log2(M / 2^236)log2(x) = (E - bias) + log2(M / 2^236): the exponent exactly and the mantissa through the log2 kernel, one rounding when its 4 units clear the rounding boundary; within 2^-8 of one, d = x - 1 exact and d ln1pk(d) log2 e. Else the same in two words (pow's wide log2): correctly rounded, exact at powers of two.
x > 0 OutOfRange CR 3,255 2,8226,669
log10(int256)
0xb47ca3c7
log10(x) = log2(x) log10 2log10(x) = log2(x) log10 2: the exponent exactly, the mantissa through the log2 kernel, one product and one rounding when its 4 units clear the rounding boundary; within 2^-8 of one, ln's series times log10 e. Else u log10 2 + ln(mantissa) log10 e in three words: correctly rounded, exact at the powers of ten that are numbers (10^0 to 10^102).
x > 0 OutOfRange CR 3,419 3,0386,728
sinh(int256)
0x17cdc976
sinh(x) = (U + U/(1 + U)) / 2 with U = e^|x| - 1 from expm1's machinerysinh(x) = (U + U/(1 + U)) / 2 with U = e^|x| - 1 from expm1's machinery: below 2^-8, x times T = (e^|x| - 1)/|x| from its polynomial (no cancellation, so the relative precision holds all the way down); to 1/2, U from exp's unrounded kernel; above, (e^|x| - e^-|x|) / 2 from that kernel and its reciprocal. One rounding when the bound clears the rounding boundary, else the series or both exponentials in two words: correctly rounded.
|x| below about 181,705 Overflow, OutOfRange CR 3,614 3,6708,007
cosh(int256)
0xaa13e96a
cosh(x) = (e^|x| + e^-|x|) / 2 from exp's unrounded kernel and its reciprocal, s…cosh(x) = (e^|x| + e^-|x|) / 2 from exp's unrounded kernel and its reciprocal, summed before the one rounding (no cancellation); else both exponentials in two words: correctly rounded. Exactly one below 2^-118.
|x| below about 181,705 Overflow, OutOfRange CR 3,671 3,6926,322
tanh(int256)
0x5e1f05c7
tanh(x) = U/(U + 2) with U = e^2|x| - 1 from expm1's machinerytanh(x) = U/(U + 2) with U = e^2|x| - 1 from expm1's machinery: below 2^-9, x times T/(1 + |x| T) with T = (e^2|x| - 1)/(2|x|) from its polynomial; to 1/2, U from exp's unrounded kernel; above, 1 - 2/(e^2|x| + 1) from that kernel. One rounding when the bound clears the rounding boundary, else sinh's and cosh's series or the exponential in two words: correctly rounded. Exactly +-1 once 2|x| log2 e passes 240.
any number OutOfRange CR 3,710 3,7178,082
atanh(int256)
0xc6285a68
atanh(x) = log1p(y) / 2 for y = 2|x|/(1 - |x|)atanh(x) = log1p(y) / 2 for y = 2|x|/(1 - |x|): below 2^-6, x times R P with R = 1/(1 - |x|) and P = ln(1 + y)/y from log1p's series at y = 2|x| R (no cancellation, so the relative precision holds all the way down); above, (log2(1 + |x|) - log2(1 - |x|)) ln 2 / 2 with both arguments exact under their leading bits (the logarithms have opposite signs). One rounding when the bound clears the rounding boundary, else the odd series or the two logarithms in two words: correctly rounded.
|x| < 1 OutOfRange CR 3,976 3,8536,047

Bits against exact rationals and mpmath at 300 decimal places over the committed vectors. CR: the correctly rounded word at every vector, to nearest or in the direction the name gives; exact: the result is always the exact value. proven: every input against a reference spec, on the proofs page. Open an op for its method; hover a reverts cell for when. Gas is the median over random inputs that take no shortcut for the 25 ops that have them (the transcendentals, roots and powers), else over the op's input ladder. "Object" includes the staticcall to the deployed object that holds the op (FP19, FP19E, FP19H or FP19S); "inline" is FP19Lib. "Bytes inline" is the runtime code one op adds to a contract that calls it through FP19Lib, helpers included (default profile, legacy pipeline; hover for via_ir). Ops that share a kernel add less together than their rows (size.json's bundles). Inlining saves about 464 gas a call; staticcall the deployed object instead when inlining would take a contract near the 24,576-byte limit, or for a large op called rarely. Sources: ops.json, precision.json, gas.json, size.json.

Gas against FP127 and Quad

opFP19 deployed (staticcall)FP19Lib (inline)FP127Lib (inline), the same opABDKMathQuad
abs72613315221
add1,126662128734
atanh t3,9763,8532,783
avg1,117663114
cbrt t3,1952,7341,022
ceil1,150523147
clamp1,131491241
cmp803248476
compound t5,7625,9513,232
cosh t3,6713,6923,913
dist1,230819289
div1,328800412818
divDown1,463987475
divUp1,530988478
eq830176108
erf t26,59831,09912,047
erfc t28,09929,0377,435
exp t3,3193,2782,7055,668
exp10 t3,3493,1772,978
exp2 t3,2973,0962,6535,025
expm1 t2,7802,3953,027
factorial2,6492,427406
floor1,13052272
frac1,05042869
fromFP127950440539
fromFixed181,147554140
fromUint937413
fromUintDown912487500
fromUintUp959488
gavg t2,5692,0991,289
hypot t3,5743,5561,576
inv1,2717472421,253
lambertW0 t9,61610,6098,069
lambertWm1 t9,86210,6967,964
lerp t6,8506,515477
ln t3,2743,0431,93416,356
log10 t3,4193,0381,923
log1p t2,8462,5582,033
log2 t3,2552,8221,72815,553
log2Up1,169679209
lt819233488
max826250113
min837251114
mul1,064530255709
mulDown1,263727315
mulUp1,244728360
neg6961271169
normCdf t13,09014,1216,188
pow t6,0115,9013,40923,467
powu t6,6246,6315,189
round1,122474212
sign76413975181
sinh t3,6143,6703,947
sqrt t1,6851,205404760
sub1,1907451281,230
tanh t3,7103,7173,828
toFP127914343292
toFixed181,029480121
toFixed18Down1,307723160
toFixed18Up1,334724185
toIntDown1,076466278
toIntUp1,124467
toUintDown872405240
toUintUp938406
zeroFloorSub1,294829212

Median gas per call, 2026-10-23: over random inputs that take no shortcut for the ops marked t, else over the op's input ladder. The object column includes the staticcall; the other three are internal library calls: FP19Lib, the same op in FP127Lib on the same value where 127.128 holds it, and ABDKMathQuad, the other float. An empty cell is an op the library does not offer; rev would be one it offers that reverted, or whose inputs were outside its domain or format, on every input. Hover a Quad cell for the call it measures: lt, inv and pow are composed from Quad's own functions, and its conversions truncate where FP19's round. The WAD libraries and 64.64 are compared on FP127's table. Min and max per cell, and the ladder medians, are in gas.json and gas19.md; every function of every comparison library is mapped in coverage.md.

The source is contracts/src/fp19/FP19.yul.src; the deployed contracts and the inline library are fuzzed equal across all of int256. Full reference: contracts/src/fp19/README.md.