Skip to main content

dashu_float/
div.rs

1use crate::{
2    error::{assert_finite_operands, assert_limited_precision, FpError, FpResult},
3    fbig::FBig,
4    helper_macros::{self, impl_binop_assign_by_taking},
5    repr::{Context, Repr, Word},
6    round::{Round, Rounded, Rounding},
7    utils::{digit_len, shl_digits_in_place, split_digits},
8};
9use core::ops::{Div, DivAssign, Rem, RemAssign};
10use dashu_base::{Approximation, DivEuclid, DivRem, DivRemEuclid, Inverse, RemEuclid, Sign};
11use dashu_int::{fast_div::ConstDivisor, modular::IntoRing, IBig, UBig};
12
13/// Attach the dividend/divisor XOR sign to a zero quotient: the raw quotient significand is
14/// `+0`, so the sign of a zero result (`0/finite`, or a finite/finite that rounds to zero) is
15/// `sign(lhs) XOR sign(rhs)`.
16fn make_div_repr<const B: Word>(
17    sign_negative: bool,
18    significand: IBig,
19    exponent: isize,
20) -> Repr<B> {
21    if significand.is_zero() {
22        if sign_negative {
23            Repr::neg_zero()
24        } else {
25            Repr::zero()
26        }
27    } else {
28        Repr::new(significand, exponent)
29    }
30}
31
32macro_rules! impl_div_for_fbig {
33    (impl $op:ident, $method:ident, $repr_method:ident) => {
34        impl<R: Round, const B: Word> $op<FBig<R, B>> for FBig<R, B> {
35            type Output = FBig<R, B>;
36            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
37                let context = Context::max(self.context, rhs.context);
38                // Route through `unwrap_fp` (not the Repr-level unwrap) so an exponent
39                // overflow/underflow saturates to the directed endpoint, mode-aware.
40                let result = context
41                    .$repr_method(self.repr, rhs.repr)
42                    .map(|r| r.map(|repr| FBig::new(repr, context)));
43                context.unwrap_fp(result)
44            }
45        }
46
47        impl<'l, R: Round, const B: Word> $op<FBig<R, B>> for &'l FBig<R, B> {
48            type Output = FBig<R, B>;
49            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
50                let context = Context::max(self.context, rhs.context);
51                let result = context
52                    .$repr_method(self.repr.clone(), rhs.repr)
53                    .map(|r| r.map(|repr| FBig::new(repr, context)));
54                context.unwrap_fp(result)
55            }
56        }
57
58        impl<'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for FBig<R, B> {
59            type Output = FBig<R, B>;
60            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
61                let context = Context::max(self.context, rhs.context);
62                let result = context
63                    .$repr_method(self.repr, rhs.repr.clone())
64                    .map(|r| r.map(|repr| FBig::new(repr, context)));
65                context.unwrap_fp(result)
66            }
67        }
68
69        impl<'l, 'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for &'l FBig<R, B> {
70            type Output = FBig<R, B>;
71            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
72                let context = Context::max(self.context, rhs.context);
73                let result = context
74                    .$repr_method(self.repr.clone(), rhs.repr.clone())
75                    .map(|r| r.map(|repr| FBig::new(repr, context)));
76                context.unwrap_fp(result)
77            }
78        }
79    };
80}
81
82macro_rules! impl_rem_for_fbig {
83    (impl $op:ident, $method:ident, $repr_method:ident) => {
84        impl<R: Round, const B: Word> $op<FBig<R, B>> for FBig<R, B> {
85            type Output = FBig<R, B>;
86            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
87                let context = Context::max(self.context, rhs.context);
88                FBig::new(context.$repr_method(self.repr, rhs.repr).value(), context)
89            }
90        }
91
92        impl<'l, R: Round, const B: Word> $op<FBig<R, B>> for &'l FBig<R, B> {
93            type Output = FBig<R, B>;
94            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
95                let context = Context::max(self.context, rhs.context);
96                FBig::new(context.$repr_method(self.repr.clone(), rhs.repr).value(), context)
97            }
98        }
99
100        impl<'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for FBig<R, B> {
101            type Output = FBig<R, B>;
102            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
103                let context = Context::max(self.context, rhs.context);
104                FBig::new(context.$repr_method(self.repr, rhs.repr.clone()).value(), context)
105            }
106        }
107
108        impl<'l, 'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for &'l FBig<R, B> {
109            type Output = FBig<R, B>;
110            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
111                let context = Context::max(self.context, rhs.context);
112                FBig::new(
113                    context
114                        .$repr_method(self.repr.clone(), rhs.repr.clone())
115                        .value(),
116                    context,
117                )
118            }
119        }
120    };
121}
122impl_div_for_fbig!(impl Div, div, repr_div);
123impl_rem_for_fbig!(impl Rem, rem, repr_rem);
124impl_binop_assign_by_taking!(impl DivAssign<Self>, div_assign, div);
125impl_binop_assign_by_taking!(impl RemAssign<Self>, rem_assign, rem);
126
127impl<R: Round, const B: Word> DivEuclid<FBig<R, B>> for FBig<R, B> {
128    type Output = IBig;
129    #[inline]
130    fn div_euclid(self, rhs: FBig<R, B>) -> Self::Output {
131        let (num, den) = align_as_int(self, rhs);
132        num.div_euclid(den)
133    }
134}
135
136impl<R: Round, const B: Word> DivEuclid<FBig<R, B>> for &FBig<R, B> {
137    type Output = IBig;
138    #[inline]
139    fn div_euclid(self, rhs: FBig<R, B>) -> Self::Output {
140        self.clone().div_euclid(rhs)
141    }
142}
143
144impl<R: Round, const B: Word> DivEuclid<&FBig<R, B>> for FBig<R, B> {
145    type Output = IBig;
146    #[inline]
147    fn div_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
148        self.div_euclid(rhs.clone())
149    }
150}
151
152impl<R: Round, const B: Word> DivEuclid<&FBig<R, B>> for &FBig<R, B> {
153    type Output = IBig;
154    #[inline]
155    fn div_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
156        self.clone().div_euclid(rhs.clone())
157    }
158}
159
160impl<R: Round, const B: Word> RemEuclid<FBig<R, B>> for FBig<R, B> {
161    type Output = FBig<R, B>;
162    #[inline]
163    fn rem_euclid(self, rhs: FBig<R, B>) -> Self::Output {
164        let r_exponent = self.repr.exponent.min(rhs.repr.exponent);
165        let context = Context::max(self.context, rhs.context);
166
167        let (num, den) = align_as_int(self, rhs);
168        let r = num.rem_euclid(den);
169        let mut r = context.convert_int(r.into()).value();
170        if !r.repr.significand.is_zero() {
171            r.repr.exponent += r_exponent;
172        }
173        r
174    }
175}
176
177impl<R: Round, const B: Word> RemEuclid<FBig<R, B>> for &FBig<R, B> {
178    type Output = FBig<R, B>;
179    #[inline]
180    fn rem_euclid(self, rhs: FBig<R, B>) -> Self::Output {
181        self.clone().rem_euclid(rhs)
182    }
183}
184
185impl<R: Round, const B: Word> RemEuclid<&FBig<R, B>> for FBig<R, B> {
186    type Output = FBig<R, B>;
187    #[inline]
188    fn rem_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
189        self.rem_euclid(rhs.clone())
190    }
191}
192
193impl<R: Round, const B: Word> RemEuclid<&FBig<R, B>> for &FBig<R, B> {
194    type Output = FBig<R, B>;
195    #[inline]
196    fn rem_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
197        self.clone().rem_euclid(rhs.clone())
198    }
199}
200
201impl<R: Round, const B: Word> DivRemEuclid<FBig<R, B>> for FBig<R, B> {
202    type OutputDiv = IBig;
203    type OutputRem = FBig<R, B>;
204    #[inline]
205    fn div_rem_euclid(self, rhs: FBig<R, B>) -> (IBig, FBig<R, B>) {
206        let r_exponent = self.repr.exponent.min(rhs.repr.exponent);
207        let context = Context::max(self.context, rhs.context);
208
209        let (num, den) = align_as_int(self, rhs);
210        let (q, r) = num.div_rem_euclid(den);
211        let mut r = context.convert_int(r.into()).value();
212        if !r.repr.significand.is_zero() {
213            r.repr.exponent += r_exponent;
214        }
215        (q, r)
216    }
217}
218
219impl<R: Round, const B: Word> DivRemEuclid<FBig<R, B>> for &FBig<R, B> {
220    type OutputDiv = IBig;
221    type OutputRem = FBig<R, B>;
222    #[inline]
223    fn div_rem_euclid(self, rhs: FBig<R, B>) -> (IBig, FBig<R, B>) {
224        self.clone().div_rem_euclid(rhs)
225    }
226}
227
228impl<R: Round, const B: Word> DivRemEuclid<&FBig<R, B>> for FBig<R, B> {
229    type OutputDiv = IBig;
230    type OutputRem = FBig<R, B>;
231    #[inline]
232    fn div_rem_euclid(self, rhs: &FBig<R, B>) -> (IBig, FBig<R, B>) {
233        self.div_rem_euclid(rhs.clone())
234    }
235}
236
237impl<R: Round, const B: Word> DivRemEuclid<&FBig<R, B>> for &FBig<R, B> {
238    type OutputDiv = IBig;
239    type OutputRem = FBig<R, B>;
240    #[inline]
241    fn div_rem_euclid(self, rhs: &FBig<R, B>) -> (IBig, FBig<R, B>) {
242        self.clone().div_rem_euclid(rhs.clone())
243    }
244}
245
246macro_rules! impl_div_primitive_with_fbig {
247    ($($t:ty)*) => {$(
248        helper_macros::impl_binop_with_primitive!(impl Div<$t>, div);
249        helper_macros::impl_binop_assign_with_primitive!(impl DivAssign<$t>, div_assign);
250    )*};
251}
252impl_div_primitive_with_fbig!(u8 u16 u32 u64 u128 usize UBig i8 i16 i32 i64 i128 isize IBig);
253// TODO: we should specialize FBig / UBig or FBig / IBig for better efficiency
254
255impl<R: Round, const B: Word> Inverse for FBig<R, B> {
256    type Output = FBig<R, B>;
257
258    #[inline]
259    fn inv(self) -> Self::Output {
260        self.context.unwrap_fp(self.context.inv(&self.repr))
261    }
262}
263
264impl<R: Round, const B: Word> Inverse for &FBig<R, B> {
265    type Output = FBig<R, B>;
266
267    #[inline]
268    fn inv(self) -> Self::Output {
269        self.context.unwrap_fp(self.context.inv(&self.repr))
270    }
271}
272
273impl<R: Round, const B: Word> FBig<R, B> {
274    /// Calculate the multiplicative inverse (`1 / self`) of the floating point number.
275    ///
276    /// # Panics
277    ///
278    /// Panics if the precision is unlimited.
279    #[inline]
280    pub fn inv(&self) -> Self {
281        self.context.unwrap_fp(self.context.inv(&self.repr))
282    }
283}
284
285// Align two float by exponent such that they are both turned into integers
286fn align_as_int<R: Round, const B: Word>(lhs: FBig<R, B>, rhs: FBig<R, B>) -> (IBig, IBig) {
287    let ediff = lhs.repr.exponent - rhs.repr.exponent;
288    let (mut num, mut den) = (lhs.repr.significand, rhs.repr.significand);
289    if ediff >= 0 {
290        shl_digits_in_place::<B>(&mut num, ediff as _);
291    } else {
292        shl_digits_in_place::<B>(&mut den, (-ediff) as _);
293    }
294    (num, den)
295}
296
297impl<R: Round> Context<R> {
298    pub(crate) fn repr_div<const B: Word>(&self, lhs: Repr<B>, rhs: Repr<B>) -> FpResult<Repr<B>> {
299        assert_finite_operands(&lhs, &rhs);
300        assert_limited_precision(self.precision);
301
302        let sign_negative = lhs.sign() != rhs.sign();
303        let sign = if sign_negative {
304            Sign::Negative
305        } else {
306            Sign::Positive
307        };
308
309        if rhs.significand.is_zero() {
310            if lhs.significand.is_zero() {
311                // 0/0 is indeterminate; callers that can signal it (Context::div) check first,
312                // otherwise fall through to div_rem which panics on division by zero.
313            } else {
314                // finite / 0 = ±inf (sign = XOR), returned as a value
315                return Ok(Approximation::Exact(Repr::infinity_with_sign(sign)));
316            }
317        }
318
319        // this method don't deal with the case where lhs significand is too large
320        debug_assert!(lhs.digits() <= self.precision + rhs.digits());
321
322        let (mut q, mut r) = lhs.significand.div_rem(&rhs.significand);
323        let mut e = lhs.exponent.checked_sub(rhs.exponent).ok_or({
324            if lhs.exponent >= 0 {
325                FpError::Overflow(sign)
326            } else {
327                FpError::Underflow(sign)
328            }
329        })?;
330        if r.is_zero() {
331            return Ok(Approximation::Exact(
332                make_div_repr(sign_negative, q, e).check_finite_exponent()?,
333            ));
334        }
335
336        let ddigits = digit_len::<B>(&rhs.significand);
337        if q.is_zero() {
338            // lhs.significand < rhs.significand
339            let rdigits = digit_len::<B>(&r); // rdigits <= ddigits
340            let shift = ddigits + self.precision - rdigits;
341            shl_digits_in_place::<B>(&mut r, shift);
342            e = e
343                .checked_sub(shift as isize)
344                .ok_or(FpError::Underflow(sign))?;
345            let (q0, r0) = r.div_rem(&rhs.significand);
346            q = q0;
347            r = r0;
348        } else {
349            let ndigits = digit_len::<B>(&q) + ddigits;
350            if ndigits < ddigits + self.precision {
351                // TODO: here the operations can be optimized: 1. prevent double power, 2. q += q0 can be |= if B is power of 2
352                let shift = ddigits + self.precision - ndigits;
353                shl_digits_in_place::<B>(&mut q, shift);
354                shl_digits_in_place::<B>(&mut r, shift);
355                e = e
356                    .checked_sub(shift as isize)
357                    .ok_or(FpError::Underflow(sign))?;
358
359                let (q0, r0) = r.div_rem(&rhs.significand);
360                q += q0;
361                r = r0;
362            }
363        }
364
365        let repr = if r.is_zero() {
366            Approximation::Exact(make_div_repr(sign_negative, q, e))
367        } else {
368            let adjust = R::round_ratio(&q, r, &rhs.significand);
369            Approximation::Inexact(make_div_repr(sign_negative, q + adjust, e), adjust)
370        };
371        Ok(repr)
372    }
373
374    pub(crate) fn repr_rem<const B: Word>(&self, lhs: Repr<B>, rhs: Repr<B>) -> Rounded<Repr<B>> {
375        assert_finite_operands(&lhs, &rhs);
376
377        let lhs_is_neg_zero = lhs.is_neg_zero();
378        let (lhs_sign, lhs_signif) = lhs.significand.into_parts();
379        let (_, rhs_signif) = rhs.significand.into_parts();
380
381        use core::cmp::Ordering;
382        let significand = match lhs.exponent.cmp(&rhs.exponent) {
383            Ordering::Equal => {
384                let r1 = lhs_signif % &rhs_signif;
385                let r2 = rhs_signif - &r1;
386                if r1 < r2 {
387                    IBig::from_parts(lhs_sign, r1)
388                } else {
389                    IBig::from_parts(-lhs_sign, r2)
390                }
391            }
392            Ordering::Greater => {
393                // if the least significant digit of lhs is higher than rhs, then we can
394                // align lhs to rhs and do simple modulo operations
395                let modulo = ConstDivisor::new(rhs_signif);
396                let shift = (lhs.exponent - rhs.exponent) as usize;
397                let scaling = if B == 2 {
398                    (UBig::ONE << shift).into_ring(&modulo)
399                } else {
400                    UBig::from_word(B).into_ring(&modulo).pow(&shift.into())
401                };
402                let r = lhs_signif.into_ring(&modulo) * scaling;
403                let r1 = r.residue();
404                let r2 = (-r).residue();
405                if r1 < r2 {
406                    IBig::from_parts(lhs_sign, r1)
407                } else {
408                    IBig::from_parts(-lhs_sign, r2)
409                }
410            }
411            Ordering::Less => {
412                // otherwise we have to split lhs into two parts
413                let shift = (rhs.exponent - lhs.exponent) as usize;
414                let (hi, lo) = split_digits::<B>(lhs_signif.into(), shift);
415
416                let mut r1 = hi % &rhs_signif;
417                let mut r2 = rhs_signif - &r1;
418
419                shl_digits_in_place::<B>(&mut r1, shift);
420                r1 += &lo;
421
422                shl_digits_in_place::<B>(&mut r2, shift);
423                r2 -= lo;
424
425                if r1 < r2 {
426                    lhs_sign * r1
427                } else {
428                    (-lhs_sign) * r2
429                }
430            }
431        };
432
433        let exponent = lhs.exponent.min(rhs.exponent);
434        if significand.is_zero() {
435            // the sign of a zero remainder follows the dividend (±0)
436            Approximation::Exact(if lhs_is_neg_zero {
437                Repr::neg_zero()
438            } else {
439                Repr::zero()
440            })
441        } else {
442            match Repr::new(significand, exponent).check_finite_exponent() {
443                Ok(repr) => self.repr_round(repr),
444                Err(e) => match e {
445                    FpError::Overflow(sign) => {
446                        Approximation::Inexact(Repr::infinity_with_sign(sign), Rounding::NoOp)
447                    }
448                    FpError::Underflow(sign) => {
449                        Approximation::Inexact(Repr::zero_with_sign(sign), Rounding::NoOp)
450                    }
451                    _ => unreachable!(),
452                },
453            }
454        }
455    }
456
457    /// Divide two floating point numbers under this context.
458    ///
459    /// # Examples
460    ///
461    /// ```
462    /// # use core::str::FromStr;
463    /// # use dashu_base::ParseError;
464    /// # use dashu_float::DBig;
465    /// use dashu_base::Approximation::*;
466    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
467    ///
468    /// let context = Context::<HalfAway>::new(2);
469    /// let a = DBig::from_str("-1.234")?;
470    /// let b = DBig::from_str("6.789")?;
471    /// assert_eq!(context.div(&a.repr(), &b.repr()), Ok(Inexact(DBig::from_str("-0.18")?, NoOp)));
472    /// # Ok::<(), ParseError>(())
473    /// ```
474    ///
475    /// # Euclidean Division
476    ///
477    /// To do euclidean division on the float numbers (get an integer quotient and remainder, equivalent to C99's
478    /// `fmod` and `remquo`), please use the methods provided by traits [DivEuclid], [RemEuclid] and [DivRemEuclid].
479    ///
480    pub fn div<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> FpResult<FBig<R, B>> {
481        if lhs.is_infinite() || rhs.is_infinite() {
482            return Err(FpError::InfiniteInput);
483        }
484        if lhs.significand.is_zero() && rhs.significand.is_zero() {
485            return Err(FpError::Indeterminate); // 0/0
486        }
487
488        let lhs_repr = if !lhs.is_pos_zero() && lhs.digits_ub() > rhs.digits_lb() + self.precision {
489            // shrink lhs if it's larger than necessary
490            Self::new(rhs.digits() + self.precision)
491                .repr_round_ref(lhs)
492                .value()
493        } else {
494            lhs.clone()
495        };
496        Ok(self
497            .repr_div(lhs_repr, rhs.clone())?
498            .map(|v| FBig::new(v, *self)))
499    }
500
501    /// Calculate the remainder of `⌈lhs / rhs⌋`.
502    ///
503    /// The remainder is calculated as `r = lhs - ⌈lhs / rhs⌋ * rhs`, the division rounds to the nearest and ties to away.
504    /// So if `n = (lhs / rhs).round()`, then `lhs == n * rhs + r` (given enough precision).
505    ///
506    /// # Examples
507    ///
508    /// ```
509    /// # use core::str::FromStr;
510    /// # use dashu_base::ParseError;
511    /// # use dashu_float::DBig;
512    /// use dashu_base::Approximation::*;
513    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
514    ///
515    /// let context = Context::<HalfAway>::new(3);
516    /// let a = DBig::from_str("6.789")?;
517    /// let b = DBig::from_str("-1.234")?;
518    /// assert_eq!(context.rem(&a.repr(), &b.repr()), Ok(Exact(DBig::from_str("-0.615")?)));
519    /// # Ok::<(), ParseError>(())
520    /// ```
521    pub fn rem<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> FpResult<FBig<R, B>> {
522        if lhs.is_infinite() || rhs.is_infinite() {
523            return Err(FpError::InfiniteInput);
524        }
525        Ok(self
526            .repr_rem(lhs.clone(), rhs.clone())
527            .map(|v| FBig::new(v, *self)))
528    }
529
530    /// Compute the multiplicative inverse of an `FBig`
531    ///
532    /// ```
533    /// # use core::str::FromStr;
534    /// # use dashu_base::ParseError;
535    /// # use dashu_float::DBig;
536    /// use dashu_base::Approximation::*;
537    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
538    ///
539    /// let context = Context::<HalfAway>::new(2);
540    /// let a = DBig::from_str("-1.234")?;
541    /// assert_eq!(context.inv(&a.repr()), Ok(Inexact(DBig::from_str("-0.81")?, NoOp)));
542    /// # Ok::<(), ParseError>(())
543    /// ```
544    #[inline]
545    pub fn inv<const B: Word>(&self, f: &Repr<B>) -> FpResult<FBig<R, B>> {
546        if f.is_infinite() {
547            return Err(FpError::InfiniteInput);
548        }
549        // inv(±0) = ±inf (produced as a value by repr_div)
550        Ok(self
551            .repr_div(Repr::one(), f.clone())?
552            .map(|v| FBig::new(v, *self)))
553    }
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559    use crate::round::mode;
560
561    fn r2(sig: i32, exp: isize) -> Repr<2> {
562        Repr::new(sig.into(), exp)
563    }
564
565    #[test]
566    fn test_div_by_zero_is_infinity() {
567        let ctx = Context::<mode::HalfEven>::new(53);
568        // finite / 0 = ±inf (a value, not an error); sign = XOR
569        let pos = ctx.div::<2>(&r2(1, 0), &Repr::<2>::zero()).unwrap().value();
570        assert!(pos.repr().is_infinite());
571        assert_eq!(pos.repr().sign(), Sign::Positive);
572
573        let neg = ctx
574            .div::<2>(&r2(-1, 0), &Repr::<2>::zero())
575            .unwrap()
576            .value();
577        assert_eq!(neg.repr().sign(), Sign::Negative);
578
579        // 1 / -0 = -inf
580        let neg2 = ctx
581            .div::<2>(&r2(1, 0), &Repr::<2>::neg_zero())
582            .unwrap()
583            .value();
584        assert_eq!(neg2.repr().sign(), Sign::Negative);
585    }
586
587    #[test]
588    fn test_zero_over_zero_is_indeterminate() {
589        let ctx = Context::<mode::HalfEven>::new(53);
590        assert_eq!(
591            ctx.div::<2>(&Repr::<2>::zero(), &Repr::<2>::zero()),
592            Err(FpError::Indeterminate)
593        );
594    }
595
596    #[test]
597    fn test_inv_zero_is_infinity() {
598        let ctx = Context::<mode::HalfEven>::new(53);
599        let r = ctx.inv::<2>(&Repr::<2>::zero()).unwrap().value();
600        assert!(r.repr().is_infinite());
601        assert_eq!(r.repr().sign(), Sign::Positive);
602    }
603
604    #[test]
605    fn test_fbig_div_zero_produces_infinity() {
606        // FBig convenience layer: 1 / 0 yields an infinity-valued FBig (no panic).
607        let one = FBig::<mode::HalfEven>::try_from(1.0f64).unwrap();
608        let zero = FBig::<mode::HalfEven>::try_from(0.0f64).unwrap();
609        let inf = one / zero;
610        assert!(inf.repr().is_infinite());
611    }
612
613    #[test]
614    #[should_panic]
615    fn test_fbig_zero_over_zero_panics() {
616        // 0 / 0 is indeterminate; the FBig layer panics.
617        let zero = FBig::<mode::HalfEven>::try_from(0.0f64).unwrap();
618        let _ = zero.clone() / zero;
619    }
620
621    // The `FBig / FBig` operator routes through `unwrap_fp`, so an exponent underflow saturates to
622    // the directed endpoint (not a mode-blind signed zero): 2^isize::MIN / 3 ≈ 2^(isize::MIN − 2)
623    // underflows; Up → smallest positive, Down → +0.
624    #[test]
625    fn test_div_directed_underflow() {
626        use dashu_int::IBig;
627        let p = 53;
628        let floor_up = FBig::<mode::Up, 2>::from_parts(IBig::ONE, isize::MIN)
629            .with_precision(p)
630            .value();
631        let floor_down = FBig::<mode::Down, 2>::from_parts(IBig::ONE, isize::MIN)
632            .with_precision(p)
633            .value();
634        let three_up = FBig::<mode::Up, 2>::from_parts(IBig::from(3), 0)
635            .with_precision(p)
636            .value();
637        let three_down = FBig::<mode::Down, 2>::from_parts(IBig::from(3), 0)
638            .with_precision(p)
639            .value();
640        let up = floor_up / &three_up;
641        let down = floor_down / &three_down;
642        assert_eq!(up.repr().significand(), &IBig::ONE);
643        assert_eq!(up.repr().exponent(), isize::MIN);
644        assert!(down.repr().is_pos_zero());
645        assert!(up > down);
646    }
647}