1use num_bigint::BigInt;
2use num_rational::BigRational;
3use num_traits::{One, Signed, Zero};
4use std::fmt;
5use std::str::FromStr;
6
7#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
8pub struct ExactNum(BigRational);
9
10#[derive(Debug, Clone)]
11pub enum ParseNumError {
12 InvalidFormat,
13 InvalidDigits,
14 ZeroDenominator,
15}
16
17impl fmt::Display for ParseNumError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 Self::InvalidFormat => write!(f, "invalid numeric format"),
21 Self::InvalidDigits => write!(f, "invalid numeric digits"),
22 Self::ZeroDenominator => write!(f, "zero denominator"),
23 }
24 }
25}
26
27impl std::error::Error for ParseNumError {}
28
29impl ExactNum {
30 pub fn parse_literal(src: &str) -> Result<Self, ParseNumError> {
31 let src = src.trim();
32 if src.is_empty() {
33 return Err(ParseNumError::InvalidFormat);
34 }
35
36 let (sign, unsigned) = if let Some(rest) = src.strip_prefix('-') {
37 (-1i8, rest)
38 } else if let Some(rest) = src.strip_prefix('+') {
39 (1i8, rest)
40 } else {
41 (1i8, src)
42 };
43
44 let (mantissa, exponent) = if let Some((mantissa, exponent)) = unsigned.split_once(['e', 'E']) {
45 let parsed_exp = exponent.parse::<i64>().map_err(|_| ParseNumError::InvalidFormat)?;
46 (mantissa, parsed_exp)
47 } else {
48 (unsigned, 0i64)
49 };
50
51 let (int_part, frac_part) = if let Some((int_part, frac_part)) = mantissa.split_once('.') {
52 (int_part, frac_part)
53 } else {
54 (mantissa, "")
55 };
56
57 if int_part.is_empty() && frac_part.is_empty() {
58 return Err(ParseNumError::InvalidFormat);
59 }
60
61 if !int_part.chars().all(|c| c.is_ascii_digit()) || !frac_part.chars().all(|c| c.is_ascii_digit()) {
62 return Err(ParseNumError::InvalidDigits);
63 }
64
65 let digits = format!("{int_part}{frac_part}");
66 let mut numerator = if digits.is_empty() {
67 BigInt::zero()
68 } else {
69 BigInt::from_str(&digits).map_err(|_| ParseNumError::InvalidDigits)?
70 };
71
72 let scale = frac_part.len() as i64 - exponent;
73 let denominator = if scale <= 0 {
74 numerator *= pow10((-scale) as u32);
75 BigInt::one()
76 } else {
77 pow10(scale as u32)
78 };
79
80 if sign < 0 {
81 numerator = -numerator;
82 }
83
84 Ok(Self(BigRational::new(numerator, denominator)))
85 }
86
87 pub fn parse_canonical(src: &str) -> Result<Self, ParseNumError> {
88 if let Some((numerator, denominator)) = src.split_once('/') {
89 let numerator = BigInt::from_str(numerator).map_err(|_| ParseNumError::InvalidDigits)?;
90 let denominator = BigInt::from_str(denominator).map_err(|_| ParseNumError::InvalidDigits)?;
91 if denominator.is_zero() {
92 return Err(ParseNumError::ZeroDenominator);
93 }
94 Ok(Self(BigRational::new(numerator, denominator)))
95 } else {
96 Self::parse_literal(src)
97 }
98 }
99
100 pub fn from_usize(value: usize) -> Self {
101 Self(BigRational::from_integer(BigInt::from(value)))
102 }
103
104 pub fn is_zero(&self) -> bool {
105 self.0.is_zero()
106 }
107
108 pub fn add(&self, other: &Self) -> Self {
109 Self(self.0.clone() + other.0.clone())
110 }
111
112 pub fn sub(&self, other: &Self) -> Self {
113 Self(self.0.clone() - other.0.clone())
114 }
115
116 pub fn mul(&self, other: &Self) -> Self {
117 Self(self.0.clone() * other.0.clone())
118 }
119
120 pub fn div(&self, other: &Self) -> Self {
121 Self(self.0.clone() / other.0.clone())
122 }
123
124 pub fn neg(&self) -> Self {
125 Self(-self.0.clone())
126 }
127
128 pub fn to_json_value(&self) -> serde_json::Value {
129 if let Some(decimal) = self.finite_decimal_string() {
130 if let Ok(number) = serde_json::Number::from_str(&decimal) {
131 return serde_json::Value::Number(number);
132 }
133 }
134
135 serde_json::json!({
136 "$type": "num",
137 "$value": self.canonical_string(),
138 })
139 }
140
141 pub fn canonical_string(&self) -> String {
142 if let Some(decimal) = self.finite_decimal_string() {
143 decimal
144 } else {
145 format!("{}/{}", self.0.numer(), self.0.denom())
146 }
147 }
148
149 fn finite_decimal_string(&self) -> Option<String> {
150 let numerator = self.0.numer().clone();
151 let mut denominator = self.0.denom().clone();
152
153 if denominator == BigInt::one() {
154 return Some(numerator.to_string());
155 }
156
157 let two = BigInt::from(2u8);
158 let five = BigInt::from(5u8);
159 let mut twos = 0u32;
160 let mut fives = 0u32;
161
162 while (&denominator % &two).is_zero() {
163 denominator /= &two;
164 twos += 1;
165 }
166
167 while (&denominator % &five).is_zero() {
168 denominator /= &five;
169 fives += 1;
170 }
171
172 if denominator != BigInt::one() {
173 return None;
174 }
175
176 let scale = twos.max(fives);
177 let mut adjusted = numerator.abs();
178 if scale > twos {
179 adjusted *= five.pow(scale - twos);
180 }
181 if scale > fives {
182 adjusted *= two.pow(scale - fives);
183 }
184
185 let digits = adjusted.to_string();
186 let negative = numerator.is_negative();
187 let sign = if negative { "-" } else { "" };
188
189 if scale == 0 {
190 return Some(format!("{sign}{digits}"));
191 }
192
193 let scale = scale as usize;
194 if digits.len() <= scale {
195 let padding = "0".repeat(scale - digits.len());
196 Some(format!("{sign}0.{padding}{digits}"))
197 } else {
198 let split = digits.len() - scale;
199 Some(format!("{sign}{}.{}", &digits[..split], &digits[split..]))
200 }
201 }
202}
203
204impl fmt::Display for ExactNum {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "{}", self.canonical_string())
207 }
208}
209
210fn pow10(exp: u32) -> BigInt {
211 BigInt::from(10u8).pow(exp)
212}