rust_finprim/
floatlike.rs

1use crate::RoundingMode;
2
3#[cfg(not(feature = "std"))]
4use libm;
5
6#[cfg(feature = "rust_decimal")]
7use rust_decimal::prelude::FromPrimitive;
8#[cfg(feature = "rust_decimal")]
9use rust_decimal::{Decimal, MathematicalOps};
10
11/// The FloatLike trait is designed to abstract over any fractional numeric type. By default,
12/// it supports f32, f64, and `rust_decimal`'s Decimal. This allows for the generic implementation of
13/// the mathematical operations supported by this library.
14pub trait FloatLike:
15    Copy
16    + core::fmt::Display
17    + core::fmt::Debug
18    + PartialOrd
19    + PartialEq
20    + core::ops::Add<Output = Self>
21    + core::ops::Sub<Output = Self>
22    + core::ops::Mul<Output = Self>
23    + core::ops::Div<Output = Self>
24    + core::ops::Neg<Output = Self>
25    + core::iter::Sum<Self>
26    + core::iter::Product<Self>
27    + core::ops::SubAssign
28    + core::ops::AddAssign
29    + core::ops::MulAssign
30{
31    const MAX: Self;
32    fn zero() -> Self;
33    fn one() -> Self;
34    fn powf(self, n: Self) -> Self;
35    fn two() -> Self {
36        Self::one() + Self::one()
37    }
38    fn abs(&self) -> Self;
39    fn is_zero(&self) -> bool {
40        *self == Self::zero()
41    }
42    fn min(self, other: Self) -> Self;
43    fn max(self, other: Self) -> Self;
44    fn from_u16(n: u16) -> Self;
45    fn from_usize(n: usize) -> Self;
46    fn from_i32(n: i32) -> Self;
47    fn from_f32(n: f32) -> Self;
48
49    fn round_with_mode(&self, dp: u32, mode: RoundingMode, epsilon: Self) -> Self;
50}
51
52// This macro generates implementations of the FloatLike trait for f32 and f64.
53#[crabtime::function]
54fn gen_floats(ftype: Vec<String>) {
55    for t in ftype.iter() {
56        let append = if *t == "f32" { "f" } else { "" };
57        crabtime::output! {
58            impl FloatLike for {{t}} {
59                const MAX: Self = {{t}}::MAX;
60                fn zero() -> Self {
61                    0.0
62                }
63                fn one() -> Self {
64                    1.0
65                }
66                fn powf(self, n: Self) -> Self {
67                    #[cfg(feature = "std")]
68                    { {{t}}::powf(self, n) }
69                    #[cfg(not(feature = "std"))]
70                    { libm::pow{{append}}(self, n) }
71                }
72                fn abs(&self) -> Self {
73                    #[cfg(feature = "std")]
74                    { {{t}}::abs(*self) }
75                    #[cfg(not(feature = "std"))]
76                    { libm::fabs{{append}}(*self) }
77                }
78                fn min(self, other: Self) -> Self {
79                    {{t}}::min(self, other)
80                }
81                fn max(self, other: Self) -> Self {
82                    {{t}}::max(self, other)
83                }
84                fn from_u16(n: u16) -> Self {
85                    n as {{t}}
86                }
87                fn from_usize(n: usize) -> Self {
88                    n as {{t}}
89                }
90                fn from_i32(n: i32) -> Self {
91                    n as {{t}}
92                }
93                fn from_f32(n: f32) -> Self {
94                    n as {{t}}
95                }
96                fn round_with_mode(&self, dp: u32, mode: RoundingMode, epsilon: Self) -> Self {
97                    let factor = FloatLike::powf(10.0{{t}}, dp as {{t}});
98                    let shifted = *self * factor;
99
100                    #[cfg(feature = "std")]
101                    let floor = shifted.floor();
102                    #[cfg(not(feature = "std"))]
103                    let floor = libm::floor{{append}}(shifted);
104
105                    // Shifted rounded
106                    #[cfg(feature = "std")]
107                    let shifted_rd = shifted.round();
108                    #[cfg(not(feature = "std"))]
109                    let shifted_rd = libm::round{{append}}(shifted);
110
111                    let diff_floor = shifted - floor;
112
113                    #[cfg(feature = "std")]
114                    let ceil = shifted.ceil();
115                    #[cfg(not(feature = "std"))]
116                    let ceil = libm::ceil{{append}}(shifted);
117
118                    let rounded = match mode {
119                        RoundingMode::HalfToEven =>
120                        // Case 1: we aren't at the half point, return normal "rounding"
121                        {
122                            if (diff_floor.abs() - 0.5).abs() > epsilon {
123                                shifted_rd
124                            } else {
125                                // Case 2: we are at the half point, round to the nearest even number
126                                if floor as i64 % 2 == 0 {
127                                    floor
128                                } else {
129                                    ceil
130                                }
131                            }
132                        }
133                        RoundingMode::HalfTowardZero => {
134                            // Case 1: we aren't at the half point, return normal "rounding"
135                            if (diff_floor.abs() - 0.5).abs() > epsilon {
136                                shifted_rd
137                            } else {
138                                // Case 2: we are at the half point, round toward zero
139                                if shifted > 0.0 {
140                                    floor
141                                } else {
142                                    ceil
143                                }
144                            }
145                        }
146                        RoundingMode::HalfAwayFromZero => {
147                            // Case 1: we aren't at the half point, return normal "rounding"
148                            if (diff_floor.abs() - 0.5).abs() > epsilon {
149                                shifted_rd
150                            } else {
151                                // Case 2: we are at the half point, round away from zero
152                                if shifted > 0.0 {
153                                    ceil
154                                } else {
155                                    floor
156                                }
157                            }
158                        }
159                        RoundingMode::TowardZero => {
160                            // Round down
161                            if shifted > 0.0 {
162                                floor
163                            } else {
164                                ceil
165                            }
166                        }
167                        RoundingMode::AwayFromZero => {
168                            // Round up
169                            if shifted > 0.0 {
170                                ceil
171                            } else {
172                                floor
173                            }
174                        }
175                        // Round down for positive numbers, round up for negative numbers
176                        RoundingMode::ToNegativeInfinity => floor,
177                        // Round up for positive numbers, round down for negative numbers
178                        RoundingMode::ToInfinity => ceil
179                    };
180                    rounded / factor
181                }
182            }
183        }
184    }
185}
186gen_floats!(["f32", "f64"]);
187
188#[cfg(feature = "rust_decimal")]
189impl FloatLike for Decimal {
190    const MAX: Self = Decimal::MAX;
191    fn zero() -> Self {
192        Decimal::ZERO
193    }
194
195    fn one() -> Self {
196        Decimal::ONE
197    }
198    fn powf(self, n: Self) -> Self {
199        self.powd(n)
200    }
201    fn abs(&self) -> Self {
202        Decimal::abs(self)
203    }
204    fn round_with_mode(&self, dp: u32, mode: RoundingMode, epsilon: Self) -> Self {
205        let rounding_mode = match mode {
206            RoundingMode::HalfToEven => rust_decimal::RoundingStrategy::MidpointNearestEven,
207            RoundingMode::HalfAwayFromZero => rust_decimal::RoundingStrategy::MidpointAwayFromZero,
208            RoundingMode::HalfTowardZero => rust_decimal::RoundingStrategy::MidpointTowardZero,
209            RoundingMode::TowardZero => rust_decimal::RoundingStrategy::ToZero,
210            RoundingMode::AwayFromZero => rust_decimal::RoundingStrategy::AwayFromZero,
211            RoundingMode::ToNegativeInfinity => rust_decimal::RoundingStrategy::ToNegativeInfinity,
212            RoundingMode::ToInfinity => rust_decimal::RoundingStrategy::ToPositiveInfinity,
213        };
214        self.round_dp_with_strategy(dp, rounding_mode)
215    }
216    fn min(self, other: Self) -> Self {
217        Decimal::min(self, other)
218    }
219    fn max(self, other: Self) -> Self {
220        Decimal::max(self, other)
221    }
222    fn from_usize(n: usize) -> Self {
223        Decimal::from(n)
224    }
225    fn from_u16(n: u16) -> Self {
226        Decimal::from(n)
227    }
228    fn from_i32(n: i32) -> Self {
229        Decimal::from(n)
230    }
231    // This may panic if the f32 cannot be represented as a Decimal
232    fn from_f32(n: f32) -> Self {
233        <Decimal as FromPrimitive>::from_f32(n).expect("Failed to convert f32 to Decimal")
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[cfg(not(feature = "std"))]
242    extern crate std;
243    #[cfg(not(feature = "std"))]
244    use std::assert_eq;
245
246    #[test]
247    fn test_floatlike_rounding() {
248        let value: f32 = 2.5;
249        assert_eq!(value.round_with_mode(0, RoundingMode::HalfToEven, 1e-5), 2.0);
250        assert_eq!(value.round_with_mode(0, RoundingMode::HalfAwayFromZero, 1e-5), 3.0);
251        assert_eq!(value.round_with_mode(0, RoundingMode::HalfTowardZero, 1e-5), 2.0);
252        assert_eq!(value.round_with_mode(0, RoundingMode::TowardZero, 1e-5), 2.0);
253        assert_eq!(value.round_with_mode(0, RoundingMode::AwayFromZero, 1e-5), 3.0);
254        assert_eq!(value.round_with_mode(0, RoundingMode::ToNegativeInfinity, 1e-5), 2.0);
255        assert_eq!(value.round_with_mode(0, RoundingMode::ToInfinity, 1e-5), 3.0);
256
257        let value: f32 = -2.5;
258        assert_eq!(value.round_with_mode(0, RoundingMode::HalfToEven, 1e-5), -2.0);
259        assert_eq!(value.round_with_mode(0, RoundingMode::HalfAwayFromZero, 1e-5), -3.0);
260        assert_eq!(value.round_with_mode(0, RoundingMode::HalfTowardZero, 1e-5), -2.0);
261        assert_eq!(value.round_with_mode(0, RoundingMode::TowardZero, 1e-5), -2.0);
262        assert_eq!(value.round_with_mode(0, RoundingMode::AwayFromZero, 1e-5), -3.0);
263        assert_eq!(value.round_with_mode(0, RoundingMode::ToNegativeInfinity, 1e-5), -3.0);
264        assert_eq!(value.round_with_mode(0, RoundingMode::ToInfinity, 1e-5), -2.0);
265
266        let value: f32 = 3.005;
267        assert_eq!(value.round_with_mode(2, RoundingMode::HalfToEven, 1e-5), 3.00);
268        assert_eq!(value.round_with_mode(2, RoundingMode::HalfAwayFromZero, 1e-5), 3.01);
269        assert_eq!(value.round_with_mode(2, RoundingMode::HalfTowardZero, 1e-5), 3.00);
270        assert_eq!(value.round_with_mode(2, RoundingMode::TowardZero, 1e-5), 3.00);
271        assert_eq!(value.round_with_mode(2, RoundingMode::AwayFromZero, 1e-5), 3.01);
272        assert_eq!(value.round_with_mode(2, RoundingMode::ToNegativeInfinity, 1e-5), 3.00);
273        assert_eq!(value.round_with_mode(2, RoundingMode::ToInfinity, 1e-5), 3.01);
274
275        let value: f64 = -3.005;
276        assert_eq!(value.round_with_mode(2, RoundingMode::HalfToEven, 1e-5), -3.00);
277        assert_eq!(value.round_with_mode(2, RoundingMode::HalfAwayFromZero, 1e-5), -3.01);
278        assert_eq!(value.round_with_mode(2, RoundingMode::HalfTowardZero, 1e-5), -3.00);
279        assert_eq!(value.round_with_mode(2, RoundingMode::TowardZero, 1e-5), -3.00);
280        assert_eq!(value.round_with_mode(2, RoundingMode::AwayFromZero, 1e-5), -3.01);
281        assert_eq!(value.round_with_mode(2, RoundingMode::ToNegativeInfinity, 1e-5), -3.01);
282        assert_eq!(value.round_with_mode(2, RoundingMode::ToInfinity, 1e-5), -3.00);
283
284        let value: f64 = 0.333;
285        assert_eq!(value.round_with_mode(2, RoundingMode::HalfToEven, 1e-5), 0.33);
286        assert_eq!(value.round_with_mode(2, RoundingMode::HalfAwayFromZero, 1e-5), 0.33);
287        assert_eq!(value.round_with_mode(2, RoundingMode::HalfTowardZero, 1e-5), 0.33);
288        assert_eq!(value.round_with_mode(2, RoundingMode::TowardZero, 1e-5), 0.33);
289        assert_eq!(value.round_with_mode(2, RoundingMode::AwayFromZero, 1e-5), 0.34);
290        assert_eq!(value.round_with_mode(2, RoundingMode::ToNegativeInfinity, 1e-5), 0.33);
291        assert_eq!(value.round_with_mode(2, RoundingMode::ToInfinity, 1e-5), 0.34);
292
293        let value: f64 = -0.333;
294        assert_eq!(value.round_with_mode(2, RoundingMode::HalfToEven, 1e-5), -0.33);
295        assert_eq!(value.round_with_mode(2, RoundingMode::HalfAwayFromZero, 1e-5), -0.33);
296        assert_eq!(value.round_with_mode(2, RoundingMode::HalfTowardZero, 1e-5), -0.33);
297        assert_eq!(value.round_with_mode(2, RoundingMode::TowardZero, 1e-5), -0.33);
298        assert_eq!(value.round_with_mode(2, RoundingMode::AwayFromZero, 1e-5), -0.34);
299        assert_eq!(value.round_with_mode(2, RoundingMode::ToNegativeInfinity, 1e-5), -0.34);
300        assert_eq!(value.round_with_mode(2, RoundingMode::ToInfinity, 1e-5), -0.33);
301
302        let value: f64 = 0.555;
303        assert_eq!(value.round_with_mode(2, RoundingMode::HalfToEven, 1e-5), 0.56);
304        assert_eq!(value.round_with_mode(2, RoundingMode::HalfAwayFromZero, 1e-5), 0.56);
305        assert_eq!(value.round_with_mode(2, RoundingMode::HalfTowardZero, 1e-5), 0.55);
306        assert_eq!(value.round_with_mode(2, RoundingMode::TowardZero, 1e-5), 0.55);
307        assert_eq!(value.round_with_mode(2, RoundingMode::AwayFromZero, 1e-5), 0.56);
308        assert_eq!(value.round_with_mode(2, RoundingMode::ToNegativeInfinity, 1e-5), 0.55);
309        assert_eq!(value.round_with_mode(2, RoundingMode::ToInfinity, 1e-5), 0.56);
310
311        let value: f64 = 3.00;
312        assert_eq!(value.round_with_mode(2, RoundingMode::HalfToEven, 1e-5), 3.00);
313        assert_eq!(value.round_with_mode(2, RoundingMode::HalfAwayFromZero, 1e-5), 3.00);
314        assert_eq!(value.round_with_mode(2, RoundingMode::HalfTowardZero, 1e-5), 3.00);
315        assert_eq!(value.round_with_mode(2, RoundingMode::TowardZero, 1e-5), 3.00);
316        assert_eq!(value.round_with_mode(2, RoundingMode::AwayFromZero, 1e-5), 3.00);
317        assert_eq!(value.round_with_mode(2, RoundingMode::ToNegativeInfinity, 1e-5), 3.00);
318        assert_eq!(value.round_with_mode(2, RoundingMode::ToInfinity, 1e-5), 3.00);
319    }
320}