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<OrdinalSuffix>,
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 OrdinalSuffix {
37 #[default]
38 Th,
39 St,
40 Nd,
41 Rd,
42}
43
44impl OrdinalSuffix {
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 OrdinalSuffix::Th => vec!['t', 'h'],
76 OrdinalSuffix::St => vec!['s', 't'],
77 OrdinalSuffix::Nd => vec!['n', 'd'],
78 OrdinalSuffix::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(OrdinalSuffix::Th),
91 ('T', 'h') => Some(OrdinalSuffix::Th),
92 ('t', 'H') => Some(OrdinalSuffix::Th),
93 ('T', 'H') => Some(OrdinalSuffix::Th),
94 ('s', 't') => Some(OrdinalSuffix::St),
95 ('S', 't') => Some(OrdinalSuffix::St),
96 ('s', 'T') => Some(OrdinalSuffix::St),
97 ('S', 'T') => Some(OrdinalSuffix::St),
98 ('n', 'd') => Some(OrdinalSuffix::Nd),
99 ('N', 'd') => Some(OrdinalSuffix::Nd),
100 ('n', 'D') => Some(OrdinalSuffix::Nd),
101 ('N', 'D') => Some(OrdinalSuffix::Nd),
102 ('r', 'd') => Some(OrdinalSuffix::Rd),
103 ('R', 'd') => Some(OrdinalSuffix::Rd),
104 ('r', 'D') => Some(OrdinalSuffix::Rd),
105 ('R', 'D') => Some(OrdinalSuffix::Rd),
106 _ => None,
107 }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use itertools::Itertools;
114 use ordered_float::OrderedFloat;
115
116 use crate::OrdinalSuffix;
117
118 use super::Number;
119
120 #[test]
121 fn hex_fifteen() {
122 assert_eq!(
123 Number {
124 value: OrderedFloat(15.0),
125 suffix: None,
126 radix: 16,
127 precision: 0
128 }
129 .to_string(),
130 "0xF"
131 )
132 }
133
134 #[test]
135 fn decimal_fifteen() {
136 assert_eq!(
137 Number {
138 value: OrderedFloat(15.0),
139 suffix: None,
140 radix: 10,
141 precision: 0
142 }
143 .to_string(),
144 "15"
145 )
146 }
147
148 #[test]
149 fn decimal_fifteen_suffix() {
150 assert_eq!(
151 Number {
152 value: OrderedFloat(15.0),
153 suffix: Some(OrdinalSuffix::Th),
154 radix: 10,
155 precision: 0
156 }
157 .to_string(),
158 "15th"
159 )
160 }
161
162 #[test]
163 fn decimal_fifteen_and_a_half() {
164 assert_eq!(
165 Number {
166 value: OrderedFloat(15.5),
167 suffix: None,
168 radix: 10,
169 precision: 2
170 }
171 .to_string(),
172 "15.50"
173 )
174 }
175
176 #[test]
177 fn issue_1051() {
178 let word = "story".chars().collect_vec();
179 assert_eq!(None, OrdinalSuffix::from_chars(&word));
180 }
181}