Skip to main content

qubit_datatype/numeric/
number_ref.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Borrowed numeric value representations used by comparison APIs.
9
10use std::cmp::Ordering;
11
12#[cfg(feature = "big-decimal")]
13use bigdecimal::BigDecimal;
14#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
15use num_bigint::BigInt;
16#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
17use num_rational::BigRational;
18#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
19use num_traits::Signed;
20#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
21use num_traits::ToPrimitive;
22#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
23use num_traits::Zero;
24
25use super::NumericComparisonPolicy;
26use super::internal::NumberRepr;
27#[cfg(feature = "big-integer")]
28use super::internal::compare_big_integer_float;
29use super::internal::compare_magnitude;
30#[cfg(feature = "big-decimal")]
31use super::internal::decimal_rational;
32#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
33use super::internal::f32_rational;
34#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
35use super::internal::f64_rational;
36use super::internal::finite_parts;
37
38/// Compares an arbitrary-precision integer with a signed fixed-width value.
39///
40/// # Parameters
41///
42/// * `left` - Arbitrary-precision value on the left.
43/// * `right` - Signed fixed-width value on the right.
44///
45/// # Returns
46///
47/// The exact mathematical ordering.
48#[cfg(feature = "big-integer")]
49#[must_use]
50#[inline]
51fn compare_big_integer_signed(left: &BigInt, right: i128) -> Ordering {
52    match left.to_i128() {
53        Some(left) => left.cmp(&right),
54        None if left.is_negative() => Ordering::Less,
55        None => Ordering::Greater,
56    }
57}
58
59/// Compares an arbitrary-precision integer with an unsigned fixed-width value.
60///
61/// # Parameters
62///
63/// * `left` - Arbitrary-precision value on the left.
64/// * `right` - Unsigned fixed-width value on the right.
65///
66/// # Returns
67///
68/// The exact mathematical ordering.
69#[cfg(feature = "big-integer")]
70#[must_use]
71#[inline]
72fn compare_big_integer_unsigned(left: &BigInt, right: u128) -> Ordering {
73    if left.is_negative() {
74        Ordering::Less
75    } else {
76        match left.to_u128() {
77            Some(left) => left.cmp(&right),
78            None => Ordering::Greater,
79        }
80    }
81}
82
83/// Compares an arbitrary-precision integer with a fixed-width representation.
84///
85/// # Parameters
86///
87/// * `left` - Arbitrary-precision integer on the left.
88/// * `right` - Candidate fixed-width integer on the right.
89///
90/// # Returns
91///
92/// The ordering for a fixed-width integer, or `None` for any other numeric
93/// representation.
94#[cfg(feature = "big-integer")]
95fn compare_big_integer_to_fixed(left: &BigInt, right: NumberRepr<'_>) -> Option<Ordering> {
96    match right {
97        NumberRepr::Int8(right) => Some(compare_big_integer_signed(left, i128::from(right))),
98        NumberRepr::Int16(right) => Some(compare_big_integer_signed(left, i128::from(right))),
99        NumberRepr::Int32(right) => Some(compare_big_integer_signed(left, i128::from(right))),
100        NumberRepr::Int64(right) => Some(compare_big_integer_signed(left, i128::from(right))),
101        NumberRepr::Int128(right) => Some(compare_big_integer_signed(left, right)),
102        NumberRepr::UInt8(right) => Some(compare_big_integer_unsigned(left, u128::from(right))),
103        NumberRepr::UInt16(right) => Some(compare_big_integer_unsigned(left, u128::from(right))),
104        NumberRepr::UInt32(right) => Some(compare_big_integer_unsigned(left, u128::from(right))),
105        NumberRepr::UInt64(right) => Some(compare_big_integer_unsigned(left, u128::from(right))),
106        NumberRepr::UInt128(right) => Some(compare_big_integer_unsigned(left, right)),
107        _ => None,
108    }
109}
110
111/// Borrows or copies a numeric value without depending on a runtime value enum.
112///
113/// Values are constructed through [`From`] implementations. The internal
114/// representation is intentionally private, so callers cannot create marker
115/// or otherwise non-numeric states.
116///
117/// # Type Parameters
118///
119/// * `'a` - Lifetime of a borrowed arbitrary-precision numeric value.
120///
121/// # Examples
122///
123/// ```
124/// use qubit_datatype::NumberRef;
125///
126/// let number = NumberRef::from(42_i64);
127/// assert!(number.is_integer());
128/// ```
129///
130/// ```
131/// use std::cmp::Ordering;
132/// use qubit_datatype::NumberRef;
133/// use qubit_datatype::NumericComparisonPolicy;
134///
135/// let integer = NumberRef::from((1_u64 << 53) + 1);
136/// let float = NumberRef::from((1_u64 << 53) as f64);
137/// assert_eq!(integer.compare(float, NumericComparisonPolicy::Exact), Some(Ordering::Greater));
138/// assert_eq!(integer.compare(float, NumericComparisonPolicy::Approximate), Some(Ordering::Equal));
139/// assert_eq!(integer.compare(NumberRef::from(f64::NAN), NumericComparisonPolicy::Exact), None);
140/// ```
141///
142/// ```compile_fail
143/// use qubit_datatype::NumberRef;
144///
145/// let _ = NumberRef::Int8(1);
146/// ```
147#[must_use]
148#[derive(Debug, Clone, Copy)]
149pub struct NumberRef<'a> {
150    /// Private numeric representation.
151    inner: NumberRepr<'a>,
152}
153
154/// Implements copied fixed-width conversions into [`NumberRef`].
155macro_rules! impl_from_copy {
156    ($source:ty, $variant:ident) => {
157        impl From<$source> for NumberRef<'_> {
158            /// Creates a numeric view by copying a fixed-width value.
159            ///
160            /// # Parameters
161            ///
162            /// * `value` - Numeric value to copy.
163            ///
164            /// # Returns
165            ///
166            /// A numeric view owning the copied scalar representation.
167            #[inline]
168            fn from(value: $source) -> Self {
169                Self {
170                    inner: NumberRepr::$variant(value),
171                }
172            }
173        }
174    };
175}
176
177/// Implements borrowed arbitrary-precision conversions into [`NumberRef`].
178#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
179macro_rules! impl_from_ref {
180    ($source:ty, $variant:ident) => {
181        impl<'a> From<&'a $source> for NumberRef<'a> {
182            /// Creates a numeric view borrowing an arbitrary-precision value.
183            ///
184            /// # Parameters
185            ///
186            /// * `value` - Numeric value to borrow.
187            ///
188            /// # Returns
189            ///
190            /// A numeric view borrowing the source for its lifetime.
191            #[inline]
192            fn from(value: &'a $source) -> Self {
193                Self {
194                    inner: NumberRepr::$variant(value),
195                }
196            }
197        }
198    };
199}
200
201impl_from_copy!(i8, Int8);
202impl_from_copy!(i16, Int16);
203impl_from_copy!(i32, Int32);
204impl_from_copy!(i64, Int64);
205impl_from_copy!(i128, Int128);
206impl_from_copy!(u8, UInt8);
207impl_from_copy!(u16, UInt16);
208impl_from_copy!(u32, UInt32);
209impl_from_copy!(u64, UInt64);
210impl_from_copy!(u128, UInt128);
211impl_from_copy!(f32, Float32);
212impl_from_copy!(f64, Float64);
213#[cfg(feature = "big-integer")]
214impl_from_ref!(BigInt, BigInteger);
215#[cfg(feature = "big-decimal")]
216impl_from_ref!(BigDecimal, BigDecimal);
217
218impl<'a> NumberRef<'a> {
219    /// Reports whether this value is an integer representation.
220    ///
221    /// # Returns
222    ///
223    /// `true` for fixed-width integer values.
224    #[inline]
225    #[must_use]
226    pub fn is_integer(self) -> bool {
227        matches!(
228            self.inner,
229            NumberRepr::Int8(_)
230                | NumberRepr::Int16(_)
231                | NumberRepr::Int32(_)
232                | NumberRepr::Int64(_)
233                | NumberRepr::Int128(_)
234                | NumberRepr::UInt8(_)
235                | NumberRepr::UInt16(_)
236                | NumberRepr::UInt32(_)
237                | NumberRepr::UInt64(_)
238                | NumberRepr::UInt128(_)
239        )
240    }
241
242    /// Reports whether this value is a primitive floating-point value.
243    ///
244    /// # Returns
245    ///
246    /// `true` for `f32` and `f64` values.
247    #[inline]
248    #[must_use]
249    pub fn is_float(self) -> bool {
250        matches!(self.inner, NumberRepr::Float32(_) | NumberRepr::Float64(_))
251    }
252
253    /// Reports whether this value is an arbitrary-precision integer.
254    ///
255    /// # Returns
256    ///
257    /// `true` for `BigInt` values when the `big-integer` feature is enabled;
258    /// otherwise, `false`.
259    #[inline]
260    #[must_use]
261    pub fn is_big_integer(self) -> bool {
262        #[cfg(feature = "big-integer")]
263        {
264            matches!(self.inner, NumberRepr::BigInteger(_))
265        }
266        #[cfg(not(feature = "big-integer"))]
267        {
268            false
269        }
270    }
271
272    /// Reports whether this value is an arbitrary-precision decimal.
273    ///
274    /// # Returns
275    ///
276    /// `true` for `BigDecimal` values when the `big-decimal` feature is
277    /// enabled; otherwise, `false`.
278    #[inline]
279    #[must_use]
280    pub fn is_big_decimal(self) -> bool {
281        #[cfg(feature = "big-decimal")]
282        {
283            matches!(self.inner, NumberRepr::BigDecimal(_))
284        }
285        #[cfg(not(feature = "big-decimal"))]
286        {
287            false
288        }
289    }
290
291    /// Reports whether this value is NaN.
292    ///
293    /// # Returns
294    ///
295    /// `true` only for a primitive NaN payload.
296    #[inline]
297    #[must_use]
298    pub fn is_nan(self) -> bool {
299        match self.inner {
300            NumberRepr::Float32(value) => value.is_nan(),
301            NumberRepr::Float64(value) => value.is_nan(),
302            _ => false,
303        }
304    }
305
306    /// Reports whether this value is an infinite primitive float.
307    ///
308    /// # Returns
309    ///
310    /// `true` for positive or negative infinity; otherwise, `false`.
311    #[inline]
312    #[must_use]
313    pub fn is_infinite(self) -> bool {
314        match self.inner {
315            NumberRepr::Float32(value) => value.is_infinite(),
316            NumberRepr::Float64(value) => value.is_infinite(),
317            _ => false,
318        }
319    }
320
321    /// Reports whether this value is finite.
322    ///
323    /// # Returns
324    ///
325    /// `true` for all integers, arbitrary-precision values, and finite
326    /// primitive floats; otherwise, `false`.
327    #[inline]
328    #[must_use]
329    pub fn is_finite(self) -> bool {
330        !self.is_nan() && !self.is_infinite()
331    }
332
333    /// Reports whether this value is mathematically zero.
334    ///
335    /// # Returns
336    ///
337    /// `true` for integer zero and both positive and negative floating-point
338    /// zero; otherwise, `false`.
339    #[must_use]
340    pub fn is_zero(self) -> bool {
341        match self.inner {
342            NumberRepr::Int8(value) => value == 0,
343            NumberRepr::Int16(value) => value == 0,
344            NumberRepr::Int32(value) => value == 0,
345            NumberRepr::Int64(value) => value == 0,
346            NumberRepr::Int128(value) => value == 0,
347            NumberRepr::UInt8(value) => value == 0,
348            NumberRepr::UInt16(value) => value == 0,
349            NumberRepr::UInt32(value) => value == 0,
350            NumberRepr::UInt64(value) => value == 0,
351            NumberRepr::UInt128(value) => value == 0,
352            NumberRepr::Float32(value) => value == 0.0,
353            NumberRepr::Float64(value) => value == 0.0,
354            #[cfg(feature = "big-integer")]
355            NumberRepr::BigInteger(value) => value.is_zero(),
356            #[cfg(feature = "big-decimal")]
357            NumberRepr::BigDecimal(value) => value.is_zero(),
358            #[cfg(not(any(feature = "big-integer", feature = "big-decimal")))]
359            NumberRepr::Lifetime(_, impossible) => match impossible {},
360        }
361    }
362
363    /// Reports whether this value is greater than zero.
364    ///
365    /// # Returns
366    ///
367    /// `true` for positive finite or infinite values; zero, negative values,
368    /// and NaN return `false`.
369    #[must_use]
370    pub fn is_positive(self) -> bool {
371        match self.inner {
372            NumberRepr::Int8(value) => value > 0,
373            NumberRepr::Int16(value) => value > 0,
374            NumberRepr::Int32(value) => value > 0,
375            NumberRepr::Int64(value) => value > 0,
376            NumberRepr::Int128(value) => value > 0,
377            NumberRepr::UInt8(value) => value > 0,
378            NumberRepr::UInt16(value) => value > 0,
379            NumberRepr::UInt32(value) => value > 0,
380            NumberRepr::UInt64(value) => value > 0,
381            NumberRepr::UInt128(value) => value > 0,
382            NumberRepr::Float32(value) => value > 0.0,
383            NumberRepr::Float64(value) => value > 0.0,
384            #[cfg(feature = "big-integer")]
385            NumberRepr::BigInteger(value) => value.is_positive(),
386            #[cfg(feature = "big-decimal")]
387            NumberRepr::BigDecimal(value) => value.is_positive(),
388            #[cfg(not(any(feature = "big-integer", feature = "big-decimal")))]
389            NumberRepr::Lifetime(_, impossible) => match impossible {},
390        }
391    }
392
393    /// Reports whether this value is less than zero.
394    ///
395    /// # Returns
396    ///
397    /// `true` for negative finite or infinite values; zero, positive values,
398    /// and NaN return `false`.
399    #[must_use]
400    pub fn is_negative(self) -> bool {
401        match self.inner {
402            NumberRepr::Int8(value) => value < 0,
403            NumberRepr::Int16(value) => value < 0,
404            NumberRepr::Int32(value) => value < 0,
405            NumberRepr::Int64(value) => value < 0,
406            NumberRepr::Int128(value) => value < 0,
407            NumberRepr::UInt8(_) => false,
408            NumberRepr::UInt16(_) => false,
409            NumberRepr::UInt32(_) => false,
410            NumberRepr::UInt64(_) => false,
411            NumberRepr::UInt128(_) => false,
412            NumberRepr::Float32(value) => value < 0.0,
413            NumberRepr::Float64(value) => value < 0.0,
414            #[cfg(feature = "big-integer")]
415            NumberRepr::BigInteger(value) => value.is_negative(),
416            #[cfg(feature = "big-decimal")]
417            NumberRepr::BigDecimal(value) => value.is_negative(),
418            #[cfg(not(any(feature = "big-integer", feature = "big-decimal")))]
419            NumberRepr::Lifetime(_, impossible) => match impossible {},
420        }
421    }
422
423    /// Compares this value with `right` under an explicit numeric policy.
424    ///
425    /// Fixed-width comparisons do not require heap allocation. Some paths
426    /// involving `BigInt` or `BigDecimal` may clone coefficients or construct
427    /// exact rational or decimal values, with cost depending on the operand
428    /// representations. Direct comparisons between some large-number
429    /// representations avoid that work. Conversion limits and session budgets
430    /// do not apply to this independent comparison; constrain untrusted number
431    /// representations at the input boundary when their size must be bounded.
432    ///
433    /// See the [type-level examples](Self) for exact, approximate, and NaN
434    /// comparisons. Approximate comparison is not transitive across mixed
435    /// representations; do not use it for sorting or ordered keys.
436    ///
437    /// # Parameters
438    ///
439    /// * `right` - Right numeric operand.
440    /// * `policy` - Exact or approximate comparison policy.
441    ///
442    /// # Returns
443    ///
444    /// The numeric ordering, or `None` when either operand is NaN.
445    pub fn compare(self, right: NumberRef<'_>, policy: NumericComparisonPolicy) -> Option<Ordering> {
446        if self.is_nan() || right.is_nan() {
447            return None;
448        }
449        match (self.is_infinite(), right.is_infinite()) {
450            (true, true) => {
451                return Some(if self.is_negative() == right.is_negative() {
452                    Ordering::Equal
453                } else if self.is_negative() {
454                    Ordering::Less
455                } else {
456                    Ordering::Greater
457                });
458            }
459            (true, false) => {
460                return Some(if self.is_negative() {
461                    Ordering::Less
462                } else {
463                    Ordering::Greater
464                });
465            }
466            (false, true) => {
467                return Some(if right.is_negative() {
468                    Ordering::Greater
469                } else {
470                    Ordering::Less
471                });
472            }
473            (false, false) => {}
474        }
475        if policy == NumericComparisonPolicy::Approximate
476            && (self.is_float() || right.is_float())
477            && let (Some(left), Some(right)) = (self.to_approximate_f64(), right.to_approximate_f64())
478            && left.is_finite()
479            && right.is_finite()
480        {
481            return left.partial_cmp(&right);
482        }
483        #[cfg(any(feature = "big-integer", feature = "big-decimal"))]
484        {
485            #[cfg(feature = "big-integer")]
486            let has_big_integer = self.is_big_integer() || right.is_big_integer();
487            #[cfg(not(feature = "big-integer"))]
488            let has_big_integer = false;
489            #[cfg(feature = "big-decimal")]
490            let has_big_decimal = self.is_big_decimal() || right.is_big_decimal();
491            #[cfg(not(feature = "big-decimal"))]
492            let has_big_decimal = false;
493            if has_big_integer || has_big_decimal {
494                #[cfg(feature = "big-decimal")]
495                if policy == NumericComparisonPolicy::Exact
496                    && self.is_big_decimal() != right.is_big_decimal()
497                    && let Some(ordering) = self.compare_zero_or_opposite_sign(right)
498                {
499                    return Some(ordering);
500                }
501                return self.compare_exact_rational(right);
502            }
503        }
504        self.compare_fixed(right)
505    }
506
507    /// Compares mixed numeric representations when zero or opposite signs
508    /// determine their ordering without constructing exact rational values.
509    ///
510    /// # Parameters
511    ///
512    /// * `right` - Right numeric operand.
513    ///
514    /// # Returns
515    ///
516    /// The exact ordering when either value is zero or their nonzero signs
517    /// differ; otherwise, `None`.
518    #[cfg(feature = "big-decimal")]
519    fn compare_zero_or_opposite_sign(self, right: NumberRef<'_>) -> Option<Ordering> {
520        let left_zero = self.is_zero();
521        let right_zero = right.is_zero();
522        if left_zero {
523            return Some(if right_zero {
524                Ordering::Equal
525            } else if right.is_negative() {
526                Ordering::Greater
527            } else {
528                Ordering::Less
529            });
530        }
531        if right_zero {
532            return Some(if self.is_negative() {
533                Ordering::Less
534            } else {
535                Ordering::Greater
536            });
537        }
538        if self.is_negative() != right.is_negative() {
539            return Some(if self.is_negative() {
540                Ordering::Less
541            } else {
542                Ordering::Greater
543            });
544        }
545        None
546    }
547
548    /// Returns the private representation for numeric algorithms.
549    ///
550    /// # Returns
551    ///
552    /// The crate-private `NumberRepr` payload borrowed or copied by this value.
553    #[inline]
554    pub(in crate::numeric) const fn inner(self) -> NumberRepr<'a> {
555        self.inner
556    }
557
558    /// Compares arbitrary-precision pairs through exact rational values.
559    ///
560    /// Decimal scales that cannot be materialized as a practical power of ten
561    /// fall back to `BigDecimal`'s exact scale-aware ordering.
562    ///
563    /// # Parameters
564    ///
565    /// * `right` - Right arbitrary-precision operand.
566    ///
567    /// # Returns
568    ///
569    /// Their exact mathematical ordering, or `None` for non-finite values.
570    #[cfg(any(feature = "big-integer", feature = "big-decimal"))]
571    fn compare_exact_rational(self, right: NumberRef<'_>) -> Option<Ordering> {
572        #[cfg(feature = "big-integer")]
573        if let (NumberRepr::BigInteger(left), NumberRepr::BigInteger(right)) = (self.inner, right.inner) {
574            return Some(left.cmp(right));
575        }
576        #[cfg(feature = "big-integer")]
577        if let Some(ordering) = self.compare_big_integer_fixed(right) {
578            return Some(ordering);
579        }
580        #[cfg(feature = "big-integer")]
581        if let Some(ordering) = self.compare_big_integer_float(right) {
582            return Some(ordering);
583        }
584        #[cfg(feature = "big-decimal")]
585        if let (NumberRepr::BigDecimal(left), NumberRepr::BigDecimal(right)) = (self.inner, right.inner) {
586            return Some(left.cmp(right));
587        }
588        match (self.to_exact_rational(), right.to_exact_rational()) {
589            (Some(left), Some(right)) => Some(left.cmp(&right)),
590            #[cfg(feature = "big-decimal")]
591            _ => self.compare_exact_decimal(right),
592            #[cfg(not(feature = "big-decimal"))]
593            _ => None,
594        }
595    }
596
597    /// Compares a `BigInt` directly with a fixed-width integer.
598    ///
599    /// # Parameters
600    ///
601    /// * `right` - Right numeric operand.
602    ///
603    /// # Returns
604    ///
605    /// The ordering when the pair contains a `BigInt` and no floating-point
606    /// or decimal value; otherwise, `None`.
607    #[cfg(feature = "big-integer")]
608    #[inline]
609    fn compare_big_integer_fixed(self, right: NumberRef<'_>) -> Option<Ordering> {
610        match (self.inner, right.inner) {
611            (NumberRepr::BigInteger(left), right) => compare_big_integer_to_fixed(left, right),
612            (left, NumberRepr::BigInteger(right)) => compare_big_integer_to_fixed(right, left).map(Ordering::reverse),
613            _ => None,
614        }
615    }
616
617    /// Compares a `BigInt` directly with a finite floating-point value when
618    /// zero, sign, or highest-bit ordering determines the result.
619    ///
620    /// # Parameters
621    ///
622    /// * `right` - Right numeric operand.
623    #[cfg(feature = "big-integer")]
624    #[inline]
625    fn compare_big_integer_float(self, right: NumberRef<'_>) -> Option<Ordering> {
626        match (self.inner, right.inner) {
627            (NumberRepr::BigInteger(left), NumberRepr::Float32(_) | NumberRepr::Float64(_)) => {
628                compare_big_integer_float(left, right)
629            }
630            (NumberRepr::Float32(_) | NumberRepr::Float64(_), NumberRepr::BigInteger(right)) => {
631                compare_big_integer_float(right, self).map(Ordering::reverse)
632            }
633            _ => None,
634        }
635    }
636
637    /// Compares values through exact arbitrary-precision decimal
638    /// representations.
639    ///
640    /// # Parameters
641    ///
642    /// * `right` - Right numeric operand.
643    ///
644    /// # Returns
645    ///
646    /// Their mathematical ordering, or `None` when either operand is
647    /// non-finite.
648    #[cfg(feature = "big-decimal")]
649    #[inline]
650    fn compare_exact_decimal(self, right: NumberRef<'_>) -> Option<Ordering> {
651        Some(self.to_exact_decimal()?.cmp(&right.to_exact_decimal()?))
652    }
653
654    /// Compares two finite fixed-width numeric values exactly.
655    ///
656    /// # Parameters
657    ///
658    /// * `right` - Right fixed-width operand.
659    ///
660    /// # Returns
661    ///
662    /// Their mathematical ordering, or `None` for a non-finite or unsupported
663    /// input.
664    fn compare_fixed(self, right: NumberRef<'_>) -> Option<Ordering> {
665        let (left_negative, left_significand, left_exponent) = finite_parts(self)?;
666        let (right_negative, right_significand, right_exponent) = finite_parts(right)?;
667        if left_significand == 0 && right_significand == 0 {
668            return Some(Ordering::Equal);
669        }
670        match left_negative.cmp(&right_negative) {
671            Ordering::Less => Some(Ordering::Greater),
672            Ordering::Greater => Some(Ordering::Less),
673            Ordering::Equal => {
674                let ordering = compare_magnitude(left_significand, left_exponent, right_significand, right_exponent);
675                Some(if left_negative { ordering.reverse() } else { ordering })
676            }
677        }
678    }
679
680    /// Projects this value to `f64` for approximate comparison.
681    ///
682    /// Fixed-width integers and primitive floats convert by widening or
683    /// casting. Arbitrary-precision values use `ToPrimitive::to_f64`, which
684    /// may lose precision or return `None` when the magnitude cannot be
685    /// represented or a decimal scale cannot be safely projected.
686    ///
687    /// # Returns
688    ///
689    /// The approximate `f64` projection, or `None` when an arbitrary-precision
690    /// value cannot be represented as `f64` or a decimal scale cannot be
691    /// safely projected.
692    fn to_approximate_f64(self) -> Option<f64> {
693        match self.inner {
694            NumberRepr::Int8(value) => Some(f64::from(value)),
695            NumberRepr::Int16(value) => Some(f64::from(value)),
696            NumberRepr::Int32(value) => Some(f64::from(value)),
697            NumberRepr::Int64(value) => Some(value as f64),
698            NumberRepr::Int128(value) => Some(value as f64),
699            NumberRepr::UInt8(value) => Some(f64::from(value)),
700            NumberRepr::UInt16(value) => Some(f64::from(value)),
701            NumberRepr::UInt32(value) => Some(f64::from(value)),
702            NumberRepr::UInt64(value) => Some(value as f64),
703            NumberRepr::UInt128(value) => Some(value as f64),
704            NumberRepr::Float32(value) => Some(f64::from(value)),
705            NumberRepr::Float64(value) => Some(value),
706            #[cfg(feature = "big-integer")]
707            NumberRepr::BigInteger(value) => value.to_f64(),
708            #[cfg(feature = "big-decimal")]
709            NumberRepr::BigDecimal(value) => {
710                let (coefficient, scale) = value.as_bigint_and_scale();
711                // The coefficient bit count bounds the scale reduction conservatively.
712                let max_scale_reduction = i64::try_from(coefficient.bits()).ok()?;
713                scale.checked_sub(max_scale_reduction)?;
714                value.to_f64()
715            }
716            #[cfg(not(any(feature = "big-integer", feature = "big-decimal")))]
717            NumberRepr::Lifetime(_, impossible) => match impossible {},
718        }
719    }
720
721    /// Converts this finite numeric value into an exact rational.
722    ///
723    /// # Returns
724    ///
725    /// The exact mathematical value, or `None` for non-finite values or an
726    /// impractically large decimal scale.
727    #[cfg(any(feature = "big-integer", feature = "big-decimal"))]
728    fn to_exact_rational(self) -> Option<BigRational> {
729        match self.inner {
730            NumberRepr::Int8(value) => Some(BigRational::from_integer(BigInt::from(value))),
731            NumberRepr::Int16(value) => Some(BigRational::from_integer(BigInt::from(value))),
732            NumberRepr::Int32(value) => Some(BigRational::from_integer(BigInt::from(value))),
733            NumberRepr::Int64(value) => Some(BigRational::from_integer(BigInt::from(value))),
734            NumberRepr::Int128(value) => Some(BigRational::from_integer(BigInt::from(value))),
735            NumberRepr::UInt8(value) => Some(BigRational::from_integer(BigInt::from(value))),
736            NumberRepr::UInt16(value) => Some(BigRational::from_integer(BigInt::from(value))),
737            NumberRepr::UInt32(value) => Some(BigRational::from_integer(BigInt::from(value))),
738            NumberRepr::UInt64(value) => Some(BigRational::from_integer(BigInt::from(value))),
739            NumberRepr::UInt128(value) => Some(BigRational::from_integer(BigInt::from(value))),
740            NumberRepr::Float32(value) => Some(f32_rational(value)),
741            NumberRepr::Float64(value) => Some(f64_rational(value)),
742            #[cfg(feature = "big-integer")]
743            NumberRepr::BigInteger(value) => Some(BigRational::from_integer(value.clone())),
744            #[cfg(feature = "big-decimal")]
745            NumberRepr::BigDecimal(value) => decimal_rational(value),
746        }
747    }
748
749    /// Converts this finite numeric value into an exact arbitrary-precision
750    /// decimal.
751    ///
752    /// `BigDecimal`'s primitive-float conversion decodes the IEEE significand
753    /// and exponent exactly; it does not use display formatting.
754    ///
755    /// # Returns
756    ///
757    /// The exact decimal value, or `None` for a non-finite primitive float.
758    #[cfg(feature = "big-decimal")]
759    fn to_exact_decimal(self) -> Option<BigDecimal> {
760        match self.inner {
761            NumberRepr::Int8(value) => Some(BigDecimal::from(value)),
762            NumberRepr::Int16(value) => Some(BigDecimal::from(value)),
763            NumberRepr::Int32(value) => Some(BigDecimal::from(value)),
764            NumberRepr::Int64(value) => Some(BigDecimal::from(value)),
765            NumberRepr::Int128(value) => Some(BigDecimal::from(value)),
766            NumberRepr::UInt8(value) => Some(BigDecimal::from(value)),
767            NumberRepr::UInt16(value) => Some(BigDecimal::from(value)),
768            NumberRepr::UInt32(value) => Some(BigDecimal::from(value)),
769            NumberRepr::UInt64(value) => Some(BigDecimal::from(value)),
770            NumberRepr::UInt128(value) => Some(BigDecimal::from(value)),
771            NumberRepr::Float32(value) => BigDecimal::try_from(value).ok(),
772            NumberRepr::Float64(value) => BigDecimal::try_from(value).ok(),
773            #[cfg(feature = "big-integer")]
774            NumberRepr::BigInteger(value) => Some(BigDecimal::from(value.clone())),
775            NumberRepr::BigDecimal(value) => Some(value.clone()),
776        }
777    }
778}