Skip to main content

nexus_decimal/
lib.rs

1//! Fixed-point decimal arithmetic with compile-time precision.
2//!
3//! `nexus-decimal` provides [`Decimal<B, DECIMALS>`] — a generic
4//! fixed-point type parameterized by backing integer and decimal
5//! places. Operations are `const fn` where possible, zero-allocation,
6//! and designed for financial workloads.
7//!
8//! # Choosing Your Type
9//!
10//! Define aliases that match your domain:
11//!
12//! ```
13//! use nexus_decimal::Decimal;
14//!
15//! type Price = Decimal<i64, 8>;       // 8dp, range ±92B — traditional finance
16//! type Quantity = Decimal<i64, 4>;    // 4dp, range ±922T
17//! type CryptoPrice = Decimal<i128, 12>; // 12dp, range ±39T — DeFi
18//! type Usd = Decimal<i64, 2>;         // 2dp cents
19//! ```
20//!
21//! | Backing | Max Decimals | Max Range | Use case |
22//! |---------|-------------|-----------|----------|
23//! | `i32` | 9 | ±2.1B / SCALE | Embedded, space-constrained |
24//! | `i64` | 18 | ±9.2e18 / SCALE | Traditional finance |
25//! | `i128` | 38 | ±1.7e38 / SCALE | Cryptocurrency, DeFi |
26//!
27//! # Quick Start
28//!
29//! ```
30//! use nexus_decimal::Decimal;
31//! use core::str::FromStr;
32//!
33//! type D64 = Decimal<i64, 8>;
34//!
35//! let price = D64::from_str("123.45").unwrap();
36//! let qty = D64::from_i32(10).unwrap();
37//!
38//! let notional = price * qty;
39//! assert_eq!(notional.to_string(), "1234.5");
40//!
41//! let bid = D64::from_str("100.00").unwrap();
42//! let ask = D64::from_str("100.50").unwrap();
43//! let mid = bid.midpoint(ask);
44//! assert_eq!(mid.to_string(), "100.25");
45//! ```
46//!
47//! # Integer Conversions
48//!
49//! `From<IntType>` is implemented for primitive integer types whenever the
50//! conversion is sound — i.e., `IntType::MAX * 10^DECIMALS` fits the backing.
51//! Otherwise, `TryFrom<i64>` and `TryFrom<u64>` provide fallible conversions.
52//! Smaller types that don't fit must be widened explicitly.
53//!
54//! ```
55//! use nexus_decimal::Decimal;
56//! type D64 = Decimal<i64, 8>;
57//!
58//! // Sound combinations: infallible.
59//! let qty: D64 = 100_i32.into();
60//! let count: D64 = 42_u16.into();
61//!
62//! // Unsound combinations: fallible.
63//! let huge: Result<D64, _> = i64::MAX.try_into();
64//! assert!(huge.is_err());
65//! ```
66//!
67//! Use [`Decimal::from_scaled`] for tick-size construction:
68//!
69//! ```
70//! use nexus_decimal::Decimal;
71//! type D64 = Decimal<i64, 8>;
72//!
73//! let tick = D64::from_scaled(1, 5).unwrap(); // 0.00001
74//! ```
75//!
76//! # Compile-Time Constants
77//!
78//! ```
79//! use nexus_decimal::Decimal;
80//!
81//! type D64 = Decimal<i64, 8>;
82//!
83//! const PRICE: D64 = D64::new(100, 50_000_000); // 100.50
84//! const FEE: D64 = D64::from_raw(500_000);       // 0.005
85//! const TOTAL: D64 = match PRICE.checked_add(FEE) {
86//!     Some(v) => v,
87//!     None => panic!("overflow"),
88//! };
89//! ```
90//!
91//! # Arithmetic Variants
92//!
93//! Every arithmetic operation comes in four flavors:
94//!
95//! | Variant | Returns | On overflow |
96//! |---------|---------|-------------|
97//! | `checked_*` | `Option<Self>` | `None` |
98//! | `try_*` | `Result<Self, SpecificError>` | Typed error |
99//! | `saturating_*` | `Self` | Clamps to `MIN`/`MAX` |
100//! | `wrapping_*` | `Self` | Wraps around |
101//!
102//! Operators (`+`, `-`, `*`, `/`, `%`) always panic on overflow
103//! in both debug and release builds.
104//!
105//! # Error Types
106//!
107//! Errors are scoped per operation — no catch-all enum:
108//!
109//! | Error | Used by | Variants |
110//! |-------|---------|----------|
111//! | [`OverflowError`] | `try_add`, `try_mul`, etc. | (unit struct) |
112//! | [`DivError`] | `try_div` | `Overflow`, `DivisionByZero` |
113//! | [`ParseError`] | `from_str_exact`, `FromStr` | `InvalidFormat`, `Overflow`, `PrecisionLoss` |
114//! | [`ConvertError`] | `from_f64`, `TryFrom` | `Overflow`, `PrecisionLoss` |
115//!
116//! # Feature Flags
117//!
118//! | Feature | Dependencies | Provides |
119//! |---------|-------------|----------|
120//! | `std` (default) | — | `Error` trait impls |
121//! | `serde` | `serde` | Serialize/Deserialize (string for JSON, raw for binary) |
122//! | `num-traits` | `num-traits` | Zero, One, Num, Signed, Bounded, Checked*, ToPrimitive |
123//!
124//! # `no_std` Support
125//!
126//! Disable default features for `no_std`:
127//! ```toml
128//! nexus-decimal = { version = "0.1", default-features = false }
129//! ```
130//!
131//! # Migration from fixdec
132//!
133//! ```ignore
134//! // Before:
135//! use fixdec::D64;
136//!
137//! // After:
138//! use nexus_decimal::Decimal;
139//! type D64 = Decimal<i64, 8>;
140//! ```
141//!
142//! API differences:
143//! - `mul_i64` / `mul_i128` → `mul_int` (takes the backing type)
144//! - `DecimalError` → per-method error types ([`OverflowError`], [`DivError`], etc.)
145//! - No predefined aliases — define your own (`type Price = Decimal<i64, 8>`)
146//! - New: financial methods (`midpoint`, `spread`, `round_to_tick`, etc.)
147
148#![no_std]
149#![warn(missing_docs)]
150
151#[cfg(feature = "std")]
152extern crate std;
153
154pub mod backing;
155pub mod error;
156
157mod arithmetic;
158mod bytes;
159mod constants;
160mod convert;
161mod decimal;
162mod div_by_scale;
163mod financial;
164mod format;
165mod from_int;
166mod ops;
167mod pow10;
168mod rounding;
169mod wide;
170
171#[cfg(feature = "serde")]
172mod serde_impl;
173
174#[cfg(feature = "num-traits")]
175mod num_traits_impl;
176
177pub use backing::Backing;
178pub use decimal::Decimal;
179pub use error::{ConvertError, DivError, OverflowError, ParseError};