xsd_types/value/decimal/
mod.rs

1use std::fmt;
2use std::hash::Hash;
3use std::ops::Deref;
4use std::str::FromStr;
5use std::{borrow::Borrow, collections::HashSet};
6
7use lazy_static::lazy_static;
8use num_bigint::BigInt;
9use num_rational::BigRational;
10use num_traits::{Signed, ToPrimitive, Zero};
11use once_cell::unsync::OnceCell;
12
13use crate::lexical::LexicalFormOf;
14use crate::{
15	lexical, Datatype, DecimalDatatype, Double, Float, IntDatatype, LongDatatype,
16	NonNegativeIntegerDatatype, NonPositiveIntegerDatatype, ParseXsd, ShortDatatype,
17	UnsignedIntDatatype, UnsignedLongDatatype, UnsignedShortDatatype, XsdValue,
18};
19
20pub use num_bigint::Sign;
21
22mod integer;
23
24pub use integer::*;
25
26lazy_static! {
27	static ref I64_MIN: BigInt = i64::MIN.into();
28	static ref I64_MIN_RATIO: BigRational = I64_MIN.clone().into();
29	static ref I32_MIN: BigInt = i32::MIN.into();
30	static ref I32_MIN_RATIO: BigRational = I32_MIN.clone().into();
31	static ref I16_MIN: BigInt = i16::MIN.into();
32	static ref I16_MIN_RATIO: BigRational = I16_MIN.clone().into();
33	static ref I8_MIN: BigInt = i8::MIN.into();
34	static ref I8_MIN_RATIO: BigRational = I8_MIN.clone().into();
35	static ref U64_MAX: BigInt = u64::MAX.into();
36	static ref U64_MAX_RATIO: BigRational = U64_MAX.clone().into();
37	static ref U32_MAX: BigInt = u32::MAX.into();
38	static ref U32_MAX_RATIO: BigRational = U32_MAX.clone().into();
39	static ref U16_MAX: BigInt = u16::MAX.into();
40	static ref U16_MAX_RATIO: BigRational = U16_MAX.clone().into();
41	static ref U8_MAX: BigInt = u8::MAX.into();
42	static ref U8_MAX_RATIO: BigRational = U8_MAX.clone().into();
43	static ref TEN: BigInt = 10u32.into();
44}
45
46/// Decimal number.
47///
48/// Internally a decimal number is represented as a `BigRational` with a finite
49/// decimal representation.
50#[derive(Clone)]
51pub struct Decimal {
52	data: BigRational,
53	lexical: OnceCell<lexical::DecimalBuf>,
54}
55
56impl PartialEq for Decimal {
57	fn eq(&self, other: &Self) -> bool {
58		self.data.eq(&other.data)
59	}
60}
61
62impl Eq for Decimal {}
63
64impl PartialOrd for Decimal {
65	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66		Some(self.data.cmp(other))
67	}
68}
69
70impl Ord for Decimal {
71	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
72		self.data.cmp(&other.data)
73	}
74}
75
76impl Hash for Decimal {
77	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
78		self.data.hash(state)
79	}
80}
81
82impl fmt::Debug for Decimal {
83	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84		write!(f, "Decimal({:?})", self.data)
85	}
86}
87
88/// Checks that a rational has a finite decimal representation.
89///
90/// This structure will cache some data to avoid reallocation.
91/// This way running the check for multiple rational numbers can be slightly
92/// more efficient.
93#[derive(Default)]
94pub struct DecimalCheck {
95	set: HashSet<BigInt>,
96}
97
98impl DecimalCheck {
99	pub fn is_decimal(&mut self, r: &BigRational) -> bool {
100		self.set.clear();
101
102		let mut rem = if *r < BigRational::zero() {
103			-r.numer()
104		} else {
105			r.numer().clone()
106		};
107
108		rem %= r.denom();
109		while !rem.is_zero() && !self.set.contains(&rem) {
110			self.set.insert(rem.clone());
111			rem = (rem * TEN.clone()) % r.denom();
112		}
113
114		rem.is_zero()
115	}
116}
117
118/// Checks that the given rational has a finite decimal representation.
119#[inline(always)]
120pub fn is_decimal(r: &BigRational) -> bool {
121	let mut c = DecimalCheck::default();
122	c.is_decimal(r)
123}
124
125/// Returns the decimal lexical representation of the given rational number, if
126/// any.
127pub fn decimal_lexical_representation(r: &BigRational) -> Option<lexical::DecimalBuf> {
128	use std::fmt::Write;
129
130	let mut fraction = String::new();
131	let mut map = std::collections::HashMap::new();
132
133	let mut rem = if r.is_negative() {
134		-r.numer()
135	} else {
136		r.numer().clone()
137	};
138
139	rem %= r.denom();
140	while !rem.is_zero() && !map.contains_key(&rem) {
141		map.insert(rem.clone(), fraction.len());
142		rem *= TEN.clone();
143		fraction.push_str(&(rem.clone() / r.denom()).to_string());
144		rem %= r.denom();
145	}
146
147	let mut output = if r.is_negative() {
148		"-".to_owned()
149	} else {
150		String::new()
151	};
152
153	output.push_str(&(r.numer() / r.denom()).to_string());
154
155	if rem.is_zero() {
156		if !fraction.is_empty() {
157			write!(output, ".{}", &fraction).unwrap();
158		}
159
160		Some(unsafe { lexical::DecimalBuf::new_unchecked(output) })
161	} else {
162		None
163	}
164}
165
166impl Decimal {
167	/// Creates a new decimal number from a rational number.
168	///
169	/// # Safety
170	///
171	/// The input rational number must have a finite decimal representation.
172	pub unsafe fn new_unchecked(r: BigRational) -> Self {
173		Self {
174			data: r,
175			lexical: OnceCell::new(),
176		}
177	}
178
179	#[inline(always)]
180	pub fn as_big_rational(&self) -> &BigRational {
181		&self.data
182	}
183
184	#[inline(always)]
185	pub fn into_big_rational(self) -> BigRational {
186		self.data
187	}
188
189	#[inline(always)]
190	pub fn zero() -> Self {
191		Self {
192			data: BigRational::zero(),
193			lexical: OnceCell::new(),
194		}
195	}
196
197	#[inline(always)]
198	pub fn is_zero(&self) -> bool {
199		self.data.is_zero()
200	}
201
202	#[inline(always)]
203	pub fn is_positive(&self) -> bool {
204		self.data.is_positive()
205	}
206
207	#[inline(always)]
208	pub fn is_negative(&self) -> bool {
209		self.data.is_negative()
210	}
211
212	pub fn as_integer(&self) -> Option<&Integer> {
213		if self.data.is_integer() {
214			Some(Integer::from_bigint_ref(self.data.numer()))
215		} else {
216			None
217		}
218	}
219
220	pub fn into_integer(self) -> Option<Integer> {
221		if self.data.is_integer() {
222			Some(Integer::from(self.data.numer().clone())) // TODO avoid cloning.
223		} else {
224			None
225		}
226	}
227
228	pub fn decimal_type(&self) -> DecimalDatatype {
229		if self.data.is_integer() {
230			if self.data >= BigRational::zero() {
231				if self.data > BigRational::zero() {
232					if self.data <= *U8_MAX_RATIO {
233						UnsignedShortDatatype::UnsignedByte.into()
234					} else if self.data <= *U16_MAX_RATIO {
235						UnsignedShortDatatype::UnsignedShort.into()
236					} else if self.data <= *U32_MAX_RATIO {
237						UnsignedIntDatatype::UnsignedInt.into()
238					} else if self.data <= *U64_MAX_RATIO {
239						UnsignedLongDatatype::UnsignedLong.into()
240					} else {
241						NonNegativeIntegerDatatype::PositiveInteger.into()
242					}
243				} else {
244					UnsignedShortDatatype::UnsignedByte.into()
245				}
246			} else if self.data >= *I8_MIN_RATIO {
247				ShortDatatype::Byte.into()
248			} else if self.data >= *I16_MIN_RATIO {
249				ShortDatatype::Short.into()
250			} else if self.data >= *I32_MIN_RATIO {
251				IntDatatype::Int.into()
252			} else if self.data >= *I64_MIN_RATIO {
253				LongDatatype::Long.into()
254			} else {
255				NonPositiveIntegerDatatype::NegativeInteger.into()
256			}
257		} else {
258			DecimalDatatype::Decimal
259		}
260	}
261
262	#[inline(always)]
263	pub fn lexical_representation(&self) -> &lexical::DecimalBuf {
264		self.lexical
265			.get_or_init(|| decimal_lexical_representation(&self.data).unwrap())
266	}
267
268	pub fn as_f64(&self) -> Option<f64> {
269		self.data.to_f64()
270	}
271
272	pub fn as_f32(&self) -> Option<f32> {
273		self.data.to_f32()
274	}
275
276	pub fn as_float(&self) -> Option<Float> {
277		self.as_f32().map(Float::from)
278	}
279
280	pub fn as_double(&self) -> Option<Double> {
281		self.as_f64().map(Double::from)
282	}
283}
284
285impl fmt::Display for Decimal {
286	#[inline(always)]
287	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288		self.lexical_representation().fmt(f)
289	}
290}
291
292impl<'a> From<&'a lexical::Decimal> for Decimal {
293	#[inline(always)]
294	fn from(value: &'a lexical::Decimal) -> Self {
295		value.to_owned().into()
296	}
297}
298
299impl From<lexical::DecimalBuf> for Decimal {
300	#[inline(always)]
301	fn from(value: lexical::DecimalBuf) -> Self {
302		let integer_part: BigInt = value.integer_part().as_str().parse().unwrap();
303		let data = match value.fractional_part() {
304			Some(fract) => {
305				let numer = fract.as_str().parse().unwrap();
306				let mut denom = BigInt::new(Sign::Plus, vec![1u32]);
307				for _ in 0..fract.as_str().len() {
308					denom *= 10
309				}
310
311				BigRational::from(integer_part) + BigRational::new(numer, denom)
312			}
313			None => integer_part.into(),
314		};
315
316		Self {
317			data,
318			lexical: value.into(),
319		}
320	}
321}
322
323impl FromStr for Decimal {
324	type Err = lexical::InvalidDecimal;
325
326	#[inline(always)]
327	fn from_str(s: &str) -> Result<Self, Self::Err> {
328		let l = lexical::DecimalBuf::new(s.to_owned()).map_err(|(e, _)| e)?;
329		Ok(l.into())
330	}
331}
332
333macro_rules! from_int {
334	($($ty:ident),*) => {
335		$(
336			impl From<$ty> for Decimal {
337				fn from(value: $ty) -> Self {
338					Self {
339						data: BigInt::from(value).into(),
340						lexical: OnceCell::new()
341					}
342				}
343			}
344		)*
345	};
346}
347
348from_int!(u8, u16, u32, u64, i8, i16, i32, i64, usize, isize);
349
350macro_rules! try_into_int {
351	($($ty:ident),*) => {
352		$(
353			impl TryFrom<Decimal> for $ty {
354				type Error = FromDecimalError;
355
356				fn try_from(value: Decimal) -> Result<Self, FromDecimalError> {
357					match value.as_integer() {
358						Some(i) => {
359							i.try_into().map_err(|_| FromDecimalError)
360						}
361						None => Err(FromDecimalError)
362					}
363				}
364			}
365
366			impl<'a> TryFrom<&'a Decimal> for $ty {
367				type Error = FromDecimalError;
368
369				fn try_from(value: &'a Decimal) -> Result<Self, FromDecimalError> {
370					match value.as_integer() {
371						Some(i) => {
372							i.try_into().map_err(|_| FromDecimalError)
373						}
374						None => Err(FromDecimalError)
375					}
376				}
377			}
378		)*
379	};
380}
381
382#[derive(Debug, thiserror::Error)]
383#[error("decimal number conversion failed")]
384pub struct FromDecimalError;
385
386try_into_int!(u8, u16, u32, u64, i8, i16, i32, i64, usize, isize);
387
388impl TryFrom<Decimal> for f32 {
389	type Error = FromDecimalError;
390
391	fn try_from(value: Decimal) -> Result<Self, Self::Error> {
392		value.as_f32().ok_or(FromDecimalError)
393	}
394}
395
396impl TryFrom<Decimal> for Float {
397	type Error = FromDecimalError;
398
399	fn try_from(value: Decimal) -> Result<Self, Self::Error> {
400		value.as_float().ok_or(FromDecimalError)
401	}
402}
403
404impl TryFrom<Decimal> for f64 {
405	type Error = FromDecimalError;
406
407	fn try_from(value: Decimal) -> Result<Self, Self::Error> {
408		value.as_f64().ok_or(FromDecimalError)
409	}
410}
411
412impl TryFrom<Decimal> for Double {
413	type Error = FromDecimalError;
414
415	fn try_from(value: Decimal) -> Result<Self, Self::Error> {
416		value.as_double().ok_or(FromDecimalError)
417	}
418}
419
420impl From<BigInt> for Decimal {
421	#[inline(always)]
422	fn from(value: BigInt) -> Self {
423		Self {
424			data: value.into(),
425			lexical: OnceCell::new(),
426		}
427	}
428}
429
430impl From<Integer> for Decimal {
431	#[inline(always)]
432	fn from(value: Integer) -> Self {
433		let n: BigInt = value.into();
434		n.into()
435	}
436}
437
438impl AsRef<BigRational> for Decimal {
439	#[inline(always)]
440	fn as_ref(&self) -> &BigRational {
441		&self.data
442	}
443}
444
445impl Borrow<BigRational> for Decimal {
446	#[inline(always)]
447	fn borrow(&self) -> &BigRational {
448		&self.data
449	}
450}
451
452impl Deref for Decimal {
453	type Target = BigRational;
454
455	#[inline(always)]
456	fn deref(&self) -> &Self::Target {
457		&self.data
458	}
459}
460
461/// Error raised when trying to create a decimal value from a rational without
462/// finite decimal representation.
463#[derive(Debug, thiserror::Error)]
464#[error("no decimal representation for rational number {0}")]
465pub struct NoDecimalRepresentation(pub BigRational);
466
467impl TryFrom<BigRational> for Decimal {
468	type Error = NoDecimalRepresentation;
469
470	#[inline(always)]
471	fn try_from(value: BigRational) -> Result<Self, Self::Error> {
472		if is_decimal(&value) {
473			Ok(unsafe { Self::new_unchecked(value) })
474		} else {
475			Err(NoDecimalRepresentation(value))
476		}
477	}
478}
479
480impl From<Decimal> for BigRational {
481	#[inline(always)]
482	fn from(value: Decimal) -> Self {
483		value.data
484	}
485}
486
487impl XsdValue for Decimal {
488	#[inline(always)]
489	fn datatype(&self) -> Datatype {
490		self.decimal_type().into()
491	}
492}
493
494impl ParseXsd for Decimal {
495	type LexicalForm = lexical::Decimal;
496}
497
498impl LexicalFormOf<Decimal> for lexical::Decimal {
499	type ValueError = std::convert::Infallible;
500
501	fn try_as_value(&self) -> Result<Decimal, Self::ValueError> {
502		Ok(self.value())
503	}
504}
505
506#[derive(Debug, thiserror::Error)]
507pub enum NonDecimalFloat {
508	#[error("float is NaN")]
509	Nan,
510
511	#[error("float is positive infinity")]
512	PositiveInfinity,
513
514	#[error("float is negative infinity")]
515	NegativeInfinity,
516}
517
518impl TryFrom<Float> for Decimal {
519	type Error = NonDecimalFloat;
520
521	fn try_from(value: Float) -> Result<Self, Self::Error> {
522		if value.is_nan() {
523			Err(NonDecimalFloat::Nan)
524		} else if value.is_infinite() {
525			if value.is_positive() {
526				Err(NonDecimalFloat::PositiveInfinity)
527			} else {
528				Err(NonDecimalFloat::NegativeInfinity)
529			}
530		} else {
531			Ok(BigRational::from_float(value.into_f32())
532				.unwrap()
533				.try_into()
534				.unwrap())
535		}
536	}
537}
538
539impl TryFrom<Double> for Decimal {
540	type Error = NonDecimalFloat;
541
542	fn try_from(value: Double) -> Result<Self, Self::Error> {
543		if value.is_nan() {
544			Err(NonDecimalFloat::Nan)
545		} else if value.is_infinite() {
546			if value.is_sign_positive() {
547				Err(NonDecimalFloat::PositiveInfinity)
548			} else {
549				Err(NonDecimalFloat::NegativeInfinity)
550			}
551		} else {
552			Ok(BigRational::from_float(*value).unwrap().try_into().unwrap())
553		}
554	}
555}