1use std::error::Error;
2use std::fmt::Display;
3
4pub const FRAC_PLACES: u32 = 19;
10
11pub const FRAC_SCALE_U128: u128 = 10_u128.pow(FRAC_PLACES);
13pub const FRAC_SCALE_I128: i128 = FRAC_SCALE_U128 as i128;
14
15#[derive(Debug, Eq, PartialEq)]
20pub enum ParseDec19x19Error {
21 ParseIntError(std::num::ParseIntError),
22 OutOfBounds,
23 TooPrecise,
24 InvalidChar { char: char, pos: usize },
25}
26
27impl From<std::num::ParseIntError> for ParseDec19x19Error {
28 fn from(err: std::num::ParseIntError) -> Self {
29 Self::ParseIntError(err)
30 }
31}
32
33impl Error for ParseDec19x19Error {}
34impl Display for ParseDec19x19Error {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Self::ParseIntError(err) => Display::fmt(err, f),
38 Self::OutOfBounds => write!(f, "Value out of bounds"),
39 Self::TooPrecise => write!(f, "Value too precise"),
40 Self::InvalidChar { char, pos } =>
41 write!(f, "Invalid character `{char}` at position {pos}"),
42 }
43 }
44}
45
46pub fn shift_decimal(
82 int_part: &str,
83 frac_part: &str,
84 exp: i128,
85) -> (String, String) {
86 let mut int_part = int_part.to_string();
87 let mut frac_part = frac_part.to_string();
88
89 #[expect(clippy::comparison_chain)]
90 if exp > 0 {
91 let exp = exp as usize;
92 let move_count = exp.min(frac_part.len());
93 int_part.push_str(&frac_part[..move_count]);
94 frac_part = frac_part[move_count..].to_string();
95 if exp > move_count {
96 int_part.push_str(&"0".repeat(exp - move_count));
97 }
98 } else if exp < 0 {
99 let exp = (-exp) as usize;
100 let move_count = exp.min(int_part.len());
101 let moved = &int_part[int_part.len() - move_count..];
102 frac_part = format!("{moved}{frac_part}");
103 int_part.truncate(int_part.len() - move_count);
104 if exp > move_count {
105 frac_part = format!("{}{frac_part}", "0".repeat(exp - move_count));
106 }
107 }
108
109 let mut int_part = int_part.trim_start_matches('0').to_string();
110 let mut frac_part = frac_part.trim_end_matches('0').to_string();
111
112 if int_part.is_empty() {
113 int_part = "0".to_string();
114 }
115 if frac_part.is_empty() {
116 frac_part = "0".to_string();
117 }
118
119 (int_part, frac_part)
120}
121
122pub fn parse_dec19x19_internal(s: &str) -> Result<i128, ParseDec19x19Error> {
123 let clean = s.replace(['_', ' '], "");
130 let trimmed = clean.trim();
131 let is_negative = trimmed.starts_with('-');
132 let e_parts: Vec<&str> = trimmed.split('e').collect();
133 if e_parts.len() > 2 {
134 let pos = e_parts[0].len() + e_parts[1].len() + 1;
135 return Err(ParseDec19x19Error::InvalidChar { char: 'e', pos })
136 }
137 let exp: i128 = e_parts.get(1).map_or(Ok(0), |t| t.parse())?;
138 let parts: Vec<&str> = e_parts[0].split('.').collect();
139 let parts_count = parts.len();
140 if parts_count > 2 {
141 let pos = parts[0].len() + parts[1].len() + 1;
142 return Err(ParseDec19x19Error::InvalidChar { char: '.', pos })
143 }
144 let int_part_str = parts[0].to_string();
145 let frac_part_str = parts.get(1).map(|t| t.to_string()).unwrap_or_default();
146 let (int_part_str2, frac_part_str2) = shift_decimal(&int_part_str, &frac_part_str, exp);
147 let int_part: i128 = int_part_str2.parse()?;
148 let frac_part: i128 = {
149 if frac_part_str2.len() > FRAC_PLACES as usize {
150 return Err(ParseDec19x19Error::TooPrecise);
151 }
152 let mut buffer = [b'0'; FRAC_PLACES as usize];
153 let frac_bytes = frac_part_str2.as_bytes();
154 buffer[..frac_bytes.len()].copy_from_slice(frac_bytes);
155 #[allow(clippy::unwrap_used)]
156 let padded = std::str::from_utf8(&buffer).unwrap();
157 padded.parse()?
158 };
159 let scaled = int_part.checked_mul(FRAC_SCALE_I128).ok_or(ParseDec19x19Error::OutOfBounds)?;
160 let repr = if is_negative {
161 scaled.checked_sub(frac_part)
162 } else {
163 scaled.checked_add(frac_part)
164 }.ok_or(ParseDec19x19Error::OutOfBounds)?;
165 Ok(repr)
166}
167
168#[derive(Debug, Clone, Copy)]
173pub struct Formatter {
174 pub separator: Option<char>,
175 pub precision: Option<usize>,
176 pub width: Option<usize>,
177 pub align: Option<std::fmt::Alignment>,
178 pub fill: char,
179 pub sign_plus: bool
180}
181
182pub trait Format {
183 fn format(&self, f: &mut Formatter) -> String;
184}
185
186pub trait Rand {
191 fn rand(seed: u64, int: impl IntoRandRange, frac: impl IntoRandRange) -> Self;
192}
193
194pub type RandRange = std::ops::RangeInclusive<u32>;
195
196pub trait IntoRandRange {
197 fn into_rand_range(self) -> RandRange;
198}
199
200impl IntoRandRange for RandRange {
201 fn into_rand_range(self) -> RandRange {
202 self
203 }
204}
205
206impl IntoRandRange for u32 {
207 fn into_rand_range(self) -> RandRange {
208 self ..= self
209 }
210}