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.
| digits | range | add | mul | div | exp | ln | pow | sqrt | |
|---|---|---|---|---|---|---|---|---|---|
| FP19 | 71 | 10±78,913 | 662 | 530 | 800 | 3,278 | 3,043 | 5,901 | 1,205 |
| FP127 | 38 at 1 | 2−128 to 2127 (±1.7 × 1038) | 128 | 255 | 412 | 2,705 | 1,934 | 3,409 | 404 |
| ABDKMathQuad | 34 | 10±4,932 | 734 | 709 | 818 | 5,668 | 16,356 | 23,467 | 760 |
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.
| contract | what it holds | address | ops |
|---|---|---|---|
| FP19 | the number | 0xAa1f71B7B08d568379b28E1488c6B737a1C6eb52 | add 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 |
| FP19E | the elementary functions, on one exp/log kernel | 0x017F68A57dD53a6dF2043c24B712b287F9549AE2 | exp exp2 exp10 expm1 ln log2 log10 log2Up log1p pow powu compound |
| FP19H | the hyperbolic functions | 0xEfefa2B63A173d56F9db79C5dd8A607F4aA859A5 | sinh cosh tanh atanh |
| FP19S | the special functions | 0x73E8809F163d4358773cc5F25c134997B4b842F4 | erf 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).
- 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.
- Round toward the protocol at the token edge. Round down what you pay out and round up what you take in:
toUintDownandtoUintUp,toFixed18DownandtoFixed18Up,mulDownandmulUp,divDownanddivUp. 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.
| op | what | domain | reverts | bits (min/mean) | gas, object | gas, inline | bytes 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 | 662 | 632 |
| 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 | 745 | 633 |
| mul(int256,int256)proven 0xbbe93d91 |
a * b: the 474-bit product of the significands, rounded once. | any two numbers | Overflow, OutOfRange | CR | 1,064 | 530 | 337 |
| 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 | 800 | 444 |
| 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,205 | 531 |
| 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,278 | 6,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,043 | 6,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,901 | 12,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,558 | 6,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,395 | 7,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,099 | 12,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,037 | 12,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,121 | 12,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,609 | 10,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,696 | 10,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,631 | 13,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,951 | 11,735 |
| neg(int256) 0x54a53dc1 |
-x: the sign bit flipped; -0 is 0. | any number | OutOfRange | exact | 696 | 127 | 108 |
| abs(int256) 0x1b5ac4b5 |
|x|: the sign bit cleared. | any number | OutOfRange | exact | 726 | 133 | 108 |
| sign(int256) 0xae610b48 |
-1, 0 or 1 as numbers. | any number | OutOfRange | exact | 764 | 139 | 116 |
| cmp(int256,int256) 0xc688366a |
-1, 0 or 1 as plain integers: the order of a and b. | any two numbers | OutOfRange | exact | 803 | 248 | 178 |
| lt(int256,int256) 0x30880038 |
1 if a < b, else 0, as a plain integer. | any two numbers | OutOfRange | exact | 819 | 233 | 173 |
| eq(int256,int256) 0xaa2c45bf |
1 if a == b, else 0. Representations are unique, so this is bit equality. | any two numbers | OutOfRange | exact | 830 | 176 | 129 |
| fromUint(uint256)proven 0xe3814b19 |
The number x, exact below 2^237, rounded once above. | any x | never | CR | 937 | 413 | 234 |
| 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 | 440 | 244 |
| 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 | 343 | 313 |
| 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 | 554 | 325 |
| 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 | 480 | 351 |
| 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 | 727 | 449 |
| 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 | 728 | 450 |
| 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 | 987 | 544 |
| 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 | 988 | 545 |
| 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 | 405 | 284 |
| 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 | 406 | 285 |
| toIntDown(int256)proven 0x64f06660 |
floor(x) as a plain int256 (toward minus infinity), exact. | -2^255 <= x < 2^255 | Overflow, OutOfRange | exact | 1,076 | 466 | 304 |
| toIntUp(int256)proven 0x5ed6bfec |
ceil(x) as a plain int256 (toward plus infinity), exact. | -2^255 <= x < 2^255 | Overflow, OutOfRange | exact | 1,124 | 467 | 305 |
| 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 | 522 | 376 |
| ceil(int256)proven 0x42fba7ae |
The smallest integral number at or above x, exact. | any number | OutOfRange | exact | 1,150 | 523 | 377 |
| 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 | 723 | 447 |
| 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 | 724 | 448 |
| 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 | 487 | 302 |
| 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 | 488 | 303 |
| min(int256,int256) 0x29aa9cbe |
The smaller of a and b: one of the two words, exact. | any two numbers | OutOfRange | exact | 837 | 251 | 184 |
| max(int256,int256) 0x81fe5786 |
The larger of a and b: one of the two words, exact. | any two numbers | OutOfRange | exact | 826 | 250 | 184 |
| 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 | 491 | 242 |
| 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 | 663 | 633 |
| 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 | 819 | 660 |
| zeroFloorSub(int256,int256) 0xff78cf2d |
max(0, a - b), rounded once. | any two numbers | Overflow, OutOfRange | CR | 1,294 | 829 | 663 |
| inv(int256)proven 0x3a02c983 |
1 / x: div's exact quotient floor(2^491 / m), rounded once. | x != 0 | DivisionByZero, Overflow, OutOfRange | CR | 1,271 | 747 | 401 |
| 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,515 | 2,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 | 428 | 980 |
| 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 | 474 | 343 |
| 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 | 679 | 381 |
| 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,427 | 8,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,734 | 960 |
| 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,099 | 713 |
| 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,556 | 978 |
| 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,096 | 5,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,177 | 5,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,822 | 6,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,038 | 6,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,670 | 8,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,692 | 6,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,717 | 8,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,853 | 6,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
| op | FP19 deployed (staticcall) | FP19Lib (inline) | FP127Lib (inline), the same op | ABDKMathQuad |
|---|---|---|---|---|
| abs | 726 | 133 | 152 | 21 |
| add | 1,126 | 662 | 128 | 734 |
| atanh t | 3,976 | 3,853 | 2,783 | |
| avg | 1,117 | 663 | 114 | |
| cbrt t | 3,195 | 2,734 | 1,022 | |
| ceil | 1,150 | 523 | 147 | |
| clamp | 1,131 | 491 | 241 | |
| cmp | 803 | 248 | 476 | |
| compound t | 5,762 | 5,951 | 3,232 | |
| cosh t | 3,671 | 3,692 | 3,913 | |
| dist | 1,230 | 819 | 289 | |
| div | 1,328 | 800 | 412 | 818 |
| divDown | 1,463 | 987 | 475 | |
| divUp | 1,530 | 988 | 478 | |
| eq | 830 | 176 | 108 | |
| erf t | 26,598 | 31,099 | 12,047 | |
| erfc t | 28,099 | 29,037 | 7,435 | |
| exp t | 3,319 | 3,278 | 2,705 | 5,668 |
| exp10 t | 3,349 | 3,177 | 2,978 | |
| exp2 t | 3,297 | 3,096 | 2,653 | 5,025 |
| expm1 t | 2,780 | 2,395 | 3,027 | |
| factorial | 2,649 | 2,427 | 406 | |
| floor | 1,130 | 522 | 72 | |
| frac | 1,050 | 428 | 69 | |
| fromFP127 | 950 | 440 | 539 | |
| fromFixed18 | 1,147 | 554 | 140 | |
| fromUint | 937 | 413 | ||
| fromUintDown | 912 | 487 | 500 | |
| fromUintUp | 959 | 488 | ||
| gavg t | 2,569 | 2,099 | 1,289 | |
| hypot t | 3,574 | 3,556 | 1,576 | |
| inv | 1,271 | 747 | 242 | 1,253 |
| lambertW0 t | 9,616 | 10,609 | 8,069 | |
| lambertWm1 t | 9,862 | 10,696 | 7,964 | |
| lerp t | 6,850 | 6,515 | 477 | |
| ln t | 3,274 | 3,043 | 1,934 | 16,356 |
| log10 t | 3,419 | 3,038 | 1,923 | |
| log1p t | 2,846 | 2,558 | 2,033 | |
| log2 t | 3,255 | 2,822 | 1,728 | 15,553 |
| log2Up | 1,169 | 679 | 209 | |
| lt | 819 | 233 | 488 | |
| max | 826 | 250 | 113 | |
| min | 837 | 251 | 114 | |
| mul | 1,064 | 530 | 255 | 709 |
| mulDown | 1,263 | 727 | 315 | |
| mulUp | 1,244 | 728 | 360 | |
| neg | 696 | 127 | 116 | 9 |
| normCdf t | 13,090 | 14,121 | 6,188 | |
| pow t | 6,011 | 5,901 | 3,409 | 23,467 |
| powu t | 6,624 | 6,631 | 5,189 | |
| round | 1,122 | 474 | 212 | |
| sign | 764 | 139 | 75 | 181 |
| sinh t | 3,614 | 3,670 | 3,947 | |
| sqrt t | 1,685 | 1,205 | 404 | 760 |
| sub | 1,190 | 745 | 128 | 1,230 |
| tanh t | 3,710 | 3,717 | 3,828 | |
| toFP127 | 914 | 343 | 292 | |
| toFixed18 | 1,029 | 480 | 121 | |
| toFixed18Down | 1,307 | 723 | 160 | |
| toFixed18Up | 1,334 | 724 | 185 | |
| toIntDown | 1,076 | 466 | 278 | |
| toIntUp | 1,124 | 467 | ||
| toUintDown | 872 | 405 | 240 | |
| toUintUp | 938 | 406 | ||
| zeroFloorSub | 1,294 | 829 | 212 |
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.