uninum 0.1.1

A robust, ergonomic unified number type for Rust with automatic overflow handling, type promotion, and cross-type consistency.
Documentation
//! A unified numeric type system for Rust with automatic overflow handling and
//! ergonomic primitive operations.
//!
//! This crate provides the `Number` enum which unifies all numeric types
//! and provides consistent behavior across operations with automatic type
//! promotion and overflow handling.

#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![deny(clippy::all, clippy::cargo)]
#![warn(clippy::nursery, clippy::pedantic)]
// Multiple versions of some transitive dev-dependencies (e.g., `syn`) are unavoidable
// until upstream crates align on editions. Restrict to allow to this lint only.
#![allow(clippy::multiple_crate_versions)]
//! # Key Features
//!
//! - **Ergonomic operations**: `number + 5`, `5 + number`, `&number + 5` all
//!   work naturally
//! - **Optimal string parsing**: Integers use the smallest fitting type, floats use
//!   the highest precision
//! - **No undefined behavior**: All operations are mathematically well-defined
//! - **Automatic type promotion**: Overflow protection through intelligent type
//!   promotion
//! - **Cross-type operations**: Mix different numeric types seamlessly
//! - **IEEE 754 semantics**: Special values (NaN, Infinity) are preserved
//!   correctly
//!
//! # Float Construction
//!
//! Use the `num!` macro or direct construction with Float64 wrapper:
//!
//! ```rust
//! use uninum::{num, Float64, Number};
//!
//! // Using num! macro (recommended):
//! let a = num!(2.718);
//!
//! // Direct construction:
//! let b = Number::from(2.718_f64);
//! ```
//!
//! # Quick Start
//!
//! ```rust
//! use uninum::{num, Number};
//!
//! let a = Number::from(10u64);
//! let b = num!(3.14); // Using num! macro
//!
//! // Ergonomic operations with primitives
//! let result1 = &a + 5; // Number + i32
//! let result2 = 2.5 * &a; // f64 * Number
//! let result3 = &a + &b; // Number + Number
//!
//! // Comparison operations
//! assert!(&a > 5); // Number > i32
//! assert!(15 > &a); // i32 > Number
//! assert!(&a == 10); // Number == i32
//!
//! // String parsing with optimal type selection
//! let small = Number::try_from("42").unwrap(); // -> U64(42)
//! let large = Number::try_from("70_000").unwrap(); // -> U64(70_000)
//! let float = Number::try_from("3.14").unwrap(); // -> Decimal(3.14) or F64(3.14)
//! ```
//!
//! # Division by Zero Behavior
//!
//! Division operations follow IEEE 754 inspired semantics:
//! - `finite / 0` returns `+∞` or `-∞` (signed infinity)
//! - `0 / 0` returns `NaN` (Not a Number)
//! - Integer division by zero promotes to F64 and follows IEEE 754 rules
//!
//! Division operations never panic and always return a valid `Number`.
//!
//! # Special Float Values with Decimal Feature
//!
//! When the `decimal` feature is enabled, operations involving NaN or Infinity
//! always use F64 to preserve IEEE 754 semantics. Additionally, float division
//! with integer values promotes to Decimal for better precision:
//! - `NaN + Decimal` returns `F64(NaN)`, not `Decimal(0)`
//! - `Infinity * Decimal` returns `F64(Infinity)`, not overflow
//! - `num!(1.0) / num!(2.0)` returns `Decimal(0.5)` for exact representation
//! - Special values propagate correctly through all operations

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

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

/// Internal modules
mod constants;
mod conversions;
#[cfg(feature = "decimal")]
mod decimal_pool;
mod fast_dispatch;
pub mod float;
#[macro_use]
mod macros;
pub mod math;
mod ops;
mod ref_ops;
#[cfg(feature = "serde")]
mod serde_impl;
pub mod traits;
mod types;

/// Public API exports
pub use conversions::errors::{ParseNumberError, TryFromNumberError};
pub use float::Float64;
// The num! macro is exported via #[macro_export] in macros.rs
/// Internal macro imports
#[cfg(feature = "decimal")]
use macros::accessors::impl_try_get_decimal_method;
use macros::accessors::{impl_try_get_float_methods, impl_try_get_integer_methods};
pub use math::RoundingStrategy;
#[cfg(feature = "bitwise")]
pub use ops::bitwise::BitwiseError;
pub use types::Number;

const I128_F64_MIN: f64 = -170_141_183_460_469_231_731_687_303_715_884_105_728.0; // -2^127
const I128_F64_EXCL_MAX: f64 = 170_141_183_460_469_231_731_687_303_715_884_105_728.0; //  2^127

impl Number {
    // Getter methods for integer types
    impl_try_get_integer_methods!(try_get_u64, U64, u64, try_get_i64, I64, i64,);

    // Getter methods for float types
    impl_try_get_float_methods!(try_get_f64, F64, f64,);

    // Getter method for decimal type
    #[cfg(feature = "decimal")]
    impl_try_get_decimal_method!();

    /// Converts the value to `i128` if it represents an integer within the
    /// `i128` range. Returns `None` on overflow or for non-integer values.
    #[must_use]
    pub fn as_i128(&self) -> Option<i128> {
        match self {
            Self::U64(n) => Some(i128::from(*n)),
            Self::I64(n) => Some(i128::from(*n)),
            Self::F64(n) => {
                let f = n.0;
                if f.fract() == 0.0 && (I128_F64_MIN..I128_F64_EXCL_MAX).contains(&f) {
                    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
                    {
                        Some(f as i128)
                    }
                } else {
                    None
                }
            }
            #[cfg(feature = "decimal")]
            Self::Decimal(d) => {
                if d.fract().is_zero() {
                    use rust_decimal::prelude::ToPrimitive;
                    d.to_i128()
                } else {
                    None
                }
            }
        }
    }
}