brine_fp/
unsigned.rs

1use super::consts::*;
2use super::signed::SignedNumeric;
3use super::InnerUint;
4use core::convert::*;
5use core::ops::{Add, Sub, Mul, Div};
6
7#[cfg(not(feature = "std"))]
8use alloc::{format, string::String};
9
10// Based on the following implementations:
11// https://github.com/solana-labs/solana-program-library/blob/v2.0/libraries/math/src/precise_number.rs
12// https://github.com/StrataFoundation/strata/blob/master/programs/spl-token-bonding/src/precise_number.rs
13
14/// A `UnsignedNumeric` is an unsigned 192-bit fixed-point number with 18 decimal places of
15/// precision.
16///
17/// ### Internal Representation
18/// Internally, the value is stored as a [`InnerUint`], which wraps a little-endian array `[u64; 3]`.
19/// This means the layout is:
20///
21/// ```text
22/// InnerUint([lo, mid, hi])
23/// // equivalent to:
24/// // value = lo + (mid << 64) + (hi << 128)
25/// ```
26///
27/// Each component contributes to the full 192-bit value:
28///
29/// ```text
30/// value = (hi × 2^128) + (mid × 2^64) + lo
31/// ```
32///
33/// ### Fixed-Point Scaling
34/// All values are scaled by [`ONE`] (10^18). That is, the internal number is interpreted
35/// as `raw / ONE` to recover its real-world value.
36///
37/// Examples:
38/// - `InnerUint([1_000_000_000_000_000_000, 0, 0])` → 1.0
39/// - `InnerUint([500_000_000_000_000_000, 0, 0])` → 0.5
40/// - `InnerUint([2_000_000_000_000_000_000, 0, 0])` → 2.0
41///
42/// ### Example: High-Bit Usage
43///
44/// When you write:
45/// ```text
46/// let a = UnsignedNumeric::from([0, 0, 1]);
47/// ```
48/// This initializes the internal 192-bit value with the array `[0, 0, 1]`.
49/// In this representation:
50///
51/// - `0` is the least significant 64 bits (`lo`)
52/// - `0` is the middle 64 bits (`mid`)
53/// - `1` is the most significant 64 bits (`hi`)
54///
55/// The actual 192-bit value is computed as:
56///
57/// ```text
58/// value = (1 × 2^128) + (0 × 2^64) + 0 = 2^128
59///       = 340282366920938463463374607431768211456
60/// ```
61///
62/// Since this is a fixed-point number, the real-world value is:
63///
64/// ```text
65/// real_value = value / 10^18 = 340282366920938463463.374607431768211456
66/// ```
67///
68/// This system allows for both extremely high precision and a vast dynamic range,
69/// making [`UnsignedNumeric`] ideal for financial, scientific, or blockchain applications
70/// where `f64` or even `u128` would lose accuracy or overflow.
71#[derive(Clone, Debug, PartialEq)]
72pub struct UnsignedNumeric {
73    /// Internal value stored as a 192-bit integer, scaled by ONE (10^18).
74    pub value: InnerUint,
75}
76
77impl UnsignedNumeric {
78    /// Returns a `UnsignedNumeric` representing 0.0.
79    pub fn zero() -> Self {
80        Self { value: zero() }
81    }
82
83    /// Returns a `UnsignedNumeric` representing 1.0.
84    pub fn one() -> Self {
85        Self { value: one() }
86    }
87
88    /// Constructs a `UnsignedNumeric` from an integer value by scaling it by ONE (10^18).
89    /// For example, `new(7)` produces `7.0`.
90    pub fn new(value: u128) -> Self {
91        // `one()` is 10^18 and 10^18 * u128::MAX will always fit within 196 bits
92        let value = InnerUint::from(value).checked_mul(one()).unwrap();
93        Self { value }
94    }
95
96    /// Constructs a `UnsignedNumeric` from a `u128` that is already scaled by ONE (i.e. in fixed-point space).
97    /// This bypasses internal multiplication and is useful for constants or pre-scaled data.
98    pub fn from_scaled_u128(value: u128) -> Self {
99        Self {
100            value: InnerUint::from(value),
101        }
102    }
103
104    /// Constructs a `UnsignedNumeric` directly from a raw `[u64; 3]` value.
105    /// The input is interpreted as already scaled (fixed-point).
106    /// Layout is little-endian: `[lo, mid, hi]` = `lo + (mid << 64) + (hi << 128)`.
107    pub fn from_values(lo: u64, mid: u64, hi: u64) -> Self {
108        Self {
109            value: InnerUint([lo, mid, hi]),
110        }
111    }
112
113    /// Converts this `UnsignedNumeric` into a raw `[u8; 24]` representation.
114    pub fn to_bytes(&self) -> [u8; 24] {
115        let InnerUint([lo, mid, hi]) = self.value;
116
117        let mut bytes = [0u8; 24];
118        bytes[0..8].copy_from_slice(&lo.to_le_bytes());
119        bytes[8..16].copy_from_slice(&mid.to_le_bytes());
120        bytes[16..24].copy_from_slice(&hi.to_le_bytes());
121
122        bytes
123    }
124
125    /// Converts a raw `[u8; 24]` representation into a `UnsignedNumeric`.
126    pub fn from_bytes(bytes: &[u8; 24]) -> Self {
127        let lo = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
128        let mid = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
129        let hi = u64::from_le_bytes(bytes[16..24].try_into().unwrap());
130
131        Self {
132            value: InnerUint([lo, mid, hi]),
133        }
134    }
135
136    /// Converts this `UnsignedNumeric` into a regular `u128` by dividing by ONE.
137    /// Applies rounding correction to avoid always flooring the result.
138    /// Returns `None` if the division would overflow or the result exceeds `u128::MAX`.
139    pub fn to_imprecise(&self) -> Option<u128> {
140        self.value
141            .checked_add(Self::rounding_correction())?
142            .checked_div(one())
143            .map(|v| v.as_u128())
144    }
145
146    /// Converts this `UnsignedNumeric` into a signed version,
147    /// wrapping it in a `SignedNumeric` with `is_negative = false`.
148    /// Useful when beginning arithmetic that may result in negative values.
149    pub fn signed(&self) -> SignedNumeric {
150        SignedNumeric {
151            value: self.clone(),
152            is_negative: false,
153        }
154    }
155
156    /// Returns a rounding correction (0.5) used in division/multiplication
157    /// to mitigate truncation from integer floor behavior.
158    /// This simulates "round-to-nearest" by adding half the divisor.
159    fn rounding_correction() -> InnerUint {
160        InnerUint::from(ONE / 2)
161    }
162
163    /// Compares two `UnsignedNumeric`s for approximate equality,
164    /// allowing for a configurable `precision` window.
165    pub fn almost_eq(&self, rhs: &Self, precision: InnerUint) -> bool {
166        let (difference, _) = self.unsigned_sub(rhs);
167        difference.value < precision
168    }
169
170    /// Returns `true` if `self < rhs` in fixed-point terms.
171    pub fn less_than(&self, rhs: &Self) -> bool {
172        self.value < rhs.value
173    }
174
175    /// Returns `true` if `self > rhs`.
176    pub fn greater_than(&self, rhs: &Self) -> bool {
177        self.value > rhs.value
178    }
179
180    /// Returns `true` if `self <= rhs`.
181    pub fn less_than_or_equal(&self, rhs: &Self) -> bool {
182        self.value <= rhs.value
183    }
184
185    /// Returns `true` if `self >= rhs`.
186    pub fn greater_than_or_equal(&self, rhs: &Self) -> bool {
187        self.value >= rhs.value
188    }
189
190    /// Rounds down to the nearest whole number by truncating fractional digits.
191    pub fn floor(&self) -> Option<Self> {
192        let value = self.value.checked_div(one())?.checked_mul(one())?;
193        Some(Self { value })
194    }
195
196    /// Rounds up to the nearest whole number.
197    pub fn ceiling(&self) -> Option<Self> {
198        let value = self
199            .value
200            .checked_add(one().checked_sub(InnerUint::from(1))?)?
201            .checked_div(one())?
202            .checked_mul(one())?;
203        Some(Self { value })
204    }
205
206    /// Divides `self / rhs` in fixed-point space, maintaining precision.
207    /// Applies rounding correction to minimize truncation error.
208    /// Returns `None` on divide-by-zero or overflow.
209    pub fn checked_div(&self, rhs: &Self) -> Option<Self> {
210        if *rhs == Self::zero() {
211            return None;
212        }
213        match self.value.checked_mul(one()) {
214            Some(v) => {
215                let value = v
216                    .checked_add(Self::rounding_correction())?
217                    .checked_div(rhs.value)?;
218                Some(Self { value })
219            }
220            None => {
221                let value = self
222                    .value
223                    .checked_add(Self::rounding_correction())?
224                    .checked_div(rhs.value)?
225                    .checked_mul(one())?;
226                Some(Self { value })
227            }
228        }
229    }
230
231    /// Multiplies two `UnsignedNumeric`s and returns the result in fixed-point space.
232    /// Automatically divides by ONE to maintain correct scaling, and applies rounding correction.
233    /// Falls back to a reduced-precision path if full multiplication would overflow.
234    pub fn checked_mul(&self, rhs: &Self) -> Option<Self> {
235        match self.value.checked_mul(rhs.value) {
236            Some(v) => {
237                let value = v
238                    .checked_add(Self::rounding_correction())?
239                    .checked_div(one())?;
240                Some(Self { value })
241            }
242            None => {
243                let value = if self.value >= rhs.value {
244                    self.value.checked_div(one())?.checked_mul(rhs.value)?
245                } else {
246                    rhs.value.checked_div(one())?.checked_mul(self.value)?
247                };
248                Some(Self { value })
249            }
250        }
251    }
252
253    /// Adds two precise numbers. Returns `None` on overflow.
254    pub fn checked_add(&self, rhs: &Self) -> Option<Self> {
255        let value = self.value.checked_add(rhs.value)?;
256        Some(Self { value })
257    }
258
259    /// Subtracts `rhs` from `self`. Returns `None` if the result would be negative.
260    pub fn checked_sub(&self, rhs: &Self) -> Option<Self> {
261        let value = self.value.checked_sub(rhs.value)?;
262        Some(Self { value })
263    }
264
265    /// Computes the absolute difference between two numbers.
266    /// Returns the result and a boolean indicating whether the result was originally negative.
267    pub fn unsigned_sub(&self, rhs: &Self) -> (Self, bool) {
268        match self.value.checked_sub(rhs.value) {
269            None => {
270                let value = rhs.value.checked_sub(self.value).unwrap();
271                (Self { value }, true)
272            }
273            Some(value) => (Self { value }, false),
274        }
275    }
276
277    /// Converts the precise number into a human-readable decimal string with full 18-digit precision.
278    ///
279    /// For example, a number representing 3.1415 will be displayed as:
280    /// `"3.141500000000000000"`
281    pub fn to_string(&self) -> String {
282        let whole = self.floor().unwrap().to_imprecise().unwrap();
283        let decimals = self
284            .checked_sub(&UnsignedNumeric::new(whole))
285            .unwrap()
286            .checked_mul(&UnsignedNumeric::new(ONE))
287            .unwrap()
288            .to_imprecise()
289            .unwrap();
290        format!("{}.{:0>width$}", whole, decimals, width = 18)
291    }
292}
293
294// Standard arithmetic trait implementations
295impl Add for UnsignedNumeric {
296    type Output = Self;
297
298    fn add(self, rhs: Self) -> Self::Output {
299        self.checked_add(&rhs).unwrap()
300    }
301}
302
303impl Add<&UnsignedNumeric> for UnsignedNumeric {
304    type Output = Self;
305
306    fn add(self, rhs: &Self) -> Self::Output {
307        self.checked_add(rhs).unwrap()
308    }
309}
310
311impl Add<UnsignedNumeric> for &UnsignedNumeric {
312    type Output = UnsignedNumeric;
313
314    fn add(self, rhs: UnsignedNumeric) -> Self::Output {
315        self.checked_add(&rhs).unwrap()
316    }
317}
318
319impl Add<&UnsignedNumeric> for &UnsignedNumeric {
320    type Output = UnsignedNumeric;
321
322    fn add(self, rhs: &UnsignedNumeric) -> Self::Output {
323        self.checked_add(rhs).unwrap()
324    }
325}
326
327impl Sub for UnsignedNumeric {
328    type Output = Self;
329
330    fn sub(self, rhs: Self) -> Self::Output {
331        self.checked_sub(&rhs).unwrap()
332    }
333}
334
335impl Sub<&UnsignedNumeric> for UnsignedNumeric {
336    type Output = Self;
337
338    fn sub(self, rhs: &Self) -> Self::Output {
339        self.checked_sub(rhs).unwrap()
340    }
341}
342
343impl Sub<UnsignedNumeric> for &UnsignedNumeric {
344    type Output = UnsignedNumeric;
345
346    fn sub(self, rhs: UnsignedNumeric) -> Self::Output {
347        self.checked_sub(&rhs).unwrap()
348    }
349}
350
351impl Sub<&UnsignedNumeric> for &UnsignedNumeric {
352    type Output = UnsignedNumeric;
353
354    fn sub(self, rhs: &UnsignedNumeric) -> Self::Output {
355        self.checked_sub(rhs).unwrap()
356    }
357}
358
359impl Mul for UnsignedNumeric {
360    type Output = Self;
361
362    fn mul(self, rhs: Self) -> Self::Output {
363        self.checked_mul(&rhs).unwrap()
364    }
365}
366
367impl Mul<&UnsignedNumeric> for UnsignedNumeric {
368    type Output = Self;
369
370    fn mul(self, rhs: &Self) -> Self::Output {
371        self.checked_mul(rhs).unwrap()
372    }
373}
374
375impl Mul<UnsignedNumeric> for &UnsignedNumeric {
376    type Output = UnsignedNumeric;
377
378    fn mul(self, rhs: UnsignedNumeric) -> Self::Output {
379        self.checked_mul(&rhs).unwrap()
380    }
381}
382
383impl Mul<&UnsignedNumeric> for &UnsignedNumeric {
384    type Output = UnsignedNumeric;
385
386    fn mul(self, rhs: &UnsignedNumeric) -> Self::Output {
387        self.checked_mul(rhs).unwrap()
388    }
389}
390
391impl Div for UnsignedNumeric {
392    type Output = Self;
393
394    fn div(self, rhs: Self) -> Self::Output {
395        self.checked_div(&rhs).unwrap()
396    }
397}
398
399impl Div<&UnsignedNumeric> for UnsignedNumeric {
400    type Output = Self;
401
402    fn div(self, rhs: &Self) -> Self::Output {
403        self.checked_div(rhs).unwrap()
404    }
405}
406
407impl Div<UnsignedNumeric> for &UnsignedNumeric {
408    type Output = UnsignedNumeric;
409
410    fn div(self, rhs: UnsignedNumeric) -> Self::Output {
411        self.checked_div(&rhs).unwrap()
412    }
413}
414
415impl Div<&UnsignedNumeric> for &UnsignedNumeric {
416    type Output = UnsignedNumeric;
417
418    fn div(self, rhs: &UnsignedNumeric) -> Self::Output {
419        self.checked_div(rhs).unwrap()
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    fn max_numeric() -> UnsignedNumeric {
428        UnsignedNumeric {
429            value: InnerUint::MAX,
430        }
431    }
432
433    #[test]
434    fn test_zero_and_one() {
435        assert_eq!(UnsignedNumeric::zero().value, InnerUint::from(0));
436        assert_eq!(UnsignedNumeric::one().value, InnerUint::from(ONE));
437    }
438
439    #[test]
440    fn test_from_scaled_u128() {
441        let n = UnsignedNumeric::from_scaled_u128(42 * ONE);
442        assert_eq!(n.to_imprecise().unwrap(), 42);
443    }
444
445    #[test]
446    fn test_to_string_exact() {
447        let n = UnsignedNumeric::new(3);
448        assert_eq!(n.to_string(), "3.000000000000000000");
449    }
450
451    #[test]
452    fn test_to_string_fractional() {
453        let mut n = UnsignedNumeric::new(3);
454        n.value += InnerUint::from(250_000_000_000_000_000u128); // +0.25
455        assert_eq!(n.to_string(), "3.250000000000000000");
456    }
457
458    #[test]
459    fn test_checked_add() {
460        let a = UnsignedNumeric::new(1);
461        let b = UnsignedNumeric::new(2);
462        let sum = a.checked_add(&b).unwrap();
463        assert_eq!(sum.to_imprecise().unwrap(), 3);
464    }
465
466    #[test]
467    fn test_checked_sub_underflow() {
468        let a = UnsignedNumeric::new(1);
469        let b = UnsignedNumeric::new(2);
470        assert!(a.checked_sub(&b).is_none());
471    }
472
473    #[test]
474    fn test_checked_sub_valid() {
475        let a = UnsignedNumeric::new(3);
476        let b = UnsignedNumeric::new(1);
477        let result = a.checked_sub(&b).unwrap();
478        assert_eq!(result.to_imprecise().unwrap(), 2);
479    }
480
481    #[test]
482    fn test_checked_mul_simple() {
483        let a = UnsignedNumeric::new(2);
484        let b = UnsignedNumeric::new(3);
485        let product = a.checked_mul(&b).unwrap();
486        assert_eq!(product.to_imprecise().unwrap(), 6);
487    }
488
489    #[test]
490    fn test_checked_div_exact() {
491        let a = UnsignedNumeric::new(6);
492        let b = UnsignedNumeric::new(2);
493        let result = a.checked_div(&b).unwrap();
494        assert_eq!(result.to_imprecise().unwrap(), 3);
495    }
496
497    #[test]
498    fn test_checked_div_rounded() {
499        let a = UnsignedNumeric::new(1);
500        let b = UnsignedNumeric::new(3); // 1 / 3
501
502        let result = a.checked_div(&b).unwrap();
503        let expected = UnsignedNumeric::from_scaled_u128(333_333_333_333_333_333); // ~0.333...
504
505        assert!(result.almost_eq(&expected, InnerUint::from(1_000_000)));
506    }
507
508    #[test]
509    fn test_unsigned_sub_positive() {
510        let a = UnsignedNumeric::new(7);
511        let b = UnsignedNumeric::new(3);
512        let (result, is_negative) = a.unsigned_sub(&b);
513        assert_eq!(result.to_imprecise().unwrap(), 4);
514        assert!(!is_negative);
515    }
516
517    #[test]
518    fn test_unsigned_sub_negative() {
519        let a = UnsignedNumeric::new(3);
520        let b = UnsignedNumeric::new(7);
521        let (result, is_negative) = a.unsigned_sub(&b);
522        assert_eq!(result.to_imprecise().unwrap(), 4);
523        assert!(is_negative);
524    }
525
526    #[test]
527    fn test_almost_eq_within_precision() {
528        let a = UnsignedNumeric::new(5);
529        let mut b = a.clone();
530        b.value += InnerUint::from(10); // Very slight difference
531
532        assert!(a.almost_eq(&b, InnerUint::from(20)));
533        assert!(!a.almost_eq(&b, InnerUint::from(5)));
534    }
535
536    #[test]
537    fn test_comparisons() {
538        let a = UnsignedNumeric::new(1);
539        let b = UnsignedNumeric::new(2);
540        assert!(a.less_than(&b));
541        assert!(b.greater_than(&a));
542        assert!(a.less_than_or_equal(&b));
543        assert!(b.greater_than_or_equal(&a));
544        assert!(a.less_than_or_equal(&a));
545        assert!(a.greater_than_or_equal(&a));
546    }
547
548    #[test]
549    fn test_signed_conversion() {
550        let u = UnsignedNumeric::new(42);
551        let s = u.signed();
552        assert_eq!(s.value, u);
553        assert!(!s.is_negative);
554    }
555
556    #[test]
557    fn test_serialization() {
558        let original = UnsignedNumeric::from_values(123, 456, 789);
559        let bytes = original.to_bytes();
560        let recovered = UnsignedNumeric::from_bytes(&bytes);
561
562        assert_eq!(original, recovered);
563    }
564
565    #[test]
566    fn test_floor() {
567        let whole_number = UnsignedNumeric::new(2);
568        let mut decimal_number = UnsignedNumeric::new(2);
569        decimal_number.value += InnerUint::from(1);
570        let floor = decimal_number.floor().unwrap();
571        let floor_again = floor.floor().unwrap();
572        assert_eq!(whole_number.value, floor.value);
573        assert_eq!(whole_number.value, floor_again.value);
574    }
575
576    #[test]
577    fn test_ceiling() {
578        let whole_number = UnsignedNumeric::new(2);
579        let mut decimal_number = UnsignedNumeric::new(2);
580        decimal_number.value -= InnerUint::from(1);
581        let ceiling = decimal_number.ceiling().unwrap();
582        let ceiling_again = ceiling.ceiling().unwrap();
583        assert_eq!(whole_number.value, ceiling.value);
584        assert_eq!(whole_number.value, ceiling_again.value);
585    }
586
587    #[test]
588    fn test_add_overflow() {
589        let a = max_numeric();
590        let b = UnsignedNumeric::one();
591        assert!(a.checked_add(&b).is_none(), "Expected overflow on add");
592    }
593
594    #[test]
595    fn test_mul_overflow() {
596        let a = max_numeric();
597        let b = UnsignedNumeric::new(2); // 2.0 in scaled form
598
599        let result = a.checked_mul(&b);
600        assert!(result.is_none(), "Expected overflow on multiply");
601    }
602
603    #[test]
604    fn test_mul_fallback_path() {
605        let a = UnsignedNumeric::new(1_000_000_000_000u128); // large-ish value
606        let b = UnsignedNumeric::new(2);
607
608        let result = a.checked_mul(&b).unwrap();
609        assert_eq!(result.to_imprecise().unwrap(), 2_000_000_000_000);
610    }
611
612    #[test]
613    fn test_mul_large_by_large_overflow() {
614        let a = UnsignedNumeric {
615            value: InnerUint([0, 0, 1]), // 2^128
616        };
617        let b = a.clone(); // 2^128 * 2^128 = 2^256, definitely too large
618
619        let result = a.checked_mul(&b);
620        assert!(result.is_none(), "Expected overflow on large * large");
621    }
622
623    #[test]
624    fn test_div_by_zero() {
625        let a = UnsignedNumeric::new(42);
626        let zero = UnsignedNumeric::zero();
627
628        assert!(a.checked_div(&zero).is_none(), "Expected div by zero to fail");
629    }
630
631    #[test]
632    fn test_floor_overflow_fallback() {
633        // floor() performs: value / ONE * ONE
634        // If value is MAX, this can overflow
635        let a = max_numeric();
636        let result = a.floor();
637        assert!(result.is_some(), "Should handle overflow in floor safely");
638    }
639
640    #[test]
641    fn test_ceiling_overflow() {
642        // ceiling() performs: (value + ONE - 1) / ONE * ONE
643        // Adding ONE - 1 to MAX will overflow
644        let a = max_numeric();
645        let result = a.ceiling();
646        assert!(result.is_none(), "Expected overflow in ceiling()");
647    }
648
649    #[test]
650    fn test_rounding_correction_addition_overflow() {
651        let a = max_numeric();
652        // This simulates rounding correction inside `checked_div`
653        let corrected = a.value.checked_add(UnsignedNumeric::rounding_correction());
654        assert!(corrected.is_none(), "Expected overflow in rounding correction");
655    }
656
657    #[test]
658    fn test_arithmetic_operators() {
659        let a = UnsignedNumeric::new(5);
660        let b = UnsignedNumeric::new(3);
661        
662        // Test addition
663        let sum = a.clone() + b.clone();
664        assert_eq!(sum.to_imprecise().unwrap(), 8);
665        
666        // Test subtraction
667        let diff = a.clone() - b.clone();
668        assert_eq!(diff.to_imprecise().unwrap(), 2);
669        
670        // Test multiplication
671        let product = a.clone() * b.clone();
672        assert_eq!(product.to_imprecise().unwrap(), 15);
673        
674        // Test division
675        let quotient = a / b;
676        assert!(quotient.almost_eq(&UnsignedNumeric::from_scaled_u128(1_666_666_666_666_666_666), InnerUint::from(1_000_000)));
677    }
678
679    #[test]
680    fn test_arithmetic_operators_with_references() {
681        let a = UnsignedNumeric::new(10);
682        let b = UnsignedNumeric::new(4);
683        
684        // Test with references
685        let sum = &a + &b;
686        assert_eq!(sum.to_imprecise().unwrap(), 14);
687        
688        let diff = &a - &b;
689        assert_eq!(diff.to_imprecise().unwrap(), 6);
690        
691        let product = &a * &b;
692        assert_eq!(product.to_imprecise().unwrap(), 40);
693        
694        let quotient = &a / &b;
695        assert_eq!(quotient.to_imprecise().unwrap(), 3);
696    }
697
698    #[test]
699    fn test_arithmetic_operators_mixed_references() {
700        let a = UnsignedNumeric::new(6);
701        let b = UnsignedNumeric::new(2);
702        
703        // Test mixed reference patterns
704        let sum1 = a.clone() + &b;
705        let sum2 = &a + b.clone();
706        assert_eq!(sum1.to_imprecise().unwrap(), 8);
707        assert_eq!(sum2.to_imprecise().unwrap(), 8);
708        
709        let product1 = a.clone() * &b;
710        let product2 = &a * b.clone();
711        assert_eq!(product1.to_imprecise().unwrap(), 12);
712        assert_eq!(product2.to_imprecise().unwrap(), 12);
713    }
714}