expr_solver/number.rs
1//! Numeric type abstraction for the expression solver.
2//!
3//! This module provides a type alias `Number` that resolves to either `f64` or `Decimal`
4//! depending on the enabled feature flag. This allows the library to be used with either
5//! standard floating-point arithmetic (faster, simpler) or high-precision decimal arithmetic.
6//!
7//! ## Features
8//!
9//! - **`f64-floats`** (default): Standard f64 floating-point arithmetic
10//! - Fast and efficient for general-purpose math
11//! - Allows `Inf` and `NaN` results
12//! - Minimal error checking (only prevents panics)
13//!
14//! - **`decimal-precision`**: 128-bit Decimal for high precision
15//! - Exact decimal representation
16//! - Checked arithmetic with overflow/underflow detection
17//! - Domain validation for all operations
18//! - Ideal for financial calculations
19//!
20//! ## Type Alias
21//!
22//! The `Number` type alias resolves to:
23//! - `f64` when `f64-floats` feature is enabled
24//! - `rust_decimal::Decimal` when `decimal-precision` feature is enabled
25//!
26//! ## Internal Constants
27//!
28//! The `consts` module is internal and provides basic numeric constants (`ZERO`, `ONE`)
29//! used by the VM. Mathematical constants (pi, e, etc.) are provided through the
30//! type-specific symbol table implementations in `symbol/f64.rs` and `symbol/decimal.rs`.
31
32#[cfg(feature = "decimal-precision")]
33pub use rust_decimal::Decimal as Number;
34
35#[cfg(feature = "f64-floats")]
36pub type Number = f64;
37
38// Ensure exactly one feature is enabled
39#[cfg(all(feature = "f64-floats", feature = "decimal-precision"))]
40compile_error!("Cannot enable both 'f64-floats' and 'decimal-precision' features");
41
42#[cfg(not(any(feature = "f64-floats", feature = "decimal-precision")))]
43compile_error!("Must enable either 'f64-floats' or 'decimal-precision' feature");
44
45/// Internal numeric constants used by the VM.
46/// Mathematical constants like PI, E, etc. are provided through the symbol table.
47pub(crate) mod consts {
48 use super::Number;
49
50 #[cfg(feature = "decimal-precision")]
51 pub use rust_decimal::Decimal;
52
53 /// Zero constant
54 #[cfg(feature = "decimal-precision")]
55 pub const ZERO: Number = Decimal::ZERO;
56
57 #[cfg(feature = "f64-floats")]
58 pub const ZERO: Number = 0.0;
59
60 /// One constant
61 #[cfg(feature = "decimal-precision")]
62 pub const ONE: Number = Decimal::ONE;
63
64 #[cfg(feature = "f64-floats")]
65 pub const ONE: Number = 1.0;
66}
67
68/// Helper trait for parsing numbers from strings.
69pub trait ParseNumber: Sized {
70 /// Parse a number from a string.
71 fn parse_number(s: &str) -> Result<Self, String>;
72}
73
74#[cfg(feature = "decimal-precision")]
75impl ParseNumber for Number {
76 fn parse_number(s: &str) -> Result<Self, String> {
77 use rust_decimal::prelude::FromStr;
78 Number::from_str(s).map_err(|e| e.to_string())
79 }
80}
81
82#[cfg(feature = "f64-floats")]
83impl ParseNumber for Number {
84 fn parse_number(s: &str) -> Result<Self, String> {
85 s.parse::<f64>().map_err(|e| e.to_string())
86 }
87}
88
89/// Macro for creating numeric literals in a type-neutral way.
90///
91/// # Examples
92///
93/// ```
94/// use expr_solver::num;
95///
96/// let x = num!(42);
97/// let y = num!(3.14159);
98/// let z = num!(2.5);
99/// ```
100///
101/// This macro resolves to:
102/// - `$val as f64` when `f64-floats` is enabled
103/// - `dec!($val)` when `decimal-precision` is enabled
104#[macro_export]
105#[cfg(feature = "decimal-precision")]
106macro_rules! num {
107 ($val:expr) => {
108 rust_decimal_macros::dec!($val)
109 };
110}
111
112/// Macro for creating numeric literals in a type-neutral way.
113///
114/// # Examples
115///
116/// ```
117/// use expr_solver::num;
118///
119/// let x = num!(42);
120/// let y = num!(3.14159);
121/// let z = num!(2.5);
122/// ```
123///
124/// This macro resolves to:
125/// - `$val as f64` when `f64-floats` is enabled
126/// - `dec!($val)` when `decimal-precision` is enabled
127#[macro_export]
128#[cfg(feature = "f64-floats")]
129macro_rules! num {
130 ($val:expr) => {
131 $val as f64
132 };
133}