uninum 0.1.1

A robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
Documentation
//! Core types and error definitions for the Number type system.

#[cfg(feature = "decimal")]
use std::sync::Arc;

#[cfg(feature = "decimal")]
use rust_decimal::Decimal;

use crate::float::Float64;

/// A unified numeric type that provides automatic overflow handling, type
/// promotion, ergonomic primitive operations, and consistent cross-type
/// operations.
///
/// # Type Variants
///
/// **Integer Types:**
/// - `U64(u64)`, `I64(i64)` - 64-bit integers
///
/// **Float Types:**
/// - `F64(Float64)` - 64-bit IEEE 754 float (Ord + Hash)
///
/// **Decimal Type:**
/// - `Decimal(Arc<Decimal>)` - Arbitrary precision decimal (requires "decimal"
///   feature)
///
/// # Ergonomic Operations
///
/// All arithmetic and comparison operations work naturally with primitive
/// types.
///
/// ## **Important**: `&num + 1` vs `num + 1`
///
/// - **`&num + 1`** - Uses a reference, `num` remains available for reuse
/// - **`num + 1`** - Consumes (moves) `num`, making it unavailable afterward
///
/// ```rust
/// # use uninum::Number;
/// let num = Number::from(10i64);
///
/// // Using references - num remains available
/// let result1 = &num + 5; // Uses reference, num still available
/// let result2 = &num * 2; // Can reuse num again
/// let result3 = &num + &num; // Both are references
///
/// // num is still available here
///
/// // Using owned values - num gets consumed
/// let result4 = num + 1; // num is moved here
///                        // println!("{}", num);        // ❌ ERROR: num is no longer available
/// ```
///
/// ## All Operation Types
///
/// ```rust
/// # use uninum::Number;
/// let num = Number::from(10i64);
///
/// // Arithmetic operations (all work with references)
/// let add_result = &num + 5; // Number + i64
/// let mul_result = 2.5 * &num; // f64 * Number
/// let sub_result = &num - 3; // Number - i64
/// let div_result = &num / 2; // Number / i64
///
/// // Comparison operations
/// assert!(&num > 5); // Number > i64
/// assert!(15 > &num); // i64 > Number
/// assert!(&num == 10); // Number == i64
/// ```
///
/// # String Parsing
///
/// String parsing uses optimal type selection:
/// ```rust
/// # use uninum::Number;
/// # use std::convert::TryFrom;
/// // Positive integers parse to U64, negative to I64
/// let positive = Number::try_from("42").unwrap(); // -> U64(42)
/// let negative = Number::try_from("-42").unwrap(); // -> I64(-42)
/// let large = Number::try_from("70_000").unwrap(); // -> U64(70_000)
///
/// // Floats use highest precision (Decimal if enabled, F64 otherwise)
/// let float = Number::try_from("3.14").unwrap(); // -> Decimal(3.14) or F64(3.14)
///
/// // Type suffixes (e.g., `u64`) are rejected to avoid ambiguous parsing
/// assert!(Number::try_from("42u64").is_err());
/// ```
///
/// # Float Precision Note
///
/// Floating-point values are stored as `Float64`, so equality compares the
/// underlying `f64` bits exactly:
/// ```rust
/// # use uninum::Number;
/// let a = Number::from(3.141592653589793_f64);
/// let b = Number::from(3.1415926535897926_f64);
/// assert!(a != b);
/// ```
/// Use `approx_eq()` for precision-tolerant comparisons when a small epsilon is
/// acceptable.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum Number {
    #[cfg(feature = "decimal")]
    #[doc(hidden)]
    Decimal(Arc<Decimal>),
    #[doc(hidden)]
    U64(u64),
    #[doc(hidden)]
    I64(i64),
    #[doc(hidden)]
    F64(Float64),
}