FP127 is one Yul source that generates two forms. Both are in the repository and both are proven equal on the full int256 domain, including which inputs revert and with what data.
Encoding
A value is one int256 holding value × 2^128, two's complement. Encode with int256(value * 2**128), decode by dividing by 2^128. ONE is 2^128. Range about ±1.7 × 10^38, 128 fractional bits, about 38 decimal digits. Every parameter and result of every op is that one type; there is no 18-decimal flavour of each op. fromFixed18 and toFixed18 are the bridge to WAD, and they are the only place a WAD value should meet FP127.
Errors are Overflow(), DivisionByZero() and OutOfRange(). Arithmetic is checked: nothing wraps.
Deployed form
- network
- sepolia (chain id 11155111)
- address
- 0xe43F720861074497db2e974E5c4554E4A3b341B9
- deployer
- 0x4e59b44847b379578588920cA78FbF26c0B4956C (the CREATE2 proxy)
- salt
- "FP127 v3" = 0x4650313237207633000000000000000000000000000000000000000000000000
- init code hash
- 0x67362c4ef1771ae6109a3d8a8291ab42dcde9619a2f1deb849292c81823a65e8
- runtime
- 7593 bytes, keccak 0xab019bcc0e8ce61720b018c96fae06f7b05cc7caaf7c30550c095f82922db496
- compiler
- Yul, solc 0.8.24+commit.e11b9ed9, optimizer 200 runs, shanghai
- deployed
- block 11861162, source commit 7b74fbc
- verified
- Sourcify, Blockscout. Etherscan has no Yul path.
- record
- FP127.md, fp127.json
Any contract can staticcall it, and every contract that does runs identical bytecode. There is no storage, no owner and no upgrade path; a change to the source is a new init code hash, a new salt and a new address, recorded in the repository.
A read from the command line, sqrt(2):
cast call 0xe43F720861074497db2e974E5c4554E4A3b341B9 "sqrt(int256)(int256)" \
680564733841876926926749214863536422912 \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com
# 481231938336009023090067544955250113854
python3 -c "print(481231938336009023090067544955250113854 / 2**128)"
# 1.4142135623730951
From Solidity, through the interface:
import {IFP127} from "fp127/IFP127.sol";
contract StaticcallExample {
IFP127 public constant FP127 = IFP127(0xe43F720861074497db2e974E5c4554E4A3b341B9);
int256 internal constant ONE = int256(1) << 128;
function accrue(int256 principalWad, int256 rateWad, uint256 steps) external view returns (int256 balanceWad) {
int256 b = FP127.fromFixed18(principalWad);
int256 growth = FP127.add(ONE, FP127.fromFixed18(rateWad));
for (uint256 i = 0; i < steps; i++) {
b = FP127.mul(b, growth);
}
return FP127.toFixed18(b);
}
}
Inline form
FP127Lib.sol holds the same Yul bodies in memory-safe assembly blocks, one internal pure function per op, so there is no external call and no shared address. It is the cheapest per op and the form to use when the math belongs inside your contract.
forge install securedataresearch/blockulator
echo "fp127/=lib/blockulator/contracts/src/fp127/" >> remappings.txt
import {FP127Lib} from "fp127/FP127Lib.sol";
contract CompoundExample {
function accrue(int256 principalWad, int256 rateWad, uint256 steps) external pure returns (int256 balanceWad) {
int256 b = FP127Lib.fromFixed18(principalWad);
int256 growth = FP127Lib.add(FP127Lib.ONE, FP127Lib.fromFixed18(rateWad));
for (uint256 i = 0; i < steps; i++) {
b = FP127Lib.mul(b, growth);
}
return FP127Lib.toFixed18(b);
}
}
Both examples are compiled and tested in the repository under test/examples/, against the compound scenario of the ladder. Through the deployed object each op costs about 700 gas more than inline, the price of the staticcall; the compare page has both columns for every op.
The ops
Generated from the Yul source: the signature and selector, what the op computes, the domain, and what it reverts on. Precision and gas are joined from the committed reports.
| op | what | domain | reverts | bits min / mean | gas object | gas inline |
|---|---|---|---|---|---|---|
| fromFixed18(int256) 0x9aea0a13 |
Convert an 18-decimal fixed-point value to 127.128. | |x| < 2^127 | OutOfRange() if |x| >= 2^127 | exact | 1223 | 778 |
| toFixed18(int256) 0xa2745c74 |
Convert a 127.128 value to 18-decimal fixed-point. | any x | never | exact | 1269 | 790 |
| add(int256,int256) 0xa5f3c23b |
a + b. Reverts with Overflow on signed overflow. | any a, b | Overflow() on signed overflow | exact | 1303 | 281 |
| sub(int256,int256) 0xadefc37b |
a - b. Reverts with Overflow on signed overflow. | any a, b | Overflow() on signed overflow | exact | 1340 | 319 |
| mul(int256,int256) 0xbbe93d91 |
floor(a * b / 2^128), the 127.128 product. | any a, b | Overflow() when a·b / 2^128 does not fit in int256 | exact | 1569 | 472 |
| div(int256,int256) 0x43509138 |
a / b in 127.128, truncated toward zero. | b != 0 | DivisionByZero() when b == 0; Overflow() when the quotient does not fit | exact | 1401 | 939 |
| exp2(int256) 0xdbbb06d2 |
2^x. | x < 127; returns 0 below -128 | Overflow() for x >= 127 | 146 / 211 | 5116 | 5173 |
| exp(int256) 0xe46751e3 |
e^x, computed as 2^(x * log2(e)). | x <= 88; returns 0 below -88 | Overflow() for x > 88 | 146 / 221 | 5261 | 5353 |
| exp10(int256) 0x4c0dafa2 |
10^x, computed as 2^(x * log2(10)). | x·log2(10) < 127 | Overflow() above that | 146 / 211 | 4858 | 5689 |
| log2(int256) 0xe0d68737 |
log2(x) for x > 0. | x > 0 | OutOfRange() for x <= 0 | 255 / 255 | 1970 | 832 |
| ln(int256) 0x8e6f2353 |
ln(x) = log2(x) * ln(2) for x > 0, scaled at 2^-192 before one rounding. Reverts with OutOfRange otherwise. | x > 0 | OutOfRange() for x <= 0 | 255 / 255 | 1696 | 999 |
| log10(int256) 0xb47ca3c7 |
log10(x) = log2(x) / log2(10) for x > 0, scaled at 2^-192 before one rounding. Reverts with OutOfRange otherwise. | x > 0 | OutOfRange() for x <= 0 | 255 / 255 | 1943 | 1042 |
| log2Up(int256) 0xa1985bb6 |
ceil(log2(x)) for x > 0. Exact for powers of two. | x > 0 | OutOfRange() for x <= 0 | exact | 1648 | 755 |
| sqrt(int256) 0x665df460 |
sqrt(x) for x >= 0. | x >= 0 | OutOfRange() for x < 0 | exact | 3753 | 3564 |
| pow(int256,int256) 0x92b0c5b2 |
x^y = 2^(y * log2(x)) for x >= 0. | x >= 0 | OutOfRange() for x < 0; Overflow() when the result does not fit | 122 / 226 | 5803 | 6408 |
| cbrt(int256) 0xd4b6188c |
Cube root, defined for all x: cbrt(-x) = -cbrt(x). | any x | Overflow() only at -2^255 | 123 / 152 | 6163 | 6792 |
| gavg(int256,int256) 0xa13ed853 |
Geometric mean sqrt(a * b). Reverts with OutOfRange if a * b < 0. | a·b >= 0 | OutOfRange() if a·b < 0; Overflow() if a·b does not fit | exact | 4161 | 3846 |
| hypot(int256,int256) 0xc479210c |
sqrt(a^2 + b^2), checked. | any a, b | Overflow() if a^2 + b^2 does not fit | exact | 4684 | 4175 |
| inv(int256) 0x3a02c983 |
1 / x. Reverts with DivisionByZero for x == 0. | x != 0 | DivisionByZero() for x == 0; Overflow() when 1/x does not fit | exact | 1295 | 1078 |
| abs(int256) 0x1b5ac4b5 |
|x|. Reverts with Overflow for x == -2^255, which has no positive. | any x | Overflow() at -2^255 | exact | 706 | 612 |
| neg(int256) 0x54a53dc1 |
-x. Reverts with Overflow for x == -2^255. | any x | Overflow() at -2^255 | exact | 869 | 604 |
| sign(int256) 0xae610b48 |
-1, 0 or 1 in 127.128. | any x | never | exact | 1275 | 576 |
| min(int256,int256) 0x29aa9cbe |
Signed minimum. | any a, b | never | exact | 925 | 493 |
| max(int256,int256) 0x81fe5786 |
Signed maximum. | any a, b | never | exact | 1197 | 515 |
| clamp(int256,int256,int256) 0x7b8d0f0c |
min(hi, max(lo, x)). If lo > hi the result is hi, as in the Huff. | any x, lo, hi | never | exact | — | — |
| avg(int256,int256) 0xebf274be |
floor((a + b) / 2) without intermediate overflow. | any a, b | never | exact | 1632 | 501 |
| zeroFloorSub(int256,int256) 0xff78cf2d |
max(0, a - b), with the subtraction checked. | any a, b | Overflow() if a - b does not fit | exact | 1751 | 683 |
| dist(int256,int256) 0x2110b689 |
|a - b|, checked. | any a, b | Overflow() if a - b does not fit | exact | 976 | 736 |
| lerp(int256,int256,int256) 0x84e13b60 |
a + t * (b - a), checked. t is not clamped to [0, 1]. | any a, b, t | Overflow() on any intermediate that does not fit | exact | — | — |
| floor(int256) 0xda3ff648 |
Largest integer <= x. Clearing the fraction bits of a two's complement word is a floor for both signs. | any x | never | exact | 1401 | 583 |
| ceil(int256) 0x42fba7ae |
Smallest integer >= x, as -floor(-x). Reverts with Overflow when that integer is 2^127, which does not fit. | any x | Overflow() when the result is 2^127 | exact | 858 | 658 |
| frac(int256) 0xae2785ac |
x - floor(x), always in [0, 1). | any x | never | exact | 1266 | 601 |
| round(int256) 0x58b4370b |
Nearest integer, halves rounded up: floor(x + 1/2). Checked. | any x | Overflow() when the result is 2^127 | exact | 980 | 761 |
| gcd(int256,int256) 0x63b03896 |
gcd of the integer parts |floor(a)|, |floor(b)|, as a 127.128 integer. | any a, b | never | exact | 1209 | 753 |
| factorial(int256) 0x5ad78e8a |
floor(n)! for 0 <= n < 34. 33! is the largest that fits. | 0 <= n < 34 | OutOfRange() outside that | exact | 1581 | 1290 |
| pi(int256) 0x929edbfb |
pi by Ramanujan's 1914 series, floor(terms) terms: 1/pi = (2 sqrt2 / 9801) sum_k (4k)! (1103 + 26390 k) / ((k!)^4 396^(4k)). Each term adds about eight digits: 7, 15, 23, 31, then 36 at five terms, the format's floor; more terms change nothing. | 0 <= terms < 8, an integer in 127.128 | OutOfRange() outside that | 122 / 139 | 5364 | 5987 |
| lambertW0(int256) 0x6de23d34 |
Principal branch of the Lambert W function, W(x) e^W(x) = x, for x >= -1/e. | x >= -1/e | OutOfRange() below -1/e | 65 / 249 | 36086 | 45497 |
| lambertWm1(int256) 0xae94abc8 |
Secondary real branch of the Lambert W function, W(x) e^W(x) = x with W(x) <= -1, for -1/e <= x < 0. | -1/e <= x < 0 | OutOfRange() outside that | 67 / 243 | 29159 | 35588 |
Bits are relative correct bits against mpmath at 200 decimal places over the committed vectors; "exact" is bit-identical. Gas is the median over each op's input ladder: "object" includes the staticcall to the deployed FP127, "inline" is FP127Lib. Sources: ops.json (generated from the Yul source), precision.json, gas.json. lerp and clamp are not on the gas ladder.
For agents
The same address and ABI are published for machines at /.well-known/agent.json and /llms.txt. The ABI is /data/abi.json. Reads are eth_call: no gas, no wallet, no transaction.