evobench_tools/
digit_num.rs1use std::{fmt::Display, io::Write};
7
8use rand::Rng;
9
10#[derive(Debug, Clone, Copy)]
13pub struct Digit(u8);
14
15impl Display for Digit {
16 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17 std::fmt::Write::write_char(f, (self.0 + b'0') as char)
18 }
19}
20
21#[derive(thiserror::Error, Debug)]
22#[error("out of range")]
23pub struct OutOfRange;
24
25impl TryFrom<u8> for Digit {
26 type Error = OutOfRange;
27
28 fn try_from(value: u8) -> Result<Self, Self::Error> {
29 if value < 10 {
30 Ok(Self(value))
31 } else {
32 Err(OutOfRange)
33 }
34 }
35}
36
37impl Digit {
38 pub fn random() -> Self {
39 let mut rng = rand::thread_rng();
40 let d = rng.gen_range(0..10);
41 d.try_into().expect("no bug")
42 }
43}
44
45#[derive(Debug, Clone)]
49pub struct DigitNum<const PRECISION: usize>(Vec<Digit>);
50
51pub struct DigitNumFormat {
52 pub underscores: bool,
53 pub omit_trailing_dot: bool,
55}
56
57impl<const PRECISION: usize> DigitNum<PRECISION> {
58 pub fn new() -> Self {
59 Self(vec![])
60 }
61
62 pub fn into_changed_dot_position<const NEW_PRECISION: usize>(self) -> DigitNum<NEW_PRECISION> {
63 DigitNum(self.0)
64 }
65
66 pub fn push_lowest_digit(&mut self, d: Digit) {
67 if d.0 == 0 && self.0.is_empty() {
68 return;
69 }
70 self.0.push(d)
71 }
72
73 pub fn write<W: Write>(&self, params: DigitNumFormat, mut out: W) -> std::io::Result<()> {
74 let DigitNumFormat {
75 underscores,
76 omit_trailing_dot,
77 } = params;
78 let len = self.0.len();
79 let after_dot = PRECISION;
80 let mut digits = self.0.iter();
81 let len_missing_after_dot = if len > after_dot {
82 let len_before_dot = len - after_dot;
83 let mut position = len_before_dot % 3;
84 if position == 0 {
85 position = 3;
86 }
87 for _ in 0..len_before_dot {
88 if position == 0 {
89 if underscores {
90 write!(out, "_")?;
91 }
92 position = 3;
93 }
94 position -= 1;
95 let d = digits.next().expect("digits till lowest one are present");
96 write!(out, "{d}")?;
97 }
98 0
99 } else {
100 write!(out, "0")?;
101 after_dot - len
102 };
103
104 if after_dot == 0 && omit_trailing_dot {
105 return Ok(());
106 }
107
108 write!(out, ".")?;
109
110 let mut position = 3;
111
112 for _ in 0..len_missing_after_dot {
113 if position == 0 {
114 if underscores {
115 write!(out, "_")?;
116 }
117 position = 3;
118 }
119 position -= 1;
120 write!(out, "0")?;
121 }
122 for d in digits {
123 if position == 0 {
124 if underscores {
125 write!(out, "_")?;
126 }
127 position = 3;
128 }
129 position -= 1;
130 write!(out, "{d}")?;
131 }
132 Ok(())
133 }
134
135 pub fn to_string_with_params(&self, params: DigitNumFormat) -> String {
136 let mut s = Vec::new();
137 self.write(params, &mut s)
138 .expect("no errors writing to String");
139 String::from_utf8(s).expect("no non-utf8")
140 }
141}
142
143impl<const PRECISION: usize> TryFrom<&DigitNum<PRECISION>> for u64 {
145 type Error = OutOfRange;
146
147 fn try_from(value: &DigitNum<PRECISION>) -> Result<Self, Self::Error> {
148 let mut num: u64 = 0;
149 for Digit(d) in value.0.iter() {
150 num = num.checked_mul(10).ok_or(OutOfRange)?;
151 num = num.checked_add(u64::from(*d)).ok_or(OutOfRange)?;
152 }
153 Ok(num)
154 }
155}
156
157impl<const PRECISION: usize> Default for DigitNum<PRECISION> {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn t_digit_num() {
170 assert!(Digit::try_from(10).is_err());
171 let mut num: DigitNum<6> = DigitNum::new();
172 num.push_lowest_digit(0.try_into().unwrap());
173 num.push_lowest_digit(3.try_into().unwrap());
174 num.push_lowest_digit(9.try_into().unwrap());
175 assert_eq!(
176 num.to_string_with_params(DigitNumFormat {
177 underscores: false,
178 omit_trailing_dot: false
179 }),
180 "0.000039"
181 );
182 assert_eq!(
183 num.to_string_with_params(DigitNumFormat {
184 underscores: true,
185 omit_trailing_dot: false
186 }),
187 "0.000_039"
188 );
189 for d in [7, 1, 3, 4] {
190 num.push_lowest_digit(d.try_into().unwrap());
191 }
192 assert_eq!(
193 num.to_string_with_params(DigitNumFormat {
194 underscores: false,
195 omit_trailing_dot: false
196 }),
197 "0.397134"
198 );
199 for d in [5, 3, 5, 2, 9] {
200 num.push_lowest_digit(d.try_into().unwrap());
201 }
202 assert_eq!(
203 num.to_string_with_params(DigitNumFormat {
204 underscores: false,
205 omit_trailing_dot: false
206 }),
207 "39713.453529"
208 );
209 assert_eq!(
210 num.to_string_with_params(DigitNumFormat {
211 underscores: true,
212 omit_trailing_dot: false
213 }),
214 "39_713.453_529"
215 );
216 let num_u64 = u64::try_from(&num).unwrap();
217 assert_eq!(num_u64, 39713453529);
218 }
219}