Question: precise decimal arithmetic in stdlib (std.decimal) on the roadmap?
The stdlib in v0.1.4 covers a lot of what a small native CLI / library needs — std.json, std.http.fetch (thanks for landing #64), std.parse, std.time — and the source-backed stdlib pattern got three back-to-back additions on 2026-05-25 (std.path, std.str, std.math), so this seems like the right moment to ask about the next adjacent piece. I can't find a precise-decimal arithmetic story anywhere in the published surface. Asking because that's the gap that decides whether the math + parsing pieces of the small Bitcoin price viewer I mentioned in #47 and #83 (PeterXMR/spotprice) are portable to Zero today, or whether the port has to wait. Same four-option framing pattern as those issues; happy to be told it's option (4).
What's documented today (v0.1.4)
- The full numeric primitive set in
docs/articles/primitives.mdisi8 i16 i32 i64 / u8 u16 u32 u64 / usize isize / f32 f64. Eleven fixed-width scalars, two of which are IEEE 754 binary floats. No decimal, no fixed-point, no arbitrary-precision. docs/articles/modules/parse.mdexposes integer parsers only —parseU8 / parseU16 / parseU32returningMaybe<T>. NoparseDecimal, noparseF64.docs/articles/modules/json.mdcoversparse,validate,streamTokenswith no documented number-precision contract. The conformance fixtureconformance/native/pass/std-data-formats.0exercises JSON parsing on a generic document tree without numeric-precision assertions.- Float literals are parsed at compile time via the C standard library's
strtod()inparse_float_literal()innative/zero-c/src/checker.c, withf64as the default for untyped float literals. The literal0.1becomes its IEEE 754 binary approximation at lex time — no decimal-preservation pass. - The fresh
std.mathdoc (docs/articles/modules/math.md) declares its current scope explicitly: "Helpers are pure, target-neutral fixed-width integer operations… The module does not provide floating-point math, big integers, or arbitrary-precision number theory." The same doc's design notes say "Large-number algorithms should be added as explicit APIs with documented bounds instead of hidden heap allocation or implicit widening." I read that as the policy that any future numeric module — including a hypotheticalstd.decimal— would be expected to honor. - The stdlib design statement in
docs/articles/standard-library.mdis "Zero's standard library is pay-as-used and capability-aware. Importing memory helpers does not pull in hosted filesystem helpers." Astd.decimalthat's a pure source-backed module (no hosted capability, no allocation, no FFI) fits that posture cleanly. - No open or closed PR proposes a decimal type. I checked across
decimal,BigDecimal,fixed-point,money,currency,precision,rounding,BigInt, and a handful of adjacent terms in both--state allissues and PRs. - Related on the float side: PR #124 (SysV AMD64 f32/f64 return-type lowering, open) and PR #184 (f64 literals in the test runner, open) confirm that even IEEE 754 binary float arithmetic across the direct backends is still in progress, so today's "use f64 instead" path is itself not fully solid.
Provisional four-option framing (same pattern as #47 / #83)
- Already exists, I missed it — ruled out by the absence of any decimal module under
docs/articles/modules/and the integer-only parser surface instd.parse. - Planned but not yet shipped — most likely.
std.mathlanded in #273 only a few days ago, which suggests numeric-stdlib work is actively in scope; a decimal module would be the next adjacent piece. - Build it on top of u64 / i64 yourself, or FFI to a C decimal library — workable for spotprice (the workspace pins
panic = "abort"and the existing Rust code uses 128-bit fixed-precisionrust_decimalanyway), but loses the "agent-readable typed core surface" point of using Zero in the first place. Doable viaexamples/c-interopif that's the recommendation. - Out of scope by design — Zero is targeting host CLIs / Vercel-edge servers / wasm where decimal isn't load-bearing, and financial-precision math is intentionally a library-author concern rather than a stdlib concern. Totally reasonable scoping; just want to know explicitly.
Provisional answer: I suspect (2) for a small future addition, (3) for today's workaround.
Three concrete questions
- Is a
std.decimal(or equivalently-named) module tracked as a target version or milestone? Even at "weeks vs. months vs. no concrete plan" resolution. The closest precedent in shape is PR #273 (std.math, source-backed u32 helpers — 99 LOC instd/math.0, plus a 52-LOC conformance breadth test, a 34-LOC example, thestd_sig.c/std_source.cmetadata triple, the doc page, and the CHANGELOG entry — no new C-level runtime). A 128-bit fixed-precision value can be expressed as ashape Decimal { hi: u64, lo: u64, scale: u8 }(multi-field shapes are confirmed working inexamples/point.0,examples/config-shape.0, and conformance fixtures), so the type surface is bounded. The implementation honesty caveat: addition and subtraction are doable in pure Zero today via manual carry detection onu64, but multiplication of two 128-bit values needs schoolbook 32×32 splitting (sixteen partial products) because Zero today has nou128, noaddcarry, and no wide-mul primitive — that's a real cost that the currentstd.math/u32 surface doesn't yet have to pay. So a fair follow-up question is: is the preferred path (a) pure-source schoolbook splitting (works today, slower per op), (b) waiting on awide_mul_u64/addcarry_u64primitive to land first, or (c) a different representation entirely? - If a decimal type lands, which representation model is the intended one? Modern stdlib decimals split three ways: IEEE 754-2008 decimal128 (C23's
_Decimal128, the BDE C++ implementation, and Java'sMathContext.DECIMAL128all anchor here — 34 decimal digits, cohort-based encoding, standards-traceable); arbitrary-precisionBigDecimal-style (Java, Python, Go'sshopspring/decimal, the DeciMojo community lib — flexible but requires allocation or some implicit-widening mechanism, which would conflict with thestd.mathdesign note above); and fixed-precision 128-bit (Rust'srust_decimal: 96-bit mantissa + 8-bit scale + sign = 128-bitCopy-able value, ~28 digits; Swift'sDecimal: 160-bit, ~38 digits). The two contemporary systems-language designs that started from a clean sheet (Rust 2014, Swift 2014) independently chose fixed-precision; that path is also the one that composes best with Zero's existing fixed-width primitive set and avoids the "hidden heap allocation or implicit widening" footgunstd.mathexplicitly disclaims. Sufficient for spotprice's worst case (USD price × 21M BTC × 1e8 sats < 22 digits). Provisional answer: fixed-precision 128-bit, with IEEE 754 decimal128 as the "if we want standards-traceable" alternative; arbitrary-precision as the "if we want a general numerics stdlib including non-financial" choice. Want to defer to whichever you've already been thinking about. - Which rounding-control model fits the language's stance, and what's the fallibility shape? Three known rounding shapes in comparable stdlib decimals: per-operation argument (Rust
rust_decimal::round_dp(n), Goshopspring/decimal.Round(places), SwiftNSDecimalNumber.RoundingModepassed to method calls); per-operationMathContextobject (JavaBigDecimal); thread-local global context (Pythondecimal.localcontext()). The clean-sheet systems-language designs (Rust, Swift) both went per-operation, but no preference assumed here. On fallibility: the established idiom in current stdlib isMaybe<T>—std.parsereturnsMaybe<u8>/Maybe<u16>/Maybe<u32>for fallible numeric parsing and explicitly says "Integer parsers returnMaybe<T>instead of allocating diagnostics. Callers can layer richer errors on top." That seems like the natural shape fordecimal.parse,decimal.div, and friends. Adjacent design question: Zero today has explicit+%(wrapping) and+|(saturating) integer operators perconformance/native/pass/wrapping-saturating-arithmetic.0— does decimal arithmetic want an analogous explicit-overflow-handling family, or is fallible-Maybe-returning sufficient at this layer? Real-world grounding: spotprice'score/convert/src/lib.rs:56-59had to guardDecimal::Divwithchecked_divbecause the defaultDivpanics on scale overflow and the workspace shipspanic = "abort". That's the exact "infallible-by-default sharp edge" a fallible-by-defaultstd.decimalwould let users avoid.
Why I'm asking
The spotprice port-targets are core/convert/src/lib.rs (sats ↔ fiat conversion: every public function takes/returns Decimal) and core/aggregator/src/median.rs (median + σ-rejection across N exchange feeds, all Decimal). Both modules currently depend on rust_decimal = { workspace = true, features = ["serde-with-str"] } (core/Cargo.toml:52-53) and never touch f64 for price quantities.
For sizing, when I walked the spotprice call sites against the reference decimal libs surveyed in question 2, the consumer-side surface that actually gets called is small: roughly parse(string), from_int(int), add / sub / mul / div, cmp, abs, round(places, mode), floor, to_int, to_string, plus a RoundingMode with the four common variants (HalfEven, HalfUp, Down, Up). Same order of magnitude as std.math's ten u32 helpers in #273. Not proposing this as the surface — the design questions above are exactly the ones that would determine what the surface actually looks like — just sharing the shape I evaluated against to confirm the gap is real and the consumer-side surface is bounded.
Concrete value if this lands: spotprice's two pure-math modules become portable to Zero in their current shape (no design changes), which is exactly the "core-as-library, UI-in-platform-native" pattern that #83's library-artifact question is asking about. The two issues are independent — this one is the correctness gap, #83 is the delivery gap — but they're the same evaluation arc.
Related issues / PRs
- #47 (HTTP client — closed by PR #64; same four-option framing precedent)
- #83 (library artifact emit + Kotlin/Swift bindings — still open; the delivery-side counterpart to this issue)
- #273 (
Add source-backed std.math— merged; closest stylistic precedent for a source-backed numeric stdlib module) - #124 (ABI classification + f32/f64 return-type lowering, SysV AMD64 — open; context that even IEEE 754 float arithmetic is still partial across the direct backends)
- #184 (f64 literals in the test runner — open; same area)
- #20 (structured concurrency — adjacent but kept on its own issue; spotprice's fan-out across exchange feeds wants both eventually)
Happy to leave this open as a tracker for "current state of decimal arithmetic in Zero" the same way #47 functioned for HTTP. No rush; just want the answer on the public record so external evaluators can plan around it.
Activity
0No activity yet.