irox_tools/primitives/
f64.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! A collection of utilities for the f64 built-in
7//!
8
9use crate::{ToF64, ToSigned, WrappingSub};
10
11///
12/// Finds the minimum and maximum value in the provided iterator.
13/// Example:
14/// ```
15/// let values : Vec<f64> = vec![0.0, 5.0, 30.0, 20.0, 2.0];
16/// let (min, max) = irox_tools::f64::min_max(&values);
17///
18/// assert_eq!(min, 0.0);
19/// assert_eq!(max, 30.0);
20/// ```
21#[must_use]
22pub fn min_max(iter: &[f64]) -> (f64, f64) {
23    let mut min = f64::MAX;
24    let mut max = f64::MIN;
25
26    for val in iter {
27        min = min.min(*val);
28        max = max.max(*val);
29    }
30
31    (min, max)
32}
33
34pub trait FloatExt {
35    type Type;
36    type Size;
37    fn trunc(self) -> Self::Type;
38    fn fract(self) -> Self::Type;
39    fn abs(self) -> Self::Type;
40    fn round(self) -> Self::Type;
41    fn floor(self) -> Self::Type;
42    fn ceil(self) -> Self::Type;
43    fn signum(self) -> Self::Type;
44
45    fn exp(self) -> Self::Type;
46    fn ln(self) -> Self::Type;
47
48    fn powi(self, val: i32) -> Self::Type;
49    fn powf(self, val: Self::Type) -> Self::Type;
50
51    fn sqrt(self) -> Self::Type;
52    fn to_bits(self) -> Self::Size;
53    fn exponent(self) -> u16;
54    fn significand(self) -> Self::Size;
55}
56
57#[cfg(not(feature = "std"))]
58impl FloatExt for f64 {
59    type Type = f64;
60    type Size = u64;
61
62    ///
63    /// Truncate the value
64    /// Just casts to u64 then back to f64.
65    fn trunc(self) -> f64 {
66        (self as u64) as f64
67    }
68
69    fn fract(self) -> f64 {
70        self - self.trunc()
71    }
72
73    ///
74    /// Force the value to be positive by zeroing out the highest sign bit.
75    fn abs(self) -> f64 {
76        f64::from_bits(self.to_bits() & 0x7FFF_FFFF_FFFF_FFFFu64)
77    }
78
79    fn round(self) -> f64 {
80        (self + 0.5 * self.signum()).trunc()
81    }
82
83    fn floor(self) -> f64 {
84        if self.is_sign_negative() {
85            return (self - 1.0).trunc();
86        }
87        self.trunc()
88    }
89
90    fn ceil(self) -> f64 {
91        if self.is_sign_positive() {
92            return (self + 1.0).trunc();
93        }
94        self.trunc()
95    }
96
97    fn signum(self) -> f64 {
98        if self.is_nan() {
99            return f64::NAN;
100        }
101        if self.is_sign_negative() {
102            return -1.0;
103        }
104        1.0
105    }
106
107    ///
108    /// Implementation of Exponential Function from NIST DTMF eq 4.2.19: `<https://dlmf.nist.gov/4.2.E19>`
109    fn exp(self) -> Self::Type {
110        if self.is_nan() || self.is_infinite() {
111            return self;
112        }
113        let mut out = 1.0;
114        let i = self;
115        let mut idx = 1;
116        let mut next = self;
117
118        while next.abs() != 0.0 {
119            out += next;
120            idx += 1;
121            next *= i / idx as Self::Type;
122        }
123
124        out
125    }
126
127    ///
128    /// Implementation of Natural Logarithm using NIST DLMF eq 4.6.4: `<https://dlmf.nist.gov/4.6.E4>`
129    fn ln(self) -> Self::Type {
130        if !self.is_normal() {
131            return self;
132        }
133        let z = self;
134        if z == 0. {
135            return 1.;
136        } else if z < 0. {
137            return f64::NAN;
138        }
139        let iter = (z - 1.) / (z + 1.);
140        let mut out = 0.0;
141        let mut next = 2.0 * iter;
142        let mut idx = 1.0;
143        let mut base = iter;
144        while next != 0.0 {
145            out += next;
146            idx += 2.0;
147            base *= iter * iter;
148            next = 2.0 * base / idx;
149        }
150        out
151    }
152
153    ///
154    /// Implementation of general power function using NIST DLMF eq 4.2.26: `<https://dlmf.nist.gov/4.2.E26>`
155    fn powf(self, a: Self::Type) -> Self::Type {
156        if !self.is_normal() {
157            return self;
158        }
159        let z = self;
160
161        (a * z.ln()).exp()
162    }
163
164    /// Naive implementation of integer power fn.  Will do something smarter later.
165    fn powi(self, val: i32) -> Self::Type {
166        if !self.is_normal() {
167            return self;
168        }
169        let mut out = self;
170        let i = self;
171        for _ in 0..val.abs() {
172            out *= i;
173        }
174        out
175    }
176
177    fn sqrt(self) -> Self::Type {
178        self.powf(0.5)
179    }
180
181    fn to_bits(self) -> Self::Size {
182        f64::to_bits(self)
183    }
184
185    fn exponent(self) -> u16 {
186        ((self.to_bits() >> 52) & 0x7FF) as u16
187    }
188
189    fn significand(self) -> Self::Size {
190        self.to_bits() & 0xF_FFFF_FFFF_FFFF
191    }
192}
193
194#[cfg(feature = "std")]
195impl FloatExt for f64 {
196    type Type = f64;
197    type Size = u64;
198
199    fn trunc(self) -> Self::Type {
200        f64::trunc(self)
201    }
202
203    fn fract(self) -> Self::Type {
204        f64::fract(self)
205    }
206
207    fn abs(self) -> Self::Type {
208        f64::abs(self)
209    }
210
211    fn round(self) -> Self::Type {
212        f64::round(self)
213    }
214
215    fn floor(self) -> Self::Type {
216        f64::floor(self)
217    }
218
219    fn ceil(self) -> Self::Type {
220        f64::ceil(self)
221    }
222
223    fn signum(self) -> Self::Type {
224        f64::signum(self)
225    }
226
227    fn exp(self) -> Self::Type {
228        f64::exp(self)
229    }
230
231    fn ln(self) -> Self::Type {
232        f64::ln(self)
233    }
234
235    fn powi(self, val: i32) -> Self::Type {
236        f64::powi(self, val)
237    }
238
239    fn powf(self, val: Self::Type) -> Self::Type {
240        f64::powf(self, val)
241    }
242
243    fn sqrt(self) -> Self::Type {
244        f64::sqrt(self)
245    }
246
247    fn to_bits(self) -> Self::Size {
248        f64::to_bits(self)
249    }
250
251    fn exponent(self) -> u16 {
252        ((self.to_bits() >> 52) & 0x7FF) as u16
253    }
254
255    fn significand(self) -> Self::Size {
256        self.to_bits() & 0xF_FFFF_FFFF_FFFF
257    }
258}
259
260impl WrappingSub for f64 {
261    fn wrapping_sub(&self, rhs: Self) -> Self {
262        self - rhs
263    }
264}
265impl ToF64 for f64 {
266    fn to_f64(&self) -> f64 {
267        *self
268    }
269}
270impl ToSigned for f64 {
271    type Output = f64;
272
273    fn to_signed(self) -> Self::Output {
274        self
275    }
276    fn negative_one() -> Self::Output {
277        -1.
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    #[test]
284    pub fn test_ln() {
285        assert_eq_eps!(0.0, crate::f64::FloatExt::ln(1.0f64), 1e-16);
286        assert_eq_eps!(1.0, crate::f64::FloatExt::ln(core::f64::consts::E), 1e-15);
287        assert_eq_eps!(4.605170185988092, crate::f64::FloatExt::ln(100f64), 1e-13);
288        assert_eq_eps!(
289            11.090339630053647,
290            crate::f64::FloatExt::ln(u16::MAX as f64),
291            1e-11
292        );
293    }
294
295    #[test]
296    pub fn test_exp() {
297        assert_eq_eps!(1.0, crate::f64::FloatExt::exp(0.0f64), 1e-16);
298        assert_eq_eps!(
299            core::f64::consts::E,
300            crate::f64::FloatExt::exp(1.0f64),
301            1e-15
302        );
303        assert_eq_eps!(7.38905609893065, crate::f64::FloatExt::exp(2.0f64), 1e-14);
304        assert_eq_eps!(
305            15.154262241479262,
306            crate::f64::FloatExt::exp(core::f64::consts::E),
307            1e-15
308        );
309    }
310
311    #[test]
312    pub fn test_sqrt() {
313        assert_eq!(2., crate::f64::FloatExt::sqrt(4.0f64));
314    }
315}