mathhook_core/functions/elementary/
rounding.rs

1//! Rounding function implementations
2
3use crate::core::{Expression, Number};
4use num_traits::Signed;
5use num_traits::ToPrimitive;
6
7/// Sign function
8///
9/// # Mathematical Definition
10///
11/// sign(x) = { -1  if x < 0
12///           {  0  if x = 0
13///           {  1  if x > 0
14///
15/// # Arguments
16///
17/// * `arg` - Expression to compute sign of
18///
19/// # Returns
20///
21/// Sign expression (-1, 0, or 1)
22///
23/// # Examples
24///
25/// ```
26/// use mathhook_core::functions::elementary::rounding::sign;
27/// use mathhook_core::{expr, Expression};
28///
29/// assert_eq!(sign(&expr!(-5)), expr!(-1));
30/// assert_eq!(sign(&expr!(0)), expr!(0));
31/// assert_eq!(sign(&expr!(5)), expr!(1));
32/// ```
33pub fn sign(arg: &Expression) -> Expression {
34    match arg {
35        Expression::Number(n) => evaluate_sign_number(n),
36        _ => Expression::function("sign", vec![arg.clone()]),
37    }
38}
39
40fn evaluate_sign_number(n: &Number) -> Expression {
41    match n {
42        Number::Integer(i) => Expression::integer(i.signum()),
43        Number::Float(f) => {
44            if *f > 0.0 {
45                Expression::integer(1)
46            } else if *f < 0.0 {
47                Expression::integer(-1)
48            } else {
49                Expression::integer(0)
50            }
51        }
52        Number::BigInteger(bi) => {
53            use num_bigint::Sign;
54            match bi.sign() {
55                Sign::Plus => Expression::integer(1),
56                Sign::Minus => Expression::integer(-1),
57                Sign::NoSign => Expression::integer(0),
58            }
59        }
60        Number::Rational(r) => {
61            if r.is_positive() {
62                Expression::integer(1)
63            } else if r.is_negative() {
64                Expression::integer(-1)
65            } else {
66                Expression::integer(0)
67            }
68        }
69    }
70}
71
72/// Floor function (round down to nearest integer)
73///
74/// # Mathematical Definition
75///
76/// floor(x) = ⌊x⌋ = greatest integer ≤ x
77///
78/// # Arguments
79///
80/// * `arg` - Expression to floor
81///
82/// # Returns
83///
84/// Floor expression
85///
86/// # Examples
87///
88/// ```
89/// use mathhook_core::functions::elementary::rounding::floor;
90/// use mathhook_core::{expr, Expression};
91///
92/// assert_eq!(floor(&Expression::float(3.7)), Expression::integer(3));
93/// assert_eq!(floor(&Expression::float(-2.3)), Expression::integer(-3));
94/// ```
95pub fn floor(arg: &Expression) -> Expression {
96    match arg {
97        Expression::Number(Number::Integer(i)) => Expression::integer(*i),
98        Expression::Number(Number::Float(f)) => Expression::integer(f.floor() as i64),
99        Expression::Number(Number::BigInteger(_)) => arg.clone(),
100        Expression::Number(Number::Rational(r)) => {
101            Expression::integer(r.to_f64().unwrap_or(0.0).floor() as i64)
102        }
103        _ => Expression::function("floor", vec![arg.clone()]),
104    }
105}
106
107/// Ceiling function (round up to nearest integer)
108///
109/// # Mathematical Definition
110///
111/// ceil(x) = ⌈x⌉ = smallest integer ≥ x
112///
113/// # Arguments
114///
115/// * `arg` - Expression to ceil
116///
117/// # Returns
118///
119/// Ceiling expression
120///
121/// # Examples
122///
123/// ```
124/// use mathhook_core::functions::elementary::rounding::ceil;
125/// use mathhook_core::{expr, Expression};
126///
127/// assert_eq!(ceil(&Expression::float(3.2)), Expression::integer(4));
128/// assert_eq!(ceil(&Expression::float(-2.7)), Expression::integer(-2));
129/// ```
130pub fn ceil(arg: &Expression) -> Expression {
131    match arg {
132        Expression::Number(Number::Integer(i)) => Expression::integer(*i),
133        Expression::Number(Number::Float(f)) => Expression::integer(f.ceil() as i64),
134        Expression::Number(Number::BigInteger(_)) => arg.clone(),
135        Expression::Number(Number::Rational(r)) => {
136            Expression::integer(r.to_f64().unwrap_or(0.0).ceil() as i64)
137        }
138        _ => Expression::function("ceil", vec![arg.clone()]),
139    }
140}
141
142/// Round function (round to nearest integer)
143///
144/// # Mathematical Definition
145///
146/// round(x) rounds to nearest integer, with ties rounding away from zero
147///
148/// # Arguments
149///
150/// * `arg` - Expression to round
151///
152/// # Returns
153///
154/// Rounded expression
155///
156/// # Examples
157///
158/// ```
159/// use mathhook_core::functions::elementary::rounding::round;
160/// use mathhook_core::{expr, Expression};
161///
162/// assert_eq!(round(&Expression::float(3.4)), Expression::integer(3));
163/// assert_eq!(round(&Expression::float(3.6)), Expression::integer(4));
164/// assert_eq!(round(&Expression::float(3.5)), Expression::integer(4));
165/// ```
166pub fn round(arg: &Expression) -> Expression {
167    match arg {
168        Expression::Number(Number::Integer(i)) => Expression::integer(*i),
169        Expression::Number(Number::Float(f)) => Expression::integer(f.round() as i64),
170        Expression::Number(Number::BigInteger(_)) => arg.clone(),
171        Expression::Number(Number::Rational(r)) => {
172            Expression::integer(r.to_f64().unwrap_or(0.0).round() as i64)
173        }
174        _ => Expression::function("round", vec![arg.clone()]),
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_sign() {
184        assert_eq!(sign(&Expression::integer(-5)), Expression::integer(-1));
185        assert_eq!(sign(&Expression::integer(0)), Expression::integer(0));
186        assert_eq!(sign(&Expression::integer(5)), Expression::integer(1));
187    }
188
189    #[test]
190    fn test_floor() {
191        assert_eq!(floor(&Expression::float(3.7)), Expression::integer(3));
192        assert_eq!(floor(&Expression::float(-2.3)), Expression::integer(-3));
193        assert_eq!(floor(&Expression::integer(5)), Expression::integer(5));
194    }
195
196    #[test]
197    fn test_ceil() {
198        assert_eq!(ceil(&Expression::float(3.2)), Expression::integer(4));
199        assert_eq!(ceil(&Expression::float(-2.7)), Expression::integer(-2));
200        assert_eq!(ceil(&Expression::integer(5)), Expression::integer(5));
201    }
202
203    #[test]
204    fn test_round() {
205        assert_eq!(round(&Expression::float(3.4)), Expression::integer(3));
206        assert_eq!(round(&Expression::float(3.6)), Expression::integer(4));
207        assert_eq!(round(&Expression::float(3.5)), Expression::integer(4));
208        assert_eq!(round(&Expression::integer(5)), Expression::integer(5));
209    }
210}