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