xsd_types/lexical/decimal/
mod.rs1use super::lexical_form;
2use std::borrow::{Borrow, ToOwned};
3use std::cmp::Ordering;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6
7#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
9pub enum Sign {
10 Negative,
11 Zero,
12 Positive,
13}
14
15impl Sign {
16 pub fn is_positive(&self) -> bool {
17 matches!(self, Self::Positive)
18 }
19
20 pub fn is_negative(&self) -> bool {
21 matches!(self, Self::Negative)
22 }
23
24 pub fn is_zero(&self) -> bool {
25 matches!(self, Self::Zero)
26 }
27}
28
29pub struct Overflow;
31
32mod integer;
33
34pub use integer::*;
35
36lexical_form! {
37 ty: Decimal,
41
42 buffer: DecimalBuf,
46
47 new,
52
53 new_unchecked,
59
60 value: crate::Decimal,
61 error: InvalidDecimal,
62 as_ref: as_decimal,
63 parent_forms: {}
64}
65
66impl Decimal {
67 pub fn is_positive(&self) -> bool {
70 let mut sign_positive = true;
71 for c in &self.0 {
72 match c {
73 b'+' | b'0' | b'.' => (),
74 b'-' => sign_positive = false,
75 _ => return sign_positive,
76 }
77 }
78
79 false
80 }
81
82 pub fn is_negative(&self) -> bool {
85 let mut sign_negative = true;
86 for c in &self.0 {
87 match c {
88 b'-' | b'0' | b'.' => (),
89 b'+' => sign_negative = false,
90 _ => return sign_negative,
91 }
92 }
93
94 false
95 }
96
97 pub fn is_zero(&self) -> bool {
100 for c in &self.0 {
101 if !matches!(c, b'+' | b'-' | b'0' | b'.') {
102 return false;
103 }
104 }
105
106 true
107 }
108
109 pub fn sign(&self) -> Sign {
110 let mut sign_positive = true;
111 for c in &self.0 {
112 match c {
113 b'+' | b'0' | b'.' => (),
114 b'-' => sign_positive = false,
115 _ => {
116 if sign_positive {
117 return Sign::Positive;
118 } else {
119 return Sign::Negative;
120 }
121 }
122 }
123 }
124
125 Sign::Zero
126 }
127
128 #[inline(always)]
129 pub fn integer_part(&self) -> &Integer {
130 match self.split_once('.') {
131 Some((integer_part, _)) => unsafe { Integer::new_unchecked(integer_part) },
132 None => unsafe { Integer::new_unchecked(self) },
133 }
134 }
135
136 #[inline(always)]
137 pub fn fractional_part(&self) -> Option<&FractionalPart> {
138 self.split_once('.')
139 .map(|(_, fractional_part)| unsafe { FractionalPart::new_unchecked(fractional_part) })
140 }
141
142 #[inline(always)]
143 pub fn trimmed_fractional_part(&self) -> Option<&FractionalPart> {
144 self.split_once('.').and_then(|(_, fractional_part)| {
145 let f = unsafe { FractionalPart::new_unchecked(fractional_part) }.trimmed();
146 if f.is_empty() {
147 None
148 } else {
149 Some(f)
150 }
151 })
152 }
153
154 #[inline(always)]
155 pub fn parts(&self) -> (&Integer, Option<&FractionalPart>) {
156 match self.split_once('.') {
157 Some((i, f)) => unsafe {
158 (
159 Integer::new_unchecked(i),
160 Some(FractionalPart::new_unchecked(f)),
161 )
162 },
163 None => unsafe { (Integer::new_unchecked(self), None) },
164 }
165 }
166
167 pub fn value(&self) -> crate::Decimal {
168 self.to_owned().into()
169 }
170}
171
172impl PartialEq for Decimal {
173 fn eq(&self, other: &Self) -> bool {
174 self.integer_part() == other.integer_part()
175 && self.fractional_part() == other.fractional_part()
176 }
177}
178
179impl Eq for Decimal {}
180
181impl Hash for Decimal {
182 fn hash<H: Hasher>(&self, h: &mut H) {
183 self.integer_part().hash(h);
184 match self.fractional_part() {
185 Some(f) => f.hash(h),
186 None => FractionalPart::empty().hash(h),
187 }
188 }
189}
190
191impl Ord for Decimal {
192 fn cmp(&self, other: &Self) -> Ordering {
193 let sign = self.sign();
194 match sign.cmp(&other.sign()) {
195 Ordering::Equal => {
196 let (integer_part, fractional_part) = self.parts();
197 let (other_integer_part, other_fractional_part) = other.parts();
198 match integer_part.cmp(other_integer_part) {
199 Ordering::Equal => {
200 let fractional_part = fractional_part.unwrap_or_else(FractionalPart::empty);
201 let other_fractional_part =
202 other_fractional_part.unwrap_or_else(FractionalPart::empty);
203 if sign.is_negative() {
204 fractional_part.cmp(other_fractional_part).reverse()
205 } else {
206 fractional_part.cmp(other_fractional_part)
207 }
208 }
209 other => {
210 if sign.is_negative() {
211 other.reverse()
212 } else {
213 other
214 }
215 }
216 }
217 }
218 other => other,
219 }
220 }
221}
222
223impl PartialOrd for Decimal {
224 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
225 Some(self.cmp(other))
226 }
227}
228
229macro_rules! integer_conversion {
230 { $($ty:ty),* } => {
231 $(
232 impl From<$ty> for DecimalBuf {
233 fn from(i: $ty) -> Self {
234 unsafe { DecimalBuf::new_unchecked(i.to_string()) }
235 }
236 }
237
238 impl<'a> TryFrom<&'a Decimal> for $ty {
239 type Error = Overflow;
240
241 fn try_from(i: &'a Decimal) -> Result<Self, Overflow> {
242 i.as_str().parse().map_err(|_| Overflow)
243 }
244 }
245
246 impl TryFrom<DecimalBuf> for $ty {
247 type Error = Overflow;
248
249 fn try_from(i: DecimalBuf) -> Result<Self, Overflow> {
250 i.as_str().parse().map_err(|_| Overflow)
251 }
252 }
253 )*
254 };
255}
256
257integer_conversion! {
258 u8,
259 i8,
260 u16,
261 i16,
262 u32,
263 i32,
264 u64,
265 i64,
266 usize,
267 isize
268}
269
270const DTOA_CONFIG: pretty_dtoa::FmtFloatConfig =
271 pretty_dtoa::FmtFloatConfig::default().force_no_e_notation();
272
273impl From<f32> for DecimalBuf {
274 fn from(i: f32) -> Self {
275 unsafe { DecimalBuf::new_unchecked(pretty_dtoa::ftoa(i, DTOA_CONFIG)) }
276 }
277}
278
279impl<'a> TryFrom<&'a Decimal> for f32 {
280 type Error = <f32 as std::str::FromStr>::Err;
281
282 fn try_from(i: &'a Decimal) -> Result<Self, Self::Error> {
283 i.as_str().parse()
284 }
285}
286
287impl TryFrom<DecimalBuf> for f32 {
288 type Error = <f32 as std::str::FromStr>::Err;
289
290 fn try_from(i: DecimalBuf) -> Result<Self, Self::Error> {
291 i.as_str().parse()
292 }
293}
294
295impl From<f64> for DecimalBuf {
296 fn from(i: f64) -> Self {
297 unsafe { DecimalBuf::new_unchecked(pretty_dtoa::dtoa(i, DTOA_CONFIG)) }
298 }
299}
300
301impl<'a> TryFrom<&'a Decimal> for f64 {
302 type Error = <f64 as std::str::FromStr>::Err;
303
304 fn try_from(i: &'a Decimal) -> Result<Self, Self::Error> {
305 i.as_str().parse()
306 }
307}
308
309impl TryFrom<DecimalBuf> for f64 {
310 type Error = <f64 as std::str::FromStr>::Err;
311
312 fn try_from(i: DecimalBuf) -> Result<Self, Self::Error> {
313 i.as_str().parse()
314 }
315}
316
317pub struct FractionalPart([u8]);
318
319impl FractionalPart {
320 #[inline(always)]
327 pub unsafe fn new_unchecked<S: ?Sized + AsRef<[u8]>>(s: &S) -> &Self {
328 std::mem::transmute(s.as_ref())
329 }
330
331 #[inline(always)]
332 pub fn empty<'a>() -> &'a Self {
333 unsafe { Self::new_unchecked(b"") }
334 }
335
336 #[inline(always)]
337 pub fn as_str(&self) -> &str {
338 unsafe { core::str::from_utf8_unchecked(&self.0) }
339 }
340
341 #[inline(always)]
342 pub fn as_bytes(&self) -> &[u8] {
343 &self.0
344 }
345
346 #[inline(always)]
347 pub fn is_empty(&self) -> bool {
348 self.0.is_empty()
349 }
350
351 pub fn trimmed(&self) -> &FractionalPart {
355 let mut end = 0;
356 for (i, &c) in self.0.iter().enumerate() {
357 if c != b'0' {
358 end = i + 1
359 }
360 }
361
362 unsafe { Self::new_unchecked(&self.0[0..end]) }
363 }
364}
365
366impl PartialEq for FractionalPart {
367 fn eq(&self, other: &Self) -> bool {
368 self.trimmed().0 == other.trimmed().0
369 }
370}
371
372impl Eq for FractionalPart {}
373
374impl Hash for FractionalPart {
375 fn hash<H: Hasher>(&self, h: &mut H) {
376 self.trimmed().0.hash(h)
377 }
378}
379
380impl Ord for FractionalPart {
381 fn cmp(&self, other: &Self) -> Ordering {
382 self.trimmed().0.cmp(&other.trimmed().0)
383 }
384}
385
386impl PartialOrd for FractionalPart {
387 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
388 Some(self.cmp(other))
389 }
390}
391impl AsRef<[u8]> for FractionalPart {
392 fn as_ref(&self) -> &[u8] {
393 &self.0
394 }
395}
396
397impl AsRef<str> for FractionalPart {
398 fn as_ref(&self) -> &str {
399 self.as_str()
400 }
401}
402
403fn check_bytes(s: &[u8]) -> bool {
404 check(s.iter().copied())
405}
406
407fn check<C: Iterator<Item = u8>>(mut chars: C) -> bool {
408 enum State {
409 Initial,
410 NonEmptyInteger,
411 Integer,
412 NonEmptyDecimal,
413 Decimal,
414 }
415
416 let mut state = State::Initial;
417
418 loop {
419 state = match state {
420 State::Initial => match chars.next() {
421 Some(b'+') => State::NonEmptyInteger,
422 Some(b'-') => State::NonEmptyInteger,
423 Some(b'.') => State::NonEmptyDecimal,
424 Some(b'0'..=b'9') => State::Integer,
425 _ => break false,
426 },
427 State::NonEmptyInteger => match chars.next() {
428 Some(b'0'..=b'9') => State::Integer,
429 Some(b'.') => State::Decimal,
430 _ => break false,
431 },
432 State::Integer => match chars.next() {
433 Some(b'0'..=b'9') => State::Integer,
434 Some(b'.') => State::Decimal,
435 Some(_) => break false,
436 None => break true,
437 },
438 State::NonEmptyDecimal => match chars.next() {
439 Some(b'0'..=b'9') => State::Decimal,
440 _ => break false,
441 },
442 State::Decimal => match chars.next() {
443 Some(b'0'..=b'9') => State::Decimal,
444 Some(_) => break false,
445 None => break true,
446 },
447 }
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn parse_01() {
457 Decimal::new("0").unwrap();
458 }
459
460 #[test]
461 #[should_panic]
462 fn parse_02() {
463 Decimal::new("+").unwrap();
464 }
465
466 #[test]
467 #[should_panic]
468 fn parse_03() {
469 Decimal::new("-").unwrap();
470 }
471
472 #[test]
473 #[should_panic]
474 fn parse_04() {
475 Decimal::new("012+").unwrap();
476 }
477
478 #[test]
479 fn parse_05() {
480 Decimal::new("+42").unwrap();
481 }
482
483 #[test]
484 fn parse_06() {
485 Decimal::new("-42").unwrap();
486 }
487
488 #[test]
489 #[should_panic]
490 fn parse_07() {
491 Decimal::new(".").unwrap();
492 }
493
494 #[test]
495 fn parse_08() {
496 Decimal::new(".0").unwrap();
497 }
498
499 #[test]
500 fn parse_09() {
501 Decimal::new("0.").unwrap();
502 }
503
504 #[test]
505 fn parse_10() {
506 Decimal::new("42.0").unwrap();
507 }
508
509 #[test]
510 fn format_01() {
511 assert_eq!(DecimalBuf::from(1.0e10f32).to_string(), "10000000000.0")
512 }
513
514 #[test]
515 fn cmp_01() {
516 assert!(Decimal::new("0.123").unwrap() < Decimal::new("1.123").unwrap())
517 }
518
519 #[test]
520 fn cmp_02() {
521 assert!(Decimal::new("0.123").unwrap() < Decimal::new("0.1234").unwrap())
522 }
523
524 #[test]
525 fn cmp_03() {
526 assert!(Decimal::new("0.123").unwrap() > Decimal::new("-0.123").unwrap())
527 }
528
529 #[test]
530 fn cmp_04() {
531 assert!(Decimal::new("-0.123").unwrap() > Decimal::new("-0.1234").unwrap())
532 }
533}