uninum 0.1.1

A robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
Documentation
# 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](QUICK_REFERENCE.md) 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 `BTreeMap`s and `HashMap`s 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`:

```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:

```toml
[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

```rust
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 `Number`s and references `&Number`. To avoid consuming your variables, always use references (`&`) for operations.

```rust
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.

```rust
// 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:
```toml
[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

* Apache License, Version 2.0, ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT]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.

## 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](CONTRIBUTING.md) for details on how to share feedback
or follow the internal release workflow.