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 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}