harper_core/
number.rs

1use std::fmt::Display;
2
3use is_macro::Is;
4use ordered_float::OrderedFloat;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd)]
8pub struct Number {
9    pub value: OrderedFloat<f64>,
10    pub suffix: Option<NumberSuffix>,
11    pub radix: u32,
12    pub precision: usize,
13}
14
15impl Display for Number {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        if self.radix == 16 {
18            write!(f, "0x{:X}", self.value.0 as u64)?;
19        } else {
20            write!(f, "{:.*}", self.precision, self.value.0)?;
21        }
22
23        if let Some(suffix) = self.suffix {
24            for c in suffix.to_chars() {
25                write!(f, "{}", c)?;
26            }
27        }
28
29        Ok(())
30    }
31}
32
33#[derive(
34    Debug, Serialize, Deserialize, Default, PartialEq, PartialOrd, Clone, Copy, Is, Hash, Eq,
35)]
36pub enum NumberSuffix {
37    #[default]
38    Th,
39    St,
40    Nd,
41    Rd,
42}
43
44impl NumberSuffix {
45    pub fn correct_suffix_for(number: impl Into<f64>) -> Option<Self> {
46        let number = number.into();
47
48        if number < 0.0 || number - number.floor() > f64::EPSILON || number > u64::MAX as f64 {
49            return None;
50        }
51
52        let integer = number as u64;
53
54        if let 11..=13 = integer % 100 {
55            return Some(Self::Th);
56        };
57
58        match integer % 10 {
59            0 => Some(Self::Th),
60            1 => Some(Self::St),
61            2 => Some(Self::Nd),
62            3 => Some(Self::Rd),
63            4 => Some(Self::Th),
64            5 => Some(Self::Th),
65            6 => Some(Self::Th),
66            7 => Some(Self::Th),
67            8 => Some(Self::Th),
68            9 => Some(Self::Th),
69            _ => None,
70        }
71    }
72
73    pub fn to_chars(self) -> Vec<char> {
74        match self {
75            NumberSuffix::Th => vec!['t', 'h'],
76            NumberSuffix::St => vec!['s', 't'],
77            NumberSuffix::Nd => vec!['n', 'd'],
78            NumberSuffix::Rd => vec!['r', 'd'],
79        }
80    }
81
82    /// Check the first several characters in a buffer to see if it matches a
83    /// number suffix.
84    pub fn from_chars(chars: &[char]) -> Option<Self> {
85        if chars.len() < 2 {
86            return None;
87        }
88
89        match (chars[0], chars[1]) {
90            ('t', 'h') => Some(NumberSuffix::Th),
91            ('T', 'h') => Some(NumberSuffix::Th),
92            ('t', 'H') => Some(NumberSuffix::Th),
93            ('T', 'H') => Some(NumberSuffix::Th),
94            ('s', 't') => Some(NumberSuffix::St),
95            ('S', 't') => Some(NumberSuffix::St),
96            ('s', 'T') => Some(NumberSuffix::St),
97            ('S', 'T') => Some(NumberSuffix::St),
98            ('n', 'd') => Some(NumberSuffix::Nd),
99            ('N', 'd') => Some(NumberSuffix::Nd),
100            ('n', 'D') => Some(NumberSuffix::Nd),
101            ('N', 'D') => Some(NumberSuffix::Nd),
102            ('r', 'd') => Some(NumberSuffix::Rd),
103            ('R', 'd') => Some(NumberSuffix::Rd),
104            ('r', 'D') => Some(NumberSuffix::Rd),
105            ('R', 'D') => Some(NumberSuffix::Rd),
106            _ => None,
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use ordered_float::OrderedFloat;
114
115    use crate::NumberSuffix;
116
117    use super::Number;
118
119    #[test]
120    fn hex_fifteen() {
121        assert_eq!(
122            Number {
123                value: OrderedFloat(15.0),
124                suffix: None,
125                radix: 16,
126                precision: 0
127            }
128            .to_string(),
129            "0xF"
130        )
131    }
132
133    #[test]
134    fn decimal_fifteen() {
135        assert_eq!(
136            Number {
137                value: OrderedFloat(15.0),
138                suffix: None,
139                radix: 10,
140                precision: 0
141            }
142            .to_string(),
143            "15"
144        )
145    }
146
147    #[test]
148    fn decimal_fifteen_suffix() {
149        assert_eq!(
150            Number {
151                value: OrderedFloat(15.0),
152                suffix: Some(NumberSuffix::Th),
153                radix: 10,
154                precision: 0
155            }
156            .to_string(),
157            "15th"
158        )
159    }
160
161    #[test]
162    fn decimal_fifteen_and_a_half() {
163        assert_eq!(
164            Number {
165                value: OrderedFloat(15.5),
166                suffix: None,
167                radix: 10,
168                precision: 2
169            }
170            .to_string(),
171            "15.50"
172        )
173    }
174}