Appearance
Numbers
Chain values are whole numbers with the decimal point somewhere else, and CQL keeps them exact until you ask for a division.
Token amounts, balances and supplies arrive as very large integers. This page is about the two number types that hold them, how to make them readable, and the one place rounding can happen.
Two types
bigint is a whole number of any size. Every uint256 and int256 a contract returns is a bigint, and so is $block. Nothing overflows; pow(2, 256) is fine.
decimal is an exact number with a fractional part — a whole number and a scale, never a floating-point value. 1.5 and 1.50 are the same decimal, and 0.1 + 0.2 is exactly 0.3.
Add, subtract or multiply two of either and you get an exact answer. Mix them and the bigint becomes a decimal.
Making an amount readable
A token with 18 decimals reports one token as 1000000000000000000. format(x, decimals) moves the point, and stETH's three headline numbers all want it:
cql
let stETH = ethereum:0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
from stETH as s
| project {
block: $block,
ethPerShare: format(s.getPooledEthByShares(1e18), 18),
pooledEther: format(s.getTotalPooledEther(), 18),
totalShares: format(s.getTotalShares(), 18)
}| block | ethPerShare | pooledEther | totalShares |
|---|---|---|---|
| 26042707 | 1.244710915700866902 | 9761717.135365036561936133 | 7842557.667190094063001578 |
format is exact: it never rounds, it strips trailing zeros, and it keeps at least one digit after the point, so format(1e18, 18) is 1.0 and format(1050000000000000000, 18) is 1.05. parse goes the other way — parse(1.05, 18) is 1050000000000000000 — and drops any digits beyond the scale without complaint.
Division is the exception
bigint / bigint is whole-number division, truncated toward zero: 7 / 2 is 3. If either side is a decimal, the quotient is computed to 38 significant digits, rounded half to even, and a run in which something was actually rounded says so with CQL4901. That is why the quick start's (last / first - 1) / days * 100 divides two formatted values — decimals — and why its days is todecimal(totalseconds(until - since)) / 86400 rather than the whole-number division that would make two and a half days into two. avg() works the same way. % follows / on whole numbers and is not defined on decimals.
Dividing by a constant zero is refused before the run. Dividing by a zero that came from the data gives null for that row and the run continues.
Writing a number
1000000,1_000_000— underscores between digit groups, for reading.1e18,1.5e18— exponents; the result is abigintwhen it is whole, adecimalotherwise.s.getPooledEthByShares(1e18)passes one whole share.1.05— a decimal.0xff— not a number. Hex is bytes;tobigint(0xff)is the number 255.