@skenaja/solpi
v0.1.0
Published
On-chain π for Solidity. Machin's formula (69 exact digits) and a 4,200-digit lookup table, in 42 chunks of 100. Useless. Correct. Nice.
Maintainers
Readme
solpi
On-chain π for Solidity. Computed (69 exact digits) and tabulated (4,200 digits, in 42 chunks of 100).
____ _
| _ \(_)
| |_) |_
| __/| |
|_| |_| π · π · π · π · π · π · π · π · πA single contract, Pi, with two ways to ask for the most famous transcendental number in mathematics:
| function | returns | source |
| ----------------------- | --------------------------------------------- | ------------------------------- |
| computePi() | π · 10^70 as uint256 (69 digits exact) | Machin's 1706 formula, on-chain |
| pi(uint256 chunk) | 100 fractional digits as string | precomputed bytecode lookup |
Install
As a Foundry dependency
forge install skenaja/solpiThen in your contract:
import {Pi} from "solpi/contracts/Pi.sol";
contract MyContract {
Pi public immutable piContract;
constructor() {
piContract = new Pi();
}
function fetchSliceOfPi(uint256 chunk) external view returns (string memory) {
return piContract.pi(chunk);
}
}As an npm package
npm install @skenaja/solpiThen in your contract (assumes Hardhat-style remappings):
import {Pi} from "@skenaja/solpi/contracts/Pi.sol";API
SCALE (constant)
uint256 public constant SCALE = 10**70;The fixed-point scale used by computePi(). One decimal place of π per power of ten.
DIGITS, CHUNK_SIZE, CHUNKS (constants)
uint256 public constant DIGITS = 4200;
uint256 public constant CHUNK_SIZE = 100;
uint256 public constant CHUNKS = 42;Invariant: DIGITS == CHUNK_SIZE * CHUNKS.
computePi()
function computePi() external pure returns (uint256);Computes π to 69 exact decimal digits using Machin's formula (John Machin, 1706):
π = 16·arctan(1/5) − 4·arctan(1/239)Each arctan(1/x) is summed via the Gregory–Leibniz series in scaled-uint256 fixed-point at SCALE = 10^70. The loop bails one Taylor term short of uint256 overflow; cumulative truncation costs the 70th decimal place. The remaining 69 digits are bit-exact.
Returns the integer 31415926535897932384626433832795028841971693993751058209749445923078164….
pi(uint256 chunk)
function pi(uint256 chunk) external pure returns (string memory);Returns 100 fractional digits of π by chunk index.
pi(0)→ digits 1..100 (the famous"1415926535…")pi(1)→ digits 101..200- …
pi(41)→ digits 4101..4200pi(42+)→ reverts withChunkOutOfRange(chunk, 41)
Reconstructing the full 4,200-digit fractional expansion is just pi(0) ‖ pi(1) ‖ … ‖ pi(41).
Why?
For the lulz.
Useful applications include:
- on-chain art / generative seeds where π's digits are aesthetically required
- nothing else, really
This contract is not a source of cryptographic randomness. π is the least-random number in mathematics: every digit is exactly where it has always been, and where any sufficiently determined attacker can also find it.
Development
Setup
git clone --recursive https://github.com/skenaja/solpi.git
cd solpiIf you already cloned without --recursive:
git submodule update --init --recursiveBuild
forge buildTest
forge test # unit + fuzz, default (1024 fuzz runs)
FOUNDRY_PROFILE=ci forge test # 8192 fuzz runs, deeper invariants
forge test --gas-report # gas usage per function
forge coverage # line coverageFormat
forge fmtHow it's tested
- Unit tests for known reference values (first 100 digits, last 100 digits, leading 69 of
computePi). - Fuzz tests over the chunk index space — every valid chunk must return exactly 100 ASCII decimal digits; every invalid chunk must revert with the typed
ChunkOutOfRangeerror. - Cross-validation between
computePi()andpi(0)— the first 69 fractional digits must agree byte-for-byte across the two independent code paths. - Property tests over the full 4,200-digit string — every byte is in
'0'..'9', no chunk overlaps another, concatenation reconstructs the table exactly. - Invariants on the
DIGITS == CHUNK_SIZE × CHUNKSconstant relationship. - Fixture-based verification — a separately-sourced 4,200-digit reference is loaded at runtime via
vm.readFileand compared against the contract's embedded constant.
Gas report
Generated with forge test --gas-report at the pinned compiler version (solc 0.8.27, optimizer on, 200 runs):
| Function | Min | Avg | Median | Max | notes |
| ------------ | ----: | -----: | -----: | ----: | --------------------------------------- |
| SCALE() | 266 | 266 | 266 | 266 | constant getter |
| DIGITS() | 216 | 216 | 216 | 216 | constant getter |
| CHUNK_SIZE() | 238 | 238 | 238 | 238 | constant getter |
| CHUNKS() | 282 | 282 | 282 | 282 | constant getter |
| pi(chunk) | 381 | 23,062 | 27,093 | 27,093 | 100-byte memcpy from bytecode constant |
| computePi() | 69,252 | 69,252 | 69,252 | 69,252 | 75 Taylor terms across two arctans |
| Deployment cost | Runtime size | Initcode size | EIP-170 margin | | --------------: | -----------: | ------------: | -------------: | | 1,195,526 | 5,282 B | 5,310 B | 19,294 B |
Reading is cheap: pi(chunk) is ~27k gas (≈ a single SLOAD + a small loop), constant getters are sub-300 gas. Computing is the expensive bit: computePi() runs 54 + 20 Taylor terms in fixed point, including the overflow-guard branches, for ~69k gas — still well under one block-fraction.
License
MIT.
A historical note
| year | who | digits | medium | | ---------- | ----------- | --------------- | --------------------- | | ~250 BC | Archimedes | 3 | 96-gon, by hand | | 263 AD | Liu Hui | 5 | 3072-gon | | ~1400 | Madhava | 11 | infinite series | | 1706 | Machin | 100 | THIS formula, ink | | 1873 | Shanks | 527 | last 180 wrong, oops | | 1949 | ENIAC | 2,037 | 70 hours, vacuum tubes | | 2019 | Iwao | 31,400,000,000,000 | Google Cloud | | 2026 | this repo | 4,200 | EVM bytecode |
Progress is not monotone.
