Decimals and Precision
Every price, amount and ratio is an exact decimal string. Parse, compare, format and compute with them without losing a digit.
Why Strings
A JSON number is read into a 64-bit float by almost every parser, which keeps about 16 significant digits and cannot hold most decimal fractions exactly. Canton Coin carries ten decimal places, supplies run to billions of units and ratios to 18 fractional digits, so a float would silently change the figure. Every price, amount, volume, supply, FDV, rate and ratio in the API is therefore a decimal string:
{
"price": "0.1584",
"fdv": "715482911.36",
"liquidity_to_fdv_ratio": "0.005758709342091255",
"total_supply": "4517356203.5"
}The strings are plain decimals: digits, at most one point, a leading minus only on fields that can be negative (changes), and never an exponent. Counts, durations, positions and ranks are ordinary JSON integers.
What a Float Does to a Figure
const exact = "123456789012345678901234567890.123456789";
new Intl.NumberFormat("en-US", { maximumFractionDigits: 9 }).format(exact);
// "123,456,789,012,345,678,901,234,567,890.123456789" every digit kept
new Intl.NumberFormat("en-US").format(Number(exact));
// "123,456,789,012,345,680,000,000,000,000" wrong from the 17th digit
0.1 + 0.2; // 0.30000000000000004
Number("0.1584") * 3; // 0.47520000000000007Parse with a Decimal Type
Keep values as strings until you need arithmetic, then use an exact decimal type. In JavaScript, decimal.js or big.js; in Python, the standard decimal module; in Java, BigDecimal; in Go, shopspring/decimal; in Rust, rust_decimal. Never call parseFloat or Number() on a figure.
import Decimal from "decimal.js";
const { data } = await response.json();
const quote = data[0].quotes.USD;
const price = new Decimal(quote.price); // 0.1584, exactly
const position = price.times("125000"); // value of 125,000 CC
const fdvCheck = price.times(asset.total_supply); // compare with quote.fdv
position.toFixed(2); // "19800.00"
price.gt("0.15"); // true: compare decimals, not floatsfrom decimal import Decimal, ROUND_HALF_EVEN
quote = response.json()["data"][0]["quotes"]["USD"]
price = Decimal(quote["price"]) # Decimal('0.1584'), exactly
position = price * Decimal("125000") # Decimal('19800.0000')
print(position.quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN)) # 19800.00
# Parse the whole body with Decimal so no float ever appears:
body = response.json(parse_float=Decimal)json.loads and requests' .json() only turn JSON numbers into floats. The API's figures are strings, so they arrive as str; wrap each in Decimal before arithmetic.Display Without Losing Digits
Modern JavaScript engines format a decimal string directly with Intl.NumberFormat, without converting it to a number first. Round for display only, at the last step:
const usd = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 4,
});
usd.format("0.1584"); // "$0.1584"
const compact = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 2 });
compact.format("715482911.36"); // "715.48M"Percentages and Ratios
| Field | Unit | "1.5" means | Display |
|---|---|---|---|
price_changes_pct | Percentage points | +1.5% | Append %: 1.5 becomes 1.5% |
price_change_24h_pct | Percentage points | +1.5% | Append % |
volume.change_24h_pct | Percentage points | +1.5% | Append % |
liquidity_to_fdv_ratio | Raw ratio | 150% | Multiply by 100: 0.025 becomes 2.5% |
volume_24h_to_fdv_ratio | Raw ratio | 150% | Multiply by 100 |
spread_bps | Basis points | 0.015% | Divide by 100 for % |
// Percentage points: append the sign, do not multiply.
const change = new Decimal(quote.price_changes_pct["24h"]); // "1.25"
const label = `${change.isNegative() ? "\u2212" : "+"}${change.abs().toFixed(2)}%`; // "+1.25%"
// Raw ratio: multiply by 100 for a percentage.
const ratio = new Decimal(listing.liquidity_to_fdv_ratio); // "0.005758709342091255"
ratio.times(100).toFixed(2) + "%"; // "0.58%"Nulls Are Not Zeros
A figure the API does not know is null, and a measured zero is "0". Check for null before parsing, and show an absence (a dash or "n/a") rather than 0. market_cap, for example, is null for every asset. See Responses.
fdv = quote["fdv"]
fdv_text = "n/a" if fdv is None else f"{Decimal(fdv):,.0f}"Sums and Comparisons
- Add decimals as decimals. Summing a column of floats drifts; summing
Decimalvalues does not. - Compare with the decimal type's own comparison, not with strings:
"10" < "9"is true as text. - Do not add figures across currencies or units. Market volumes in base or quote units differ per market; use the
_usdfields to compare markets.