1use core::cmp::Ordering;
4use core::fmt;
5use core::hash::{Hash, Hasher};
6use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
7#[cfg(feature = "serde")]
8use serde::de::Unexpected;
9
10enum CoerceResult {
11 PosInt(u64, u64),
12 NegInt(i64, i64),
13 Float(f64, f64),
14}
15
16fn coerce(a: N, b: N) -> CoerceResult {
18 match (a, b) {
19 (N::PosInt(a), N::PosInt(b)) => CoerceResult::PosInt(a, b),
20 (N::NegInt(a), N::NegInt(b)) => CoerceResult::NegInt(a, b),
21 (N::Float(a), N::Float(b)) => CoerceResult::Float(a, b),
22 (a, b) => CoerceResult::Float(a.to_f64(), b.to_f64()),
23 }
24}
25
26#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
28pub struct Number {
29 n: N,
30}
31
32#[derive(Clone, Copy)]
33enum N {
34 PosInt(u64),
35 NegInt(i64),
37 Float(f64),
39}
40
41impl N {
42 fn from_finite_f64(value: f64) -> N {
43 debug_assert!(value.is_finite());
44
45 #[cfg(feature = "std")]
46 let no_fraction = value.fract() == 0.0;
47
48 #[cfg(not(feature = "std"))]
50 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
51 let no_fraction = value - (value as i64 as f64) == 0.0;
52
53 if no_fraction {
54 #[allow(clippy::cast_possible_truncation)]
55 N::from(value as i64)
56 } else {
57 N::Float(value)
58 }
59 }
60
61 fn as_i64(&self) -> Option<i64> {
62 match *self {
63 N::PosInt(n) => i64::try_from(n).ok(),
64 N::NegInt(n) => Some(n),
65 N::Float(_) => None,
66 }
67 }
68
69 fn as_u64(&self) -> Option<u64> {
70 match *self {
71 N::PosInt(n) => Some(n),
72 N::NegInt(n) => u64::try_from(n).ok(),
73 N::Float(_) => None,
74 }
75 }
76
77 fn to_f64(self) -> f64 {
78 #[allow(clippy::cast_precision_loss)]
79 match self {
80 N::PosInt(n) => n as f64,
81 N::NegInt(n) => n as f64,
82 N::Float(n) => n,
83 }
84 }
85
86 fn is_f64(&self) -> bool {
87 match self {
88 N::Float(_) => true,
89 N::PosInt(_) | N::NegInt(_) => false,
90 }
91 }
92
93 fn is_i64(&self) -> bool {
94 match self {
95 N::NegInt(_) => true,
96 N::PosInt(_) | N::Float(_) => false,
97 }
98 }
99
100 fn is_u64(&self) -> bool {
101 match self {
102 N::PosInt(_) => true,
103 N::NegInt(_) | N::Float(_) => false,
104 }
105 }
106}
107
108impl PartialEq for N {
109 fn eq(&self, other: &Self) -> bool {
110 match coerce(*self, *other) {
111 CoerceResult::PosInt(a, b) => a == b,
112 CoerceResult::NegInt(a, b) => a == b,
113 CoerceResult::Float(a, b) => a == b,
114 }
115 }
116}
117
118impl Eq for N {}
120
121impl PartialOrd for N {
122 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
123 match coerce(*self, *other) {
124 CoerceResult::PosInt(a, b) => a.partial_cmp(&b),
125 CoerceResult::NegInt(a, b) => a.partial_cmp(&b),
126 CoerceResult::Float(a, b) => a.partial_cmp(&b),
127 }
128 }
129}
130
131impl Hash for N {
132 fn hash<H>(&self, h: &mut H)
133 where
134 H: Hasher,
135 {
136 match *self {
137 N::PosInt(n) => n.hash(h),
138 N::NegInt(n) => n.hash(h),
139 N::Float(n) => {
140 if n == 0.0f64 {
141 0.0f64.to_bits().hash(h);
145 } else {
146 n.to_bits().hash(h);
147 }
148 }
149 }
150 }
151}
152
153impl From<i64> for N {
154 fn from(i: i64) -> Self {
155 if i < 0 {
156 N::NegInt(i)
157 } else {
158 #[allow(clippy::cast_sign_loss)]
159 N::PosInt(i as u64)
160 }
161 }
162}
163
164impl Neg for N {
165 type Output = N;
166
167 fn neg(self) -> Self::Output {
168 match self {
169 #[allow(clippy::cast_possible_wrap)]
170 N::PosInt(value) => N::NegInt(-(value as i64)),
171 N::NegInt(value) => N::from(-value),
172 N::Float(value) => N::Float(-value),
173 }
174 }
175}
176
177impl Add for N {
178 type Output = N;
179
180 fn add(self, rhs: Self) -> Self::Output {
181 match coerce(self, rhs) {
182 CoerceResult::PosInt(a, b) => N::PosInt(a + b),
183 CoerceResult::NegInt(a, b) => N::NegInt(a + b),
184 CoerceResult::Float(a, b) => N::from_finite_f64(a + b),
185 }
186 }
187}
188
189impl Sub for N {
190 type Output = N;
191
192 fn sub(self, rhs: Self) -> Self::Output {
193 match coerce(self, rhs) {
194 CoerceResult::PosInt(a, b) => {
195 if b > a {
196 #[allow(clippy::cast_possible_wrap)]
197 N::NegInt(a as i64 - b as i64)
198 } else {
199 N::PosInt(a - b)
200 }
201 }
202 CoerceResult::NegInt(a, b) => N::from(a - b),
203 CoerceResult::Float(a, b) => N::from_finite_f64(a - b),
204 }
205 }
206}
207
208impl Mul for N {
209 type Output = N;
210
211 fn mul(self, rhs: Self) -> Self::Output {
212 match coerce(self, rhs) {
213 CoerceResult::PosInt(a, b) => N::PosInt(a * b),
214 CoerceResult::NegInt(a, b) => N::from(a * b),
215 CoerceResult::Float(a, b) => N::from_finite_f64(a * b),
216 }
217 }
218}
219
220impl Div for N {
221 type Output = N;
222
223 fn div(self, rhs: Self) -> Self::Output {
224 N::from_finite_f64(self.to_f64() / rhs.to_f64())
225 }
226}
227
228impl Rem for N {
229 type Output = N;
230
231 fn rem(self, rhs: Self) -> Self::Output {
232 match coerce(self, rhs) {
233 CoerceResult::PosInt(a, b) => N::PosInt(a % b),
234 CoerceResult::NegInt(a, b) => N::NegInt(a % b),
235 CoerceResult::Float(a, b) => N::from_finite_f64(a % b),
236 }
237 }
238}
239
240impl Number {
241 pub fn from_f64(f: f64) -> Option<Number> {
253 if f.is_finite() {
254 Some(Number::from_finite_f64(f))
255 } else {
256 None
257 }
258 }
259
260 pub(crate) fn from_finite_f64(f: f64) -> Number {
261 Number {
262 n: N::from_finite_f64(f),
263 }
264 }
265
266 #[inline]
268 pub fn as_f64(&self) -> Option<f64> {
269 Some(self.n.to_f64())
270 }
271
272 #[inline]
274 pub fn as_i64(&self) -> Option<i64> {
275 self.n.as_i64()
276 }
277
278 #[inline]
280 pub fn as_u64(&self) -> Option<u64> {
281 self.n.as_u64()
282 }
283
284 #[inline]
289 pub fn is_f64(&self) -> bool {
290 self.n.is_f64()
291 }
292
293 #[inline]
298 pub fn is_i64(&self) -> bool {
299 self.n.is_i64()
300 }
301
302 #[inline]
307 pub fn is_u64(&self) -> bool {
308 self.n.is_u64()
309 }
310
311 #[cfg(feature = "serde")]
313 #[doc(hidden)]
314 #[cold]
315 pub fn unexpected(&self) -> Unexpected<'_> {
316 match self.n {
317 N::PosInt(v) => Unexpected::Unsigned(v),
318 N::NegInt(v) => Unexpected::Signed(v),
319 N::Float(v) => Unexpected::Float(v),
320 }
321 }
322}
323
324macro_rules! impl_from_unsigned {
325 ($($ty:ty),*) => {
326 $(
327 impl From<$ty> for Number {
328 #[inline]
329 fn from(u: $ty) -> Self {
330 Number {
331 #[allow(clippy::cast_lossless)]
332 n: N::PosInt(u as u64)
333 }
334 }
335 }
336 )*
337 };
338}
339
340macro_rules! impl_from_signed {
341 ($($ty:ty),*) => {
342 $(
343 impl From<$ty> for Number {
344 #[inline]
345 fn from(i: $ty) -> Self {
346 Number {
347 #[allow(clippy::cast_lossless)]
348 n: N::from(i as i64)
349 }
350 }
351 }
352 )*
353 };
354}
355
356macro_rules! impl_binary_ops {
357 ($($op:ty => $method:ident),*) => {
358 $(
359 impl $op for Number {
360 type Output = Number;
361
362 fn $method(self, rhs: Self) -> Self::Output {
363 Number {
364 n: self.n.$method(rhs.n)
365 }
366 }
367 }
368 )*
369 };
370}
371
372impl_from_unsigned!(u8, u16, u32, u64, usize);
373impl_from_signed!(i8, i16, i32, i64, isize);
374impl_binary_ops!(Add => add, Sub => sub, Mul => mul, Div => div, Rem => rem);
375
376impl Neg for Number {
377 type Output = Number;
378
379 fn neg(self) -> Self::Output {
380 Number { n: -self.n }
381 }
382}
383
384impl fmt::Display for Number {
385 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
386 match self.n {
387 N::PosInt(v) => f.write_str(itoa::Buffer::new().format(v)),
388 N::NegInt(v) => f.write_str(itoa::Buffer::new().format(v)),
389 N::Float(v) => f.write_str(ryu::Buffer::new().format_finite(v)),
390 }
391 }
392}
393
394impl fmt::Debug for Number {
395 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
396 write!(f, "Number({self})")
397 }
398}
399
400#[cfg(feature = "serde")]
401impl serde::Serialize for Number {
402 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
403 where
404 S: serde::Serializer,
405 {
406 match self.n {
407 N::PosInt(v) => serializer.serialize_u64(v),
408 N::NegInt(v) => serializer.serialize_i64(v),
409 N::Float(v) => serializer.serialize_f64(v),
410 }
411 }
412}
413
414#[cfg(feature = "serde")]
415impl<'de> serde::Deserialize<'de> for Number {
416 fn deserialize<D>(deserializer: D) -> Result<Number, D::Error>
417 where
418 D: serde::Deserializer<'de>,
419 {
420 struct NumberVisitor;
421
422 impl serde::de::Visitor<'_> for NumberVisitor {
423 type Value = Number;
424
425 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
426 formatter.write_str("an HCL number")
427 }
428
429 fn visit_i64<E>(self, value: i64) -> Result<Number, E> {
430 Ok(value.into())
431 }
432
433 fn visit_u64<E>(self, value: u64) -> Result<Number, E> {
434 Ok(value.into())
435 }
436
437 fn visit_f64<E>(self, value: f64) -> Result<Number, E>
438 where
439 E: serde::de::Error,
440 {
441 Number::from_f64(value).ok_or_else(|| serde::de::Error::custom("not an HCL number"))
442 }
443 }
444
445 deserializer.deserialize_any(NumberVisitor)
446 }
447}
448
449#[cfg(feature = "serde")]
450impl<'de> serde::Deserializer<'de> for Number {
451 type Error = super::Error;
452
453 fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
454 where
455 V: serde::de::Visitor<'de>,
456 {
457 match self.n {
458 N::PosInt(i) => visitor.visit_u64(i),
459 N::NegInt(i) => visitor.visit_i64(i),
460 N::Float(f) => visitor.visit_f64(f),
461 }
462 }
463
464 serde::forward_to_deserialize_any! {
465 bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
466 bytes byte_buf option unit unit_struct newtype_struct seq tuple
467 tuple_struct enum map struct identifier ignored_any
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 macro_rules! float {
476 ($f:expr) => {
477 Number::from_finite_f64($f)
478 };
479 }
480
481 macro_rules! int {
482 ($i:expr) => {
483 Number::from($i)
484 };
485 }
486
487 macro_rules! assert_op {
488 ($expr:expr, $expected:expr, $check:ident) => {
489 let result = $expr;
490 assert_eq!(result, $expected, "incorrect number op result");
491 assert!(result.$check());
492 };
493 }
494
495 #[test]
496 fn neg() {
497 assert_op!(-int!(1u64), int!(-1i64), is_i64);
498 assert_op!(-float!(1.5), float!(-1.5), is_f64);
499 assert_op!(-float!(1.0), int!(-1i64), is_i64);
500 }
501
502 #[test]
503 fn add() {
504 assert_op!(int!(1i64) + int!(2u64), int!(3), is_u64);
505 assert_op!(float!(1.5) + float!(1.5), int!(3), is_u64);
506 assert_op!(float!(1.5) + int!(-1i64), float!(0.5), is_f64);
507 assert_op!(int!(-1i64) + int!(-2i64), int!(-3i64), is_i64);
508 }
509
510 #[test]
511 fn sub() {
512 assert_op!(int!(1i64) - int!(2u64), int!(-1i64), is_i64);
513 assert_op!(int!(-1i64) - int!(-2i64), int!(1u64), is_u64);
514 assert_op!(float!(1.5) - float!(1.5), int!(0), is_u64);
515 assert_op!(float!(1.5) - int!(-1i64), float!(2.5), is_f64);
516 }
517
518 #[test]
519 fn mul() {
520 assert_op!(int!(-1i64) * int!(2u64), int!(-2i64), is_i64);
521 assert_op!(int!(-1i64) * int!(-2i64), int!(2u64), is_u64);
522 assert_op!(float!(1.5) * float!(1.5), float!(2.25), is_f64);
523 assert_op!(float!(1.5) * int!(-1i64), float!(-1.5), is_f64);
524 }
525
526 #[test]
527 fn div() {
528 assert_op!(int!(1u64) / int!(2u64), float!(0.5), is_f64);
529 assert_op!(float!(4.1) / float!(2.0), float!(2.05), is_f64);
530 assert_op!(int!(4u64) / int!(2u64), int!(2u64), is_u64);
531 assert_op!(int!(-4i64) / int!(2u64), int!(-2i64), is_i64);
532 assert_op!(float!(4.0) / float!(2.0), int!(2), is_u64);
533 assert_op!(float!(-4.0) / float!(2.0), int!(-2), is_i64);
534 }
535
536 #[test]
537 fn rem() {
538 assert_op!(int!(3u64) % int!(2u64), int!(1u64), is_u64);
539 assert_op!(
540 float!(4.1) % float!(2.0),
541 float!(0.099_999_999_999_999_64),
542 is_f64
543 );
544 assert_op!(int!(4u64) % int!(2u64), int!(0u64), is_u64);
545 assert_op!(int!(-4i64) % int!(3u64), int!(-1i64), is_i64);
546 assert_op!(float!(4.0) % float!(2.0), int!(0), is_u64);
547 assert_op!(float!(-4.0) % float!(3.0), int!(-1), is_i64);
548 }
549}