A robust, ergonomic, and unified numeric type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
uninum provides a single Number enum that safely encapsulates all standard numeric types, allowing you to perform arithmetic and comparisons without boilerplate conversions, overflow panics, or floating-point precision surprises.
Want a feature-by-feature walkthrough? Check out the Quick Reference for an in-depth tour of the API surface, conversion paths, and integration tips.
Working with different numeric types in Rust can be verbose and error-prone. You have to handle conversions, worry about integer overflow, and be mindful of floating-point inaccuracies. uninum solves these problems by providing:
- Seamless Interoperability: Perform arithmetic between integers, floats, and high-precision decimals without explicit casting.
- Absolute Safety: Operations never panic. Integer overflow is automatically handled by promoting to a larger type. Division by zero follows IEEE 754 semantics, returning
InfinityorNaN. - Precision by Default: When the
decimalfeature is enabled, floating-point literals and inexact divisions are automatically converted to a high-precision decimal type, preventing common floating-point errors in financial and scientific calculations. - Ergonomic Design: A clean, intuitive API that feels like a natural extension of Rust's built-in numeric types.
- Exact first: keep lossless representations whenever they exist (integers stay numeric, decimal literals stay decimal).
- Fast when exact: among exact choices, stick to the quickest variant—integers beat decimals until you truly need decimal scale.
- Graceful fallback: when exactness isn’t possible, promote to the best available floating-point type (
Decimal, thenF64) so work keeps flowing.
| Variant | Precision | Ideal Use Cases |
|---|---|---|
Decimal |
Exact (≈28 digits) | Finance, billing, human-facing decimal math |
F64 |
~15–17 digits | Scientific/engineering work, extremely large/small values, NaN/∞ |
I64 / U64 |
Exact within range | Counting, IDs, fast integer-heavy logic |
- Intelligent Construction with
num!: A powerful macro that creates the optimalNumbervariant from literals (e.g.,num!(3.14)becomes a high-precisionDecimal). - Seamless Primitive Operations:
&num + 5,2.5 * &num, andnum == 10work just as you'd expect. - Automatic & Safe Type Promotion:
u64::MAX + 1gracefully promotes to aDecimalorF64instead of panicking. - High-Precision Decimal Arithmetic: Enable the
decimalfeature for arbitrary-precision decimal calculations, perfect for financial applications. - Total Ordering & Hashing for Floats:
Float64wrapper allows floating-point numbers (includingNaN) to be used inBTreeMaps andHashMaps with consistent, predictable behavior. - Stable Serialization: The
serdefeature emits a tagged JSON representation so variants, decimal scale, and non-finite floats (NaN,Infinity) round-trip without loss.
Add uninum to your Cargo.toml:
[dependencies]
uninum = "0.1.0"uninum follows a minimum supported Rust version (MSRV) of 1.88.0. The
crate is tested against that toolchain in CI; newer stable compilers work as
well.
For high-precision decimal, serde support, or bitwise operations, enable the corresponding features:
[dependencies]
uninum = { version = "0.1.0", features = ["decimal", "serde", "bitwise"] }uninum is developed and maintained exclusively by Synext Solution Sdn. Bhd.
Bug reports, feature ideas, and general feedback are welcome through the issue
tracker. However, Synext Solution retains sole discretion over the roadmap and
the official code base: external pull requests are not accepted, and any changes
must be coordinated through the maintainers. You are, of course, free to fork
the project under the dual MIT/Apache-2.0 license if you need customisations.
use uninum::{num, Number};
fn main() {
// The `num!` macro creates the best representation for a literal.
let integer = num!(100); // Stored as an integer
let float = num!(3.14); // Stored as a Decimal (if `decimal` feature is on)
let large_int = num!(u64::MAX);
// --- Ergonomic Operations ---
// All operations work with references, avoiding unnecessary moves.
let result1 = &integer + 50; // Number + i32
let result2 = 2.5 * &integer; // f64 * Number
let result3 = &integer + &float; // Number + Number
println!("100 + 50 = {}", result1);
println!("2.5 * 100 = {}", result2);
println!("100 + 3.14 = {}", result3);
// --- Safety and Precision ---
// No panics! Overflow promotes to a higher-precision type.
let overflow_result = large_int + 1;
println!("u64::MAX + 1 = {}", overflow_result); // No panic!
// Inexact division is handled with precision.
let division = num!(10) / num!(3);
println!("10 / 3 = {}", division); // e.g., "3.33333..." as a Decimal
// --- Comparisons ---
assert!(&integer == 100);
assert!(&float < 4);
assert!(num!(0.1) + num!(0.2) == num!(0.3)); // True if `decimal` feature is enabled!
}All arithmetic and comparison operators are implemented for both owned Numbers and references &Number. To avoid consuming your variables, always use references (&) for operations.
let num = num!(10);
// Using a reference (&num) - `num` can be reused.
let result1 = &num + 5;
let result2 = &num * 2; // `num` is still available.
println!("Original num is still: {}", num);
// Using an owned value (num) - `num` is moved and consumed.
let result3 = num + 1;
// println!("{}", num); // ❌ This would be a compile-time error.The num! macro is the recommended way to create Number instances from literals. It intelligently parses the literal to choose the best internal representation:
- Integers:
num!(42),num!(-100)becomeNumber::from(42i64), etc. - Floats:
num!(3.14),num!(1e-5)are parsed as strings to preserve full precision, creating aDecimalif thedecimalfeature is enabled, or anF64otherwise. This avoids the inherent imprecision off64literals. - Variables/Expressions:
num!(my_var)is a convenient shorthand forNumber::from(my_var).
uninum deliberately deviates from the raw IEEE semantics to provide consistent
behaviour across the entire type family:
NaNEquality: allNaNvalues compare equal to each other. This makes equality checks across mixed numeric types deterministic (Number::F64(NaN) == Number::Decimal(NaN)evaluates totrue).- Signed Zero Normalisation:
+0and-0are treated as the same value for both equality and ordering comparisons. - Total Ordering: the type implements a total ordering even for special
values—
NaNis equal to itself and considered greater than any finite number, while infinities follow the expected ordering.
uninum is designed to be safe and predictable.
- Integer Overflow: When an integer operation overflows (e.g.,
u64::MAX + 1), the operation is re-run after promoting the numbers to a higher-precision type (DecimalorF64). - Division by Zero: Follows IEEE 754 rules:
finite / 0->+Infinityor-Infinity0 / 0->NaNInfinity / Infinity->NaN
- NaN & ±0 Normalisation: For deterministic behaviour across Number variants,
all
NaNvalues compare equal to each other, and+0/-0are treated as the same value for equality and ordering. Total ordering remains well-defined even in the presence of infinities or NaNs.
Standard f64 types are prone to precision errors (e.g., 0.1 + 0.2 != 0.3). The decimal feature (enabled by default) solves this by using the rust_decimal crate for arbitrary-precision decimal arithmetic.
// With the `decimal` feature enabled:
// num!(0.1) creates a precise Decimal, not an approximate f64.
assert!(num!(0.1) + num!(0.2) == num!(0.3)); // This is true!
// Without the `decimal` feature, this would fall back to f64 and fail.
let f64_sum = Number::from(0.1_f64) + Number::from(0.2_f64);
assert!(f64_sum != Number::from(0.3_f64)); // Standard f64 imprecision.uninum uses feature flags to keep the core library lightweight. With
default-features = false the library has no runtime dependencies; every
extra crate is opt-in so you can control the footprint precisely.
| Feature | Default | Adds Dependency | Description |
|---|---|---|---|
decimal |
Yes | rust_decimal |
Enables high-precision decimal arithmetic using the rust_decimal crate. Highly recommended for financial or scientific applications. |
serde |
No | serde, ryu |
Enables serialization and deserialization support via the serde crate. |
bitwise |
No | (none) | Enables bitwise operations (&, ^, !, <<, >>) for integer variants of Number. |
To use uninum without default features:
[dependencies]
uninum = { version = "0.1.0", default-features = false }💡 Dependency policy: new dependencies must be justified and introduced as optional features whenever possible. The default configuration should remain as lean as possible so downstream users can rely on a zero-dependency core.
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work shall be dual licensed as above, without any additional terms or conditions.
We welcome bug reports and feature discussions via the issue tracker, but code changes are handled solely by the Synext maintainer team. See the Contributing Guidelines for details on how to share feedback or follow the internal release workflow.