uninum

Crates.iouninum
lib.rsuninum
version0.1.1
created_at2025-12-13 17:31:35.765566+00
updated_at2025-12-13 17:31:35.765566+00
descriptionA robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
homepage
repositoryhttps://github.com/Synext-Solution/uninum
max_upload_size
id1983222
size728,014
(cheehenn)

documentation

README

uninum

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.

Why use uninum?

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 Infinity or NaN.
  • Precision by Default: When the decimal feature 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.

Precision Philosophy

  • 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, then F64) 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

Key Features

  • Intelligent Construction with num!: A powerful macro that creates the optimal Number variant from literals (e.g., num!(3.14) becomes a high-precision Decimal).
  • Seamless Primitive Operations: &num + 5, 2.5 * &num, and num == 10 work just as you'd expect.
  • Automatic & Safe Type Promotion: u64::MAX + 1 gracefully promotes to a Decimal or F64 instead of panicking.
  • High-Precision Decimal Arithmetic: Enable the decimal feature for arbitrary-precision decimal calculations, perfect for financial applications.
  • Total Ordering & Hashing for Floats: Float64 wrapper allows floating-point numbers (including NaN) to be used in BTreeMaps and HashMaps with consistent, predictable behavior.
  • Stable Serialization: The serde feature emits a tagged JSON representation so variants, decimal scale, and non-finite floats (NaN, Infinity) round-trip without loss.

Installation

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"] }

Governance & Contribution Policy

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.

Quick Start

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!
}

Core Concepts

1. Ergonomic Operations: References vs. Owned Values

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.

2. The num! Macro

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) become Number::from(42i64), etc.
  • Floats: num!(3.14), num!(1e-5) are parsed as strings to preserve full precision, creating a Decimal if the decimal feature is enabled, or an F64 otherwise. This avoids the inherent imprecision of f64 literals.
  • Variables/Expressions: num!(my_var) is a convenient shorthand for Number::from(my_var).

3. Equality and Ordering Guarantees

uninum deliberately deviates from the raw IEEE semantics to provide consistent behaviour across the entire type family:

  • NaN Equality: all NaN values compare equal to each other. This makes equality checks across mixed numeric types deterministic (Number::F64(NaN) == Number::Decimal(NaN) evaluates to true).
  • Signed Zero Normalisation: +0 and -0 are treated as the same value for both equality and ordering comparisons.
  • Total Ordering: the type implements a total ordering even for special values—NaN is equal to itself and considered greater than any finite number, while infinities follow the expected ordering.

4. Automatic Type Promotion and Safety

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 (Decimal or F64).
  • Division by Zero: Follows IEEE 754 rules:
    • finite / 0 -> +Infinity or -Infinity
    • 0 / 0 -> NaN
    • Infinity / Infinity -> NaN
  • NaN & ±0 Normalisation: For deterministic behaviour across Number variants, all NaN values compare equal to each other, and +0 / -0 are treated as the same value for equality and ordering. Total ordering remains well-defined even in the presence of infinities or NaNs.

5. Precision with the decimal feature

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.

Feature Flags & Dependencies

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.

License

This project is licensed under either of

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.

Contributing

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.

Commit count: 0

cargo fmt