kuatia-types (Cent, Amount, AssetId), kuatia-coreEvery posting carries an amount of one asset (ADR-0001), and the core invariant
is per-asset conservation: sum(consumed) == sum(created) checked on every
commit. That sum must be exact. A ledger that rounds is not a ledger. So the
monetary type has to be exact under addition, subtraction and negation, deny
silent overflow, hash deterministically for content-addressing, and still
represent assets with different decimal precision (USD has 2, a token might
have 8, JPY has 0). What type represents a stored monetary amount, and where
does an asset's decimal scale live?
EnvelopeId, so the representation must serialize identically everywhere
(no locale, no float bit-pattern ambiguity).f64)Store amounts as binary floating point.
Pros:
Cons:
f64 cannot represent most decimal fractions exactly
(0.1 + 0.2), so conservation sums drift and the Σ consumed == Σ created
check becomes approximate, which disqualifies it for a ledger.rust_decimal, i128, rationals)Use a wider or decimal-aware numeric type that carries its own scale.
Pros:
i128) or scale-aware decimal math,
and can embed precision in the value itself.Cons:
i64 minor units already cover ~±9.2×10¹⁸ of the smallest
unit, ample for realistic balances, so the extra range is mostly unused
weight.Cent, an i64 newtype of minor units, scale held outsideCent(i64) is a private-field newtype holding an amount in the asset's
smallest unit (cents, satoshis, …). It exposes only checked arithmetic
(checked_add/checked_sub/checked_neg/checked_sum → OverflowError),
serializes as big-endian bytes (ToBytes) for hashing, and is Ord/Hash.
Decimal scale is not stored on the value or the asset: AssetId(u32) is an
opaque identifier, and Amount { decimals: u8 } is a presentation-only
parser/formatter (string ⇄ Cent) that is never persisted.
Pros:
i64, and the only arithmetic offered is checked, so overflow is a
Result, never a wrap.i64 minor units are compact (fixed 8 bytes) and index/sum
cheaply in Rust.Cons:
Cent is meaningless without knowing its asset's decimals,
and nothing in the type stops formatting a satoshi amount with 2 decimals.i64 caps a single amount/sum at ~±9.2×10¹⁸ minor units; an
asset with very high precision and very large supply could in principle
exceed it (surfaced as OverflowError, not a wrap, but a hard ceiling
nonetheless).Cent and must be defined explicitly with an agreed
rounding policy when they are introduced.Chosen option: Option 3, Cent, an i64 newtype of minor units with scale
held outside the value, because it is the only option that makes the
conservation sum exact and deterministic while keeping the stored ledger
pure integers and overflow an explicit error. Scale lives in Amount
(presentation) rather than on Cent or AssetId (storage), so precision is an
edge concern at the application boundary and never leaks into the invariant
math or the database schema. i64 is chosen over i128/decimal because its
range is more than adequate and its fixed width keeps the most pervasive type
small and trivially serializable; widening later is a contained newtype change
if a real asset ever needs it.
validate_and_plan's
conservation and floor checks operate on integers that cannot silently round
or wrap.Cent's big-endian ToBytes feeds the content-addressed EnvelopeId
deterministically; the same transfer hashes identically on every backend.BIGINT/i64 with no precision metadata,
consistent with "no DB arithmetic" and with Rust-owned identity (ADR-0003).Amount cleanly separates human input/output (with per-asset decimals)
from the stored, scale-free value.Cent to its asset's decimal places, so callers must format/parse
with the right Amount for the asset.i64 is a hard magnitude ceiling per amount and per sum (overflow →
Result, never a wrap); a future high-precision/high-supply asset may force
widening the newtype.Cent today.crates/kuatia-types/src/lib.rs (Cent, Amount, AssetId),
glossary.md.