Skip to main content

num_modular/
double.rs

1//! This module implements a double width integer type based on the largest built-in integer (u128)
2//! Part of the optimization comes from `ethnum` and `zkp-u256` crates.
3
4use core::ops::*;
5
6/// Alias of the builtin integer type with max width (currently [u128])
7#[allow(non_camel_case_types)]
8pub type umax = u128;
9
10/// Alias of the builtin signed integer type with max width (currently [i128])
11#[allow(non_camel_case_types)]
12pub type imax = i128;
13
14const HALF_BITS: u32 = umax::BITS / 2;
15
16// Split umax into hi and lo parts. It's critical to use inline here
17#[inline(always)]
18const fn split(v: umax) -> (umax, umax) {
19    (v >> HALF_BITS, v & (umax::MAX >> HALF_BITS))
20}
21
22#[inline(always)]
23const fn div_rem(n: umax, d: umax) -> (umax, umax) {
24    (n / d, n % d)
25}
26
27#[must_use]
28#[allow(non_camel_case_types)]
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
30/// A double width integer type based on the largest built-in integer type [umax] (currently [u128]), and
31/// to support double-width operations on it is the only goal for this type.
32///
33/// Although it can be regarded as u256, it's not as feature-rich as in other crates
34/// since it's only designed to support this crate and few other crates (will be noted in comments).
35pub struct udouble {
36    /// Most significant part
37    pub hi: umax,
38    /// Least significant part
39    pub lo: umax,
40}
41
42impl udouble {
43    pub const MAX: Self = Self {
44        lo: umax::MAX,
45        hi: umax::MAX,
46    };
47
48    //> (used in u128::addm)
49    #[inline]
50    pub const fn widening_add(lhs: umax, rhs: umax) -> Self {
51        let (sum, carry) = lhs.overflowing_add(rhs);
52        udouble {
53            hi: carry as umax,
54            lo: sum,
55        }
56    }
57
58    /// Calculate multiplication of two [umax] integers with result represented in double width integer
59    // equivalent to umul_ppmm, can be implemented efficiently with carrying_mul and widening_mul implemented (rust#85532)
60    //> (used in u128::mulm, MersenneInt, Montgomery::<u128>::{reduce, mul}, num-order::NumHash)
61    #[inline]
62    pub const fn widening_mul(lhs: umax, rhs: umax) -> Self {
63        let ((x1, x0), (y1, y0)) = (split(lhs), split(rhs));
64
65        let z2 = x1 * y1;
66        let (c0, z0) = split(x0 * y0); // c0 <= umax::MAX - 1
67        let (c1, z1) = split(x1 * y0 + c0);
68        let z2 = z2 + c1;
69        let (c1, z1) = split(x0 * y1 + z1);
70        Self {
71            hi: z2 + c1,
72            lo: z0 | z1 << HALF_BITS,
73        }
74    }
75
76    /// Optimized squaring function for [umax] integers
77    //> (used in Montgomery::<u128>::{square})
78    #[inline]
79    pub const fn widening_square(x: umax) -> Self {
80        // the algorithm here is basically the same as widening_mul
81        let (x1, x0) = split(x);
82
83        let z2 = x1 * x1;
84        let m = x1 * x0;
85        let (c0, z0) = split(x0 * x0);
86        let (c1, z1) = split(m + c0);
87        let z2 = z2 + c1;
88        let (c1, z1) = split(m + z1);
89        Self {
90            hi: z2 + c1,
91            lo: z0 | z1 << HALF_BITS,
92        }
93    }
94
95    //> (used in Montgomery::<u128>::reduce)
96    #[inline]
97    pub const fn overflowing_add(&self, rhs: Self) -> (Self, bool) {
98        let (lo, carry) = self.lo.overflowing_add(rhs.lo);
99        let (hi, of1) = self.hi.overflowing_add(rhs.hi);
100        let (hi, of2) = hi.overflowing_add(carry as umax);
101        (Self { lo, hi }, of1 || of2)
102    }
103
104    // double by double multiplication, listed here in case of future use
105    #[allow(dead_code)]
106    fn overflowing_mul(&self, rhs: Self) -> (Self, bool) {
107        let c2 = self.hi != 0 && rhs.hi != 0;
108        let Self { lo: z0, hi: c0 } = Self::widening_mul(self.lo, rhs.lo);
109        let (z1x, c1x) = umax::overflowing_mul(self.lo, rhs.hi);
110        let (z1y, c1y) = umax::overflowing_mul(self.hi, rhs.lo);
111        let (z1z, c1z) = umax::overflowing_add(z1x, z1y);
112        let (z1, c1) = z1z.overflowing_add(c0);
113        (Self { hi: z1, lo: z0 }, c1x | c1y | c1z | c1 | c2)
114    }
115
116    /// Multiplication of double width and single width
117    //> (used in num-order:NumHash)
118    #[inline]
119    pub const fn overflowing_mul1(&self, rhs: umax) -> (Self, bool) {
120        let Self { lo: z0, hi: c0 } = Self::widening_mul(self.lo, rhs);
121        let (z1, c1) = self.hi.overflowing_mul(rhs);
122        let (z1, cs1) = z1.overflowing_add(c0);
123        (Self { hi: z1, lo: z0 }, c1 | cs1)
124    }
125
126    /// Multiplication of double width and single width
127    //> (used in Self::mul::<umax>)
128    #[must_use]
129    #[inline]
130    pub fn checked_mul1(&self, rhs: umax) -> Option<Self> {
131        let Self { lo: z0, hi: c0 } = Self::widening_mul(self.lo, rhs);
132        let z1 = self.hi.checked_mul(rhs)?.checked_add(c0)?;
133        Some(Self { hi: z1, lo: z0 })
134    }
135
136    //> (used in num-order::NumHash)
137    #[must_use]
138    #[inline]
139    pub fn checked_shl(self, rhs: u32) -> Option<Self> {
140        if rhs < umax::BITS * 2 {
141            Some(self << rhs)
142        } else {
143            None
144        }
145    }
146
147    //> (not used yet)
148    #[must_use]
149    #[inline]
150    pub fn checked_shr(self, rhs: u32) -> Option<Self> {
151        if rhs < umax::BITS * 2 {
152            Some(self >> rhs)
153        } else {
154            None
155        }
156    }
157
158    /// Const-compatible left shift (replaces `Shl<u32>` in const contexts).
159    #[inline]
160    pub const fn shl_u32(self, rhs: u32) -> Self {
161        match rhs {
162            0 => self,
163            s if s >= umax::BITS => Self {
164                hi: self.lo << (s - umax::BITS),
165                lo: 0,
166            },
167            s => Self {
168                lo: self.lo << s,
169                hi: (self.hi << s) | (self.lo >> (umax::BITS - s)),
170            },
171        }
172    }
173
174    /// Const-compatible right shift (replaces `Shr<u32>` in const contexts).
175    #[inline]
176    pub const fn shr_u32(self, rhs: u32) -> Self {
177        match rhs {
178            0 => self,
179            s if s >= umax::BITS => Self {
180                lo: self.hi >> (rhs - umax::BITS),
181                hi: 0,
182            },
183            s => Self {
184                hi: self.hi >> s,
185                lo: (self.lo >> s) | (self.hi << (umax::BITS - s)),
186            },
187        }
188    }
189}
190
191impl From<umax> for udouble {
192    #[inline]
193    fn from(v: umax) -> Self {
194        Self { lo: v, hi: 0 }
195    }
196}
197
198impl Add for udouble {
199    type Output = udouble;
200
201    // equivalent to add_ssaaaa
202    #[inline]
203    fn add(self, rhs: Self) -> Self::Output {
204        let (lo, carry) = self.lo.overflowing_add(rhs.lo);
205        let (hi_mid, overflow1) = self.hi.overflowing_add(rhs.hi);
206        let (hi, overflow2) = hi_mid.overflowing_add(umax::from(carry));
207        debug_assert!(
208            !(overflow1 || overflow2),
209            "udouble addition overflowed 256-bit width"
210        );
211        Self { lo, hi }
212    }
213}
214//> (used in Self::div_rem)
215impl Add<umax> for udouble {
216    type Output = udouble;
217    #[inline]
218    fn add(self, rhs: umax) -> Self::Output {
219        let (lo, carry) = self.lo.overflowing_add(rhs);
220        let hi = if carry { self.hi + 1 } else { self.hi };
221        Self { lo, hi }
222    }
223}
224impl AddAssign for udouble {
225    #[inline]
226    fn add_assign(&mut self, rhs: Self) {
227        let (lo, carry) = self.lo.overflowing_add(rhs.lo);
228        let (hi_mid, overflow1) = self.hi.overflowing_add(rhs.hi);
229        let (hi, overflow2) = hi_mid.overflowing_add(umax::from(carry));
230        debug_assert!(
231            !(overflow1 || overflow2),
232            "udouble addition overflowed 256-bit width"
233        );
234        self.lo = lo;
235        self.hi = hi;
236    }
237}
238impl AddAssign<umax> for udouble {
239    #[inline]
240    fn add_assign(&mut self, rhs: umax) {
241        let (lo, carry) = self.lo.overflowing_add(rhs);
242        self.lo = lo;
243        if carry {
244            self.hi += 1
245        }
246    }
247}
248
249//> (used in test of Add)
250impl Sub for udouble {
251    type Output = Self;
252    #[inline]
253    fn sub(self, rhs: Self) -> Self::Output {
254        let carry = self.lo < rhs.lo;
255        let lo = self.lo.wrapping_sub(rhs.lo);
256        let (hi_mid, overflow1) = self.hi.overflowing_sub(rhs.hi);
257        let (hi, overflow2) = hi_mid.overflowing_sub(umax::from(carry));
258        debug_assert!(
259            !(overflow1 || overflow2),
260            "udouble subtraction underflowed 256-bit width"
261        );
262        Self { lo, hi }
263    }
264}
265impl Sub<umax> for udouble {
266    type Output = Self;
267    #[inline]
268    fn sub(self, rhs: umax) -> Self::Output {
269        let carry = self.lo < rhs;
270        let lo = self.lo.wrapping_sub(rhs);
271        let hi = if carry { self.hi - 1 } else { self.hi };
272        Self { lo, hi }
273    }
274}
275//> (used in test of AddAssign)
276impl SubAssign for udouble {
277    #[inline]
278    fn sub_assign(&mut self, rhs: Self) {
279        let carry = self.lo < rhs.lo;
280        self.lo = self.lo.wrapping_sub(rhs.lo);
281        let (hi_mid, overflow1) = self.hi.overflowing_sub(rhs.hi);
282        let (hi, overflow2) = hi_mid.overflowing_sub(umax::from(carry));
283        debug_assert!(
284            !(overflow1 || overflow2),
285            "udouble subtraction underflowed 256-bit width"
286        );
287        self.hi = hi;
288    }
289}
290impl SubAssign<umax> for udouble {
291    #[inline]
292    fn sub_assign(&mut self, rhs: umax) {
293        let carry = self.lo < rhs;
294        self.lo = self.lo.wrapping_sub(rhs);
295        if carry {
296            self.hi -= 1;
297        }
298    }
299}
300
301macro_rules! impl_sh_ops {
302    ($t:ty) => {
303        //> (used in Self::checked_shl)
304        impl Shl<$t> for udouble {
305            type Output = Self;
306            #[inline]
307            fn shl(self, rhs: $t) -> Self::Output {
308                match rhs {
309                    0 => self, // avoid shifting by full bits, which is UB
310                    s if s >= umax::BITS as $t => Self {
311                        hi: self.lo << (s - umax::BITS as $t),
312                        lo: 0,
313                    },
314                    s => Self {
315                        lo: self.lo << s,
316                        hi: (self.hi << s) | (self.lo >> (umax::BITS as $t - s)),
317                    },
318                }
319            }
320        }
321        //> (not used yet)
322        impl ShlAssign<$t> for udouble {
323            #[inline]
324            fn shl_assign(&mut self, rhs: $t) {
325                match rhs {
326                    0 => {}
327                    s if s >= umax::BITS as $t => {
328                        self.hi = self.lo << (s - umax::BITS as $t);
329                        self.lo = 0;
330                    }
331                    s => {
332                        self.hi <<= s;
333                        self.hi |= self.lo >> (umax::BITS as $t - s);
334                        self.lo <<= s;
335                    }
336                }
337            }
338        }
339        //> (used in Self::checked_shr)
340        impl Shr<$t> for udouble {
341            type Output = Self;
342            #[inline]
343            fn shr(self, rhs: $t) -> Self::Output {
344                match rhs {
345                    0 => self,
346                    s if s >= umax::BITS as $t => Self {
347                        lo: self.hi >> (rhs - umax::BITS as $t),
348                        hi: 0,
349                    },
350                    s => Self {
351                        hi: self.hi >> s,
352                        lo: (self.lo >> s) | (self.hi << (umax::BITS as $t - s)),
353                    },
354                }
355            }
356        }
357        //> (not used yet)
358        impl ShrAssign<$t> for udouble {
359            #[inline]
360            fn shr_assign(&mut self, rhs: $t) {
361                match rhs {
362                    0 => {}
363                    s if s >= umax::BITS as $t => {
364                        self.lo = self.hi >> (rhs - umax::BITS as $t);
365                        self.hi = 0;
366                    }
367                    s => {
368                        self.lo >>= s;
369                        self.lo |= self.hi << (umax::BITS as $t - s);
370                        self.hi >>= s;
371                    }
372                }
373            }
374        }
375    };
376}
377
378// only implement most useful ones, so that we don't need to optimize so many variants
379impl_sh_ops!(u8);
380impl_sh_ops!(u16);
381impl_sh_ops!(u32);
382
383//> (not used yet)
384impl BitAnd for udouble {
385    type Output = Self;
386    #[inline]
387    fn bitand(self, rhs: Self) -> Self::Output {
388        Self {
389            lo: self.lo & rhs.lo,
390            hi: self.hi & rhs.hi,
391        }
392    }
393}
394//> (not used yet)
395impl BitAndAssign for udouble {
396    #[inline]
397    fn bitand_assign(&mut self, rhs: Self) {
398        self.lo &= rhs.lo;
399        self.hi &= rhs.hi;
400    }
401}
402//> (not used yet)
403impl BitOr for udouble {
404    type Output = Self;
405    #[inline]
406    fn bitor(self, rhs: Self) -> Self::Output {
407        Self {
408            lo: self.lo | rhs.lo,
409            hi: self.hi | rhs.hi,
410        }
411    }
412}
413//> (not used yet)
414impl BitOrAssign for udouble {
415    #[inline]
416    fn bitor_assign(&mut self, rhs: Self) {
417        self.lo |= rhs.lo;
418        self.hi |= rhs.hi;
419    }
420}
421//> (not used yet)
422impl BitXor for udouble {
423    type Output = Self;
424    #[inline]
425    fn bitxor(self, rhs: Self) -> Self::Output {
426        Self {
427            lo: self.lo ^ rhs.lo,
428            hi: self.hi ^ rhs.hi,
429        }
430    }
431}
432//> (not used yet)
433impl BitXorAssign for udouble {
434    #[inline]
435    fn bitxor_assign(&mut self, rhs: Self) {
436        self.lo ^= rhs.lo;
437        self.hi ^= rhs.hi;
438    }
439}
440//> (not used yet)
441impl Not for udouble {
442    type Output = Self;
443    #[inline]
444    fn not(self) -> Self::Output {
445        Self {
446            lo: !self.lo,
447            hi: !self.hi,
448        }
449    }
450}
451
452impl udouble {
453    //> (used in Self::div_rem)
454    #[must_use]
455    #[inline]
456    pub const fn leading_zeros(self) -> u32 {
457        if self.hi == 0 {
458            self.lo.leading_zeros() + umax::BITS
459        } else {
460            self.hi.leading_zeros()
461        }
462    }
463
464    // double by double division (long division), it's not the most efficient algorithm.
465    // listed here in case of future use
466    #[allow(dead_code)]
467    fn div_rem_2by2(self, other: Self) -> (Self, Self) {
468        let mut n = self; // numerator
469        let mut d = other; // denominator
470        let mut q = Self { lo: 0, hi: 0 }; // quotient
471
472        let nbits = (2 * umax::BITS - n.leading_zeros()) as u16; // assuming umax = u128
473        let dbits = (2 * umax::BITS - d.leading_zeros()) as u16;
474        assert!(dbits != 0, "division by zero");
475
476        // Early return in case we are dividing by a larger number than us
477        if nbits < dbits {
478            return (q, n);
479        }
480
481        // Bitwise long division
482        let mut shift = nbits - dbits;
483        d <<= shift;
484        loop {
485            if n >= d {
486                q += 1;
487                n -= d;
488            }
489            if shift == 0 {
490                break;
491            }
492
493            d >>= 1u8;
494            q <<= 1u8;
495            shift -= 1;
496        }
497        (q, n)
498    }
499
500    // double by single to single division.
501    // equivalent to `udiv_qrnnd` in C or `divq` in assembly.
502    //> (used in Self::{div, rem}::<umax>)
503    #[must_use]
504    pub const fn div_rem_2by1(self, other: umax) -> (umax, umax) {
505        // the following algorithm comes from `ethnum` crate
506        const B: umax = 1 << HALF_BITS; // number base (64 bits)
507
508        // Normalize the divisor.
509        let s = other.leading_zeros();
510        let (n, d) = (self.shl_u32(s), other << s); // numerator, denominator
511        let (d1, d0) = split(d);
512        let (n1, n0) = split(n.lo); // split lower part of dividend
513
514        // Compute the first quotient digit q1.
515        let (mut q1, mut rhat) = div_rem(n.hi, d1);
516
517        // q1 has at most error 2. No more than 2 iterations.
518        while q1 >= B || q1 * d0 > B * rhat + n1 {
519            q1 -= 1;
520            rhat += d1;
521            if rhat >= B {
522                break;
523            }
524        }
525
526        let r21 =
527            n.hi.wrapping_mul(B)
528                .wrapping_add(n1)
529                .wrapping_sub(q1.wrapping_mul(d));
530
531        // Compute the second quotient digit q0.
532        let (mut q0, mut rhat) = div_rem(r21, d1);
533
534        // q0 has at most error 2. No more than 2 iterations.
535        while q0 >= B || q0 * d0 > B * rhat + n0 {
536            q0 -= 1;
537            rhat += d1;
538            if rhat >= B {
539                break;
540            }
541        }
542
543        let r = (r21
544            .wrapping_mul(B)
545            .wrapping_add(n0)
546            .wrapping_sub(q0.wrapping_mul(d)))
547            >> s;
548        let q = q1 * B + q0;
549        (q, r)
550    }
551}
552
553impl Mul<umax> for udouble {
554    type Output = Self;
555    #[inline]
556    fn mul(self, rhs: umax) -> Self::Output {
557        self.checked_mul1(rhs).expect("multiplication overflow!")
558    }
559}
560
561impl Div<umax> for udouble {
562    type Output = Self;
563    #[inline]
564    fn div(self, rhs: umax) -> Self::Output {
565        // self.div_rem(rhs.into()).0
566        if self.hi < rhs {
567            // The result fits in 128 bits.
568            Self {
569                lo: self.div_rem_2by1(rhs).0,
570                hi: 0,
571            }
572        } else {
573            let (q, r) = div_rem(self.hi, rhs);
574            Self {
575                lo: Self { lo: self.lo, hi: r }.div_rem_2by1(rhs).0,
576                hi: q,
577            }
578        }
579    }
580}
581
582//> (used in Montgomery::<u128>::transform)
583impl Rem<umax> for udouble {
584    type Output = umax;
585    #[inline]
586    fn rem(self, rhs: umax) -> Self::Output {
587        if self.hi < rhs {
588            // The result fits in 128 bits.
589            self.div_rem_2by1(rhs).1
590        } else {
591            Self {
592                lo: self.lo,
593                hi: self.hi % rhs,
594            }
595            .div_rem_2by1(rhs)
596            .1
597        }
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604    use rand::random;
605
606    #[test]
607    fn test_construction() {
608        // from widening operators
609        assert_eq!(udouble { hi: 0, lo: 2 }, udouble::widening_add(1, 1));
610        assert_eq!(
611            udouble {
612                hi: 1,
613                lo: umax::MAX - 1
614            },
615            udouble::widening_add(umax::MAX, umax::MAX)
616        );
617
618        assert_eq!(udouble { hi: 0, lo: 1 }, udouble::widening_mul(1, 1));
619        assert_eq!(udouble { hi: 0, lo: 1 }, udouble::widening_square(1));
620        assert_eq!(
621            udouble { hi: 1 << 32, lo: 0 },
622            udouble::widening_mul(1 << 80, 1 << 80)
623        );
624        assert_eq!(
625            udouble { hi: 1 << 32, lo: 0 },
626            udouble::widening_square(1 << 80)
627        );
628        assert_eq!(
629            udouble {
630                hi: 1 << 32,
631                lo: 2 << 120 | 1 << 80
632            },
633            udouble::widening_mul(1 << 80 | 1 << 40, 1 << 80 | 1 << 40)
634        );
635        assert_eq!(
636            udouble {
637                hi: 1 << 32,
638                lo: 2 << 120 | 1 << 80
639            },
640            udouble::widening_square(1 << 80 | 1 << 40)
641        );
642        assert_eq!(
643            udouble {
644                hi: umax::MAX - 1,
645                lo: 1
646            },
647            udouble::widening_mul(umax::MAX, umax::MAX)
648        );
649        assert_eq!(
650            udouble {
651                hi: umax::MAX - 1,
652                lo: 1
653            },
654            udouble::widening_square(umax::MAX)
655        );
656    }
657
658    #[test]
659    fn test_ops() {
660        const ONE: udouble = udouble { hi: 0, lo: 1 };
661        const TWO: udouble = udouble { hi: 0, lo: 2 };
662        const MAX: udouble = udouble {
663            hi: 0,
664            lo: umax::MAX,
665        };
666        const ONEZERO: udouble = udouble { hi: 1, lo: 0 };
667        const ONEMAX: udouble = udouble {
668            hi: 1,
669            lo: umax::MAX,
670        };
671        const TWOZERO: udouble = udouble { hi: 2, lo: 0 };
672
673        assert_eq!(ONE + MAX, ONEZERO);
674        assert_eq!(ONE + ONEMAX, TWOZERO);
675        assert_eq!(ONEZERO - ONE, MAX);
676        assert_eq!(ONEZERO - MAX, ONE);
677        assert_eq!(TWOZERO - ONE, ONEMAX);
678        assert_eq!(TWOZERO - ONEMAX, ONE);
679
680        assert_eq!(ONE << umax::BITS, ONEZERO);
681        assert_eq!((MAX << 1u8) + 1, ONEMAX);
682        assert_eq!(
683            ONE << 200u8,
684            udouble {
685                lo: 0,
686                hi: 1 << (200 - umax::BITS)
687            }
688        );
689        assert_eq!(ONEZERO >> umax::BITS, ONE);
690        assert_eq!(ONEMAX >> 1u8, MAX);
691
692        assert_eq!(ONE * MAX.lo, MAX);
693        assert_eq!(ONEMAX * ONE.lo, ONEMAX);
694        assert_eq!(ONEMAX * TWO.lo, ONEMAX + ONEMAX);
695        assert_eq!(MAX / ONE.lo, MAX);
696        assert_eq!(MAX / MAX.lo, ONE);
697        assert_eq!(ONE / MAX.lo, udouble { lo: 0, hi: 0 });
698        assert_eq!(ONEMAX / ONE.lo, ONEMAX);
699        assert_eq!(ONEMAX / MAX.lo, TWO);
700        assert_eq!(ONEMAX / TWO.lo, MAX);
701        assert_eq!(ONE % MAX.lo, 1);
702        assert_eq!(TWO % MAX.lo, 2);
703        assert_eq!(ONEMAX % MAX.lo, 1);
704        assert_eq!(ONEMAX % TWO.lo, 1);
705
706        assert_eq!(ONEMAX.checked_mul1(MAX.lo), None);
707        assert_eq!(TWOZERO.checked_mul1(MAX.lo), None);
708    }
709
710    #[test]
711    fn test_assign_ops() {
712        for _ in 0..10 {
713            let x = udouble {
714                hi: random::<u32>() as umax,
715                lo: random(),
716            };
717            let y = udouble {
718                hi: random::<u32>() as umax,
719                lo: random(),
720            };
721            let mut z = x;
722
723            z += y;
724            assert_eq!(z, x + y);
725            z -= y;
726            assert_eq!(z, x);
727        }
728    }
729
730    #[test]
731    #[should_panic]
732    fn test_add_overflow_panics() {
733        let _ = udouble {
734            lo: 0,
735            hi: umax::MAX,
736        } + udouble { lo: 0, hi: 1 };
737    }
738
739    #[test]
740    #[should_panic]
741    fn test_sub_underflow_panics() {
742        let _ = udouble { lo: 0, hi: 0 } - udouble { lo: 0, hi: 1 };
743    }
744
745    #[test]
746    #[should_panic]
747    fn test_add_assign_overflow_panics() {
748        let mut x = udouble {
749            lo: 0,
750            hi: umax::MAX,
751        };
752        x += udouble { lo: 0, hi: 1 };
753    }
754
755    #[test]
756    #[should_panic]
757    fn test_sub_assign_underflow_panics() {
758        let mut x = udouble { lo: 0, hi: 0 };
759        x -= udouble { lo: 0, hi: 1 };
760    }
761}