Skip to main content

ferroui_base/numerics/
single.rs

1//! Scalar helpers shared by the numerics types: invariant formatting of a
2//! single-precision float, NaN-propagating minimum and maximum, and the IEEE
3//! remainder.
4
5use std::fmt::{self, Write};
6
7use crate::utilities::math_utilities::{max_f32, min_f32};
8
9/// Small stack buffer used to post-process `{:e}` output without allocating.
10struct StackBuf {
11    bytes: [u8; 32],
12    len: usize,
13}
14
15impl Write for StackBuf {
16    fn write_str(&mut self, s: &str) -> fmt::Result {
17        let end = self.len + s.len();
18        if end > self.bytes.len() {
19            return Err(fmt::Error);
20        }
21        self.bytes[self.len..end].copy_from_slice(s.as_bytes());
22        self.len = end;
23        Ok(())
24    }
25}
26
27/// `Display` adapter printing a single-precision float the way the reference
28/// runtime does under the invariant culture: the shortest round-trippable
29/// digits, plain notation for decimal exponents in `-4..=8`, scientific
30/// notation (`1E+09`, `1E-05`) otherwise, and `NaN` / `Infinity` /
31/// `-Infinity` for the non-finite values.
32#[derive(Clone, Copy, Debug)]
33pub struct InvariantF32(pub f32);
34
35impl fmt::Display for InvariantF32 {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        let value = self.0;
38        if value.is_nan() {
39            return f.write_str("NaN");
40        }
41        if value.is_infinite() {
42            return f.write_str(if value > 0.0 { "Infinity" } else { "-Infinity" });
43        }
44        if value == 0.0 {
45            return write!(f, "{}", value);
46        }
47
48        let mut buf = StackBuf {
49            bytes: [0; 32],
50            len: 0,
51        };
52        write!(buf, "{:e}", value)?;
53        let text = std::str::from_utf8(&buf.bytes[..buf.len]).map_err(|_| fmt::Error)?;
54        let (mantissa, exponent) = text.split_once('e').ok_or(fmt::Error)?;
55        let exponent: i32 = exponent.parse().map_err(|_| fmt::Error)?;
56        if (-4..=8).contains(&exponent) {
57            return write!(f, "{}", value);
58        }
59        write!(
60            f,
61            "{}E{}{:02}",
62            mantissa,
63            if exponent < 0 { '-' } else { '+' },
64            exponent.unsigned_abs()
65        )
66    }
67}
68
69/// The larger of two floats; NaN is propagated and `+0.0 > -0.0`.
70#[inline]
71pub(crate) fn max(value1: f32, value2: f32) -> f32 {
72    max_f32(value1, value2)
73}
74
75/// The smaller of two floats; NaN is propagated and `-0.0 < +0.0`.
76#[inline]
77pub(crate) fn min(value1: f32, value2: f32) -> f32 {
78    min_f32(value1, value2)
79}
80
81/// The remainder of `x / y` as specified by IEEE 754 (the quotient is rounded
82/// to the nearest integer, ties to even).
83pub(crate) fn ieee_remainder(x: f32, y: f32) -> f32 {
84    if x.is_nan() {
85        return x;
86    }
87    if y.is_nan() {
88        return y;
89    }
90
91    let regular_mod = x % y;
92    if regular_mod.is_nan() {
93        return f32::NAN;
94    }
95    if regular_mod == 0.0 && x.is_sign_negative() {
96        return -0.0;
97    }
98
99    // `x` is neither NaN nor zero here (a zero `x` gives a zero remainder).
100    let sign = if x > 0.0 { 1.0 } else { -1.0 };
101    let alternative_result = regular_mod - (y.abs() * sign);
102    if alternative_result.abs() == regular_mod.abs() {
103        let division_result = x / y;
104        let rounded_result = division_result.round_ties_even();
105        if rounded_result.abs() > division_result.abs() {
106            return alternative_result;
107        }
108        return regular_mod;
109    }
110    if alternative_result.abs() < regular_mod.abs() {
111        return alternative_result;
112    }
113    regular_mod
114}