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, shl_digits_in_place, split_digits},
8};
9use core::cmp::Ordering;
10use core::ops::{Div, DivAssign, Rem, RemAssign};
11use dashu_base::{
12    AbsOrd, Approximation, BitTest, DivEuclid, DivRem, DivRemEuclid, Inverse, RemEuclid, Sign,
13    Signed, UnsignedAbs,
14};
15use dashu_int::{fast_div::ConstDivisor, modular::IntoRing, IBig, UBig};
16
17/// Attach the dividend/divisor XOR sign to a zero quotient: the raw quotient significand is
18/// `+0`, so the sign of a zero result (`0/finite`, or a finite/finite that rounds to zero) is
19/// `sign(lhs) XOR sign(rhs)`.
20fn make_div_repr<const B: Word>(
21    sign_negative: bool,
22    significand: IBig,
23    exponent: isize,
24) -> Repr<B> {
25    if significand.is_zero() {
26        if sign_negative {
27            Repr::neg_zero()
28        } else {
29            Repr::zero()
30        }
31    } else {
32        Repr::new(significand, exponent)
33    }
34}
35
36macro_rules! impl_div_for_fbig {
37    (impl $op:ident, $method:ident, $repr_method:ident) => {
38        impl<R: Round, const B: Word> $op<FBig<R, B>> for FBig<R, B> {
39            type Output = FBig<R, B>;
40            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
41                let context = Context::max(self.context, rhs.context);
42                // Route through `unwrap_fp` (not the Repr-level unwrap) so an exponent
43                // overflow/underflow saturates to the directed endpoint, mode-aware.
44                let result = context
45                    .$repr_method(self.repr, rhs.repr)
46                    .map(|r| r.map(|repr| FBig::new(repr, context)));
47                context.unwrap_fp(result)
48            }
49        }
50
51        impl<'l, R: Round, const B: Word> $op<FBig<R, B>> for &'l FBig<R, B> {
52            type Output = FBig<R, B>;
53            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
54                let context = Context::max(self.context, rhs.context);
55                let result = context
56                    .$repr_method(self.repr.clone(), rhs.repr)
57                    .map(|r| r.map(|repr| FBig::new(repr, context)));
58                context.unwrap_fp(result)
59            }
60        }
61
62        impl<'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for FBig<R, B> {
63            type Output = FBig<R, B>;
64            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
65                let context = Context::max(self.context, rhs.context);
66                let result = context
67                    .$repr_method(self.repr, rhs.repr.clone())
68                    .map(|r| r.map(|repr| FBig::new(repr, context)));
69                context.unwrap_fp(result)
70            }
71        }
72
73        impl<'l, 'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for &'l FBig<R, B> {
74            type Output = FBig<R, B>;
75            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
76                let context = Context::max(self.context, rhs.context);
77                let result = context
78                    .$repr_method(self.repr.clone(), rhs.repr.clone())
79                    .map(|r| r.map(|repr| FBig::new(repr, context)));
80                context.unwrap_fp(result)
81            }
82        }
83    };
84}
85
86macro_rules! impl_rem_for_fbig {
87    (impl $op:ident, $method:ident, $repr_method:ident) => {
88        impl<R: Round, const B: Word> $op<FBig<R, B>> for FBig<R, B> {
89            type Output = FBig<R, B>;
90            /// Calculates the remainder of division: if `n` is the quotient `self / rhs`
91            /// rounded to an integer under the rounding mode attached to the type, then
92            /// the result is `self - n * rhs`.
93            ///
94            /// The attached mode therefore selects the remainder's convention: with
95            /// [Zero](crate::round::mode::Zero) (the `FBig` default) the quotient is
96            /// truncated and the remainder keeps the dividend's sign, like `%` on Rust's
97            /// primitives; with `Down`/`Up` the quotient rounds floor/ceil-style (Python's
98            /// `%` is the `Down` convention); with `HalfEven`/`HalfAway` the remainder is
99            /// bounded by `|rhs|/2`, with ties to even or away from zero as in
100            /// [`FBig::round`]. Independently of the quotient rule, the mode also rounds
101            /// the remainder value itself down to the context precision. For a
102            /// non-negative (Euclidean) remainder regardless of the mode, use
103            /// [`RemEuclid`].
104            ///
105            /// # Examples
106            ///
107            /// ```
108            /// # use core::str::FromStr;
109            /// # use dashu_base::ParseError;
110            /// # use dashu_float::{FBig, DBig, round::mode};
111            /// // the binary default type truncates the quotient: 15 = 1×10 + 5
112            /// let a = FBig::<mode::Zero, 10>::from_str("15")?;
113            /// let b = FBig::<mode::Zero, 10>::from_str("10")?;
114            /// assert_eq!(a % b, FBig::<mode::Zero, 10>::from_str("5")?);
115            ///
116            /// // the decimal type rounds the quotient half away from zero: 15 = 2×10 − 5
117            /// assert_eq!(
118            ///     DBig::from_str("15")? % DBig::from_str("10")?,
119            ///     DBig::from_str("-5")?
120            /// );
121            /// # Ok::<(), ParseError>(())
122            /// ```
123            ///
124            /// # Panics
125            ///
126            /// Panics if either operand is infinite.
127            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
128                let context = Context::max(self.context, rhs.context);
129                FBig::new(context.$repr_method(self.repr, rhs.repr).value(), context)
130            }
131        }
132
133        impl<'l, R: Round, const B: Word> $op<FBig<R, B>> for &'l FBig<R, B> {
134            type Output = FBig<R, B>;
135            /// Calculates the remainder of division, rounding the quotient to an integer
136            /// under the rounding mode attached to the type. See the [`Rem`] implementation
137            /// on [`FBig`] for details.
138            fn $method(self, rhs: FBig<R, B>) -> Self::Output {
139                let context = Context::max(self.context, rhs.context);
140                FBig::new(context.$repr_method(self.repr.clone(), rhs.repr).value(), context)
141            }
142        }
143
144        impl<'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for FBig<R, B> {
145            type Output = FBig<R, B>;
146            /// Calculates the remainder of division, rounding the quotient to an integer
147            /// under the rounding mode attached to the type. See the [`Rem`] implementation
148            /// on [`FBig`] for details.
149            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
150                let context = Context::max(self.context, rhs.context);
151                FBig::new(context.$repr_method(self.repr, rhs.repr.clone()).value(), context)
152            }
153        }
154
155        impl<'l, 'r, R: Round, const B: Word> $op<&'r FBig<R, B>> for &'l FBig<R, B> {
156            type Output = FBig<R, B>;
157            /// Calculates the remainder of division, rounding the quotient to an integer
158            /// under the rounding mode attached to the type. See the [`Rem`] implementation
159            /// on [`FBig`] for details.
160            fn $method(self, rhs: &FBig<R, B>) -> Self::Output {
161                let context = Context::max(self.context, rhs.context);
162                FBig::new(
163                    context
164                        .$repr_method(self.repr.clone(), rhs.repr.clone())
165                        .value(),
166                    context,
167                )
168            }
169        }
170    };
171}
172impl_div_for_fbig!(impl Div, div, repr_div);
173impl_rem_for_fbig!(impl Rem, rem, repr_rem);
174impl_binop_assign_by_taking!(impl DivAssign<Self>, div_assign, div);
175impl_binop_assign_by_taking!(impl RemAssign<Self>, rem_assign, rem);
176
177impl<R: Round, const B: Word> DivEuclid<FBig<R, B>> for FBig<R, B> {
178    type Output = IBig;
179    #[inline]
180    fn div_euclid(self, rhs: FBig<R, B>) -> Self::Output {
181        let (num, den) = align_as_int(self, rhs);
182        num.div_euclid(den)
183    }
184}
185
186impl<R: Round, const B: Word> DivEuclid<FBig<R, B>> for &FBig<R, B> {
187    type Output = IBig;
188    #[inline]
189    fn div_euclid(self, rhs: FBig<R, B>) -> Self::Output {
190        self.clone().div_euclid(rhs)
191    }
192}
193
194impl<R: Round, const B: Word> DivEuclid<&FBig<R, B>> for FBig<R, B> {
195    type Output = IBig;
196    #[inline]
197    fn div_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
198        self.div_euclid(rhs.clone())
199    }
200}
201
202impl<R: Round, const B: Word> DivEuclid<&FBig<R, B>> for &FBig<R, B> {
203    type Output = IBig;
204    #[inline]
205    fn div_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
206        self.clone().div_euclid(rhs.clone())
207    }
208}
209
210impl<R: Round, const B: Word> RemEuclid<FBig<R, B>> for FBig<R, B> {
211    type Output = FBig<R, B>;
212    #[inline]
213    fn rem_euclid(self, rhs: FBig<R, B>) -> Self::Output {
214        let r_exponent = self.repr.exponent.min(rhs.repr.exponent);
215        let context = Context::max(self.context, rhs.context);
216
217        let (num, den) = align_as_int(self, rhs);
218        let r = num.rem_euclid(den);
219        let mut r = context.convert_int(r.into()).value();
220        if !r.repr.significand.is_zero() {
221            r.repr.exponent += r_exponent;
222        }
223        r
224    }
225}
226
227impl<R: Round, const B: Word> RemEuclid<FBig<R, B>> for &FBig<R, B> {
228    type Output = FBig<R, B>;
229    #[inline]
230    fn rem_euclid(self, rhs: FBig<R, B>) -> Self::Output {
231        self.clone().rem_euclid(rhs)
232    }
233}
234
235impl<R: Round, const B: Word> RemEuclid<&FBig<R, B>> for FBig<R, B> {
236    type Output = FBig<R, B>;
237    #[inline]
238    fn rem_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
239        self.rem_euclid(rhs.clone())
240    }
241}
242
243impl<R: Round, const B: Word> RemEuclid<&FBig<R, B>> for &FBig<R, B> {
244    type Output = FBig<R, B>;
245    #[inline]
246    fn rem_euclid(self, rhs: &FBig<R, B>) -> Self::Output {
247        self.clone().rem_euclid(rhs.clone())
248    }
249}
250
251impl<R: Round, const B: Word> DivRemEuclid<FBig<R, B>> for FBig<R, B> {
252    type OutputDiv = IBig;
253    type OutputRem = FBig<R, B>;
254    #[inline]
255    fn div_rem_euclid(self, rhs: FBig<R, B>) -> (IBig, FBig<R, B>) {
256        let r_exponent = self.repr.exponent.min(rhs.repr.exponent);
257        let context = Context::max(self.context, rhs.context);
258
259        let (num, den) = align_as_int(self, rhs);
260        let (q, r) = num.div_rem_euclid(den);
261        let mut r = context.convert_int(r.into()).value();
262        if !r.repr.significand.is_zero() {
263            r.repr.exponent += r_exponent;
264        }
265        (q, r)
266    }
267}
268
269impl<R: Round, const B: Word> DivRemEuclid<FBig<R, B>> for &FBig<R, B> {
270    type OutputDiv = IBig;
271    type OutputRem = FBig<R, B>;
272    #[inline]
273    fn div_rem_euclid(self, rhs: FBig<R, B>) -> (IBig, FBig<R, B>) {
274        self.clone().div_rem_euclid(rhs)
275    }
276}
277
278impl<R: Round, const B: Word> DivRemEuclid<&FBig<R, B>> for FBig<R, B> {
279    type OutputDiv = IBig;
280    type OutputRem = FBig<R, B>;
281    #[inline]
282    fn div_rem_euclid(self, rhs: &FBig<R, B>) -> (IBig, FBig<R, B>) {
283        self.div_rem_euclid(rhs.clone())
284    }
285}
286
287impl<R: Round, const B: Word> DivRemEuclid<&FBig<R, B>> for &FBig<R, B> {
288    type OutputDiv = IBig;
289    type OutputRem = FBig<R, B>;
290    #[inline]
291    fn div_rem_euclid(self, rhs: &FBig<R, B>) -> (IBig, FBig<R, B>) {
292        self.clone().div_rem_euclid(rhs.clone())
293    }
294}
295
296macro_rules! impl_div_primitive_with_fbig {
297    ($($t:ty)*) => {$(
298        helper_macros::impl_binop_with_primitive!(impl Div<$t>, div);
299        helper_macros::impl_binop_assign_with_primitive!(impl DivAssign<$t>, div_assign);
300    )*};
301}
302impl_div_primitive_with_fbig!(u8 u16 u32 u64 u128 usize UBig i8 i16 i32 i64 i128 isize IBig);
303// TODO: we should specialize FBig / UBig or FBig / IBig for better efficiency
304
305impl<R: Round, const B: Word> Inverse for FBig<R, B> {
306    type Output = FBig<R, B>;
307
308    #[inline]
309    fn inv(self) -> Self::Output {
310        self.context.unwrap_fp(self.context.inv(&self.repr))
311    }
312}
313
314impl<R: Round, const B: Word> Inverse for &FBig<R, B> {
315    type Output = FBig<R, B>;
316
317    #[inline]
318    fn inv(self) -> Self::Output {
319        self.context.unwrap_fp(self.context.inv(&self.repr))
320    }
321}
322
323impl<R: Round, const B: Word> FBig<R, B> {
324    /// Calculate the multiplicative inverse (`1 / self`) of the floating point number.
325    ///
326    /// # Panics
327    ///
328    /// Panics if the precision is unlimited.
329    #[inline]
330    pub fn inv(&self) -> Self {
331        self.context.unwrap_fp(self.context.inv(&self.repr))
332    }
333}
334
335// Align two float by exponent such that they are both turned into integers
336fn align_as_int<R: Round, const B: Word>(lhs: FBig<R, B>, rhs: FBig<R, B>) -> (IBig, IBig) {
337    let ediff = lhs.repr.exponent - rhs.repr.exponent;
338    let (mut num, mut den) = (lhs.repr.significand, rhs.repr.significand);
339    if ediff >= 0 {
340        shl_digits_in_place::<B>(&mut num, ediff as _);
341    } else {
342        shl_digits_in_place::<B>(&mut den, (-ediff) as _);
343    }
344    (num, den)
345}
346
347// Decide, under the rounding mode `R`, which of the two exact remainder candidates a
348// division leaves: `r1` — the magnitude of `lhs mod rhs`, carrying the dividend's sign,
349// meaning the truncated quotient is kept — or its complement `r2 = |rhs| − r1`, carrying
350// the opposite sign, meaning the quotient is stepped away from zero. Returns true for
351// the complement; `r1_zero` marks an exact multiple (every mode then keeps the quotient,
352// regardless of `r1_vs_r2` being `Less`).
353//
354// This is `R::round_low_part` applied to the quotient `k + f`, where `k` is the truncated
355// quotient, `f = ±r1/|rhs|` carries the quotient's sign, and the half test is `r1_vs_r2`.
356// The shipped modes never inspect the integer beyond its sign (and, on an exact tie, its
357// parity via `bit(0)`), so a small stand-in carrying those bits is passed instead of `k` —
358// the Greater branch never has the quotient, and the other branches get its parity for
359// free from the division that produces the remainder.
360fn rem_rounds_away<R: Round>(
361    quotient_sign: Sign,
362    r1_zero: bool,
363    r1_vs_r2: Ordering,
364    quotient_odd: bool,
365) -> bool {
366    if r1_zero {
367        return false;
368    }
369    let quotient_hint = if r1_vs_r2 == Ordering::Equal {
370        // exact half-integer quotient: only HalfEven consults the parity
371        match (quotient_sign, quotient_odd) {
372            (Sign::Positive, false) => IBig::from(2),
373            (Sign::Positive, true) => IBig::from(1),
374            (Sign::Negative, false) => IBig::from(-2),
375            (Sign::Negative, true) => IBig::from(-1),
376        }
377    } else {
378        // off-tie: only the sign of the quotient can influence the rounding
379        match quotient_sign {
380            Sign::Positive => IBig::from(1),
381            Sign::Negative => IBig::from(-1),
382        }
383    };
384    !matches!(
385        R::round_low_part::<_>(&quotient_hint, quotient_sign, || r1_vs_r2),
386        Rounding::NoOp
387    )
388}
389
390impl<R: Round> Context<R> {
391    /// Division kernel for an already-bounded dividend: `lhs` must carry at most
392    /// `rhs.digits() + precision` digits (the `FBig` operators' operands always do — a
393    /// significand is at most `precision + 1` digits and the divisor at least one). For an
394    /// arbitrary [`Repr`] dividend use [`Context::div`], which bounds it exactly (see
395    /// [`Self::repr_div_split`]).
396    pub(crate) fn repr_div<const B: Word>(&self, lhs: Repr<B>, rhs: Repr<B>) -> FpResult<Repr<B>> {
397        debug_assert!(
398            digit_len::<B>(&lhs.significand) <= digit_len::<B>(&rhs.significand) + self.precision
399        );
400        self.repr_div_split(lhs, rhs, IBig::ZERO, 0)
401    }
402
403    /// [`Self::repr_div`] for an arbitrary-width dividend: the excess low digits below the
404    /// `divisor digits + precision` bound are split off as `lo` and carried through the
405    /// division as sticky rounding information, so no information is ever lost. (Rounding the
406    /// dividend instead — as an earlier version of `Context::div` did — perturbs ties,
407    /// direction and the exactness flag: the quotient of a *rounded* dividend can divide
408    /// exactly where the true one doesn't, land on a false midpoint, or round the wrong way
409    /// under directed modes.)
410    ///
411    /// The width check runs on the cheap `digits_ub`/`digits_lb` estimates, but the split
412    /// point itself must be the EXACT digit count: splitting at exactly
413    /// `divisor digits + precision` digits guarantees the high part is not below the divisor,
414    /// which keeps the kernel's padding branches (they rescale the remainder without
415    /// rescaling the sticky low part) off the `k > 0` path entirely. A lower split — e.g.
416    /// derived from the bounds — is unsound for that reason.
417    pub(crate) fn repr_div_any_width<const B: Word>(
418        &self,
419        lhs: Repr<B>,
420        rhs: Repr<B>,
421    ) -> FpResult<Repr<B>> {
422        // (digits_ub of a zero dividend is 0, so zero takes the fast path too)
423        if lhs.digits_ub() <= rhs.digits_lb() + self.precision {
424            return self.repr_div(lhs, rhs);
425        }
426
427        // exact split (the sign of the dividend is preserved on both parts)
428        let ddigits = digit_len::<B>(&rhs.significand);
429        let k = digit_len::<B>(&lhs.significand).saturating_sub(ddigits + self.precision);
430        if k == 0 {
431            return self.repr_div(lhs, rhs);
432        }
433        let (hi, lo) = split_digits::<B>(lhs.significand, k);
434        debug_assert_eq!(digit_len::<B>(&hi), ddigits + self.precision);
435        // the kernel normalizes the divisor to be positive by negating BOTH operands, so the
436        // sticky low part must follow the (negated) dividend's sign
437        let lo = rhs.significand.sign() * lo;
438        self.repr_div_split(
439            Repr {
440                significand: hi,
441                exponent: lhs.exponent,
442            },
443            rhs,
444            lo,
445            k,
446        )
447    }
448
449    /// The division kernel proper: computes `lhs / rhs` correctly rounded to this context's
450    /// precision. `(lo, k)` describes the part of the dividend below its least significant
451    /// kept digit: the true dividend is `(lhs.significand · B^k + lo) · B^lhs.exponent`, and
452    /// the pair participates in the final rounding as a sticky fraction.
453    fn repr_div_split<const B: Word>(
454        &self,
455        lhs: Repr<B>,
456        rhs: Repr<B>,
457        lo: IBig,
458        k: usize,
459    ) -> FpResult<Repr<B>> {
460        assert_finite_operands(&lhs, &rhs);
461        assert_limited_precision(self.precision);
462
463        let sign_negative = lhs.sign() != rhs.sign();
464        let sign = if sign_negative {
465            Sign::Negative
466        } else {
467            Sign::Positive
468        };
469
470        if rhs.significand.is_zero() {
471            if lhs.significand.is_zero() {
472                // 0/0 is indeterminate; callers that can signal it (Context::div) check first,
473                // otherwise fall through to div_rem which panics on division by zero.
474            } else {
475                // finite / 0 = ±inf (sign = XOR), returned as a value
476                return Ok(Approximation::Exact(Repr::infinity_with_sign(sign)));
477            }
478        }
479
480        // Work with a positive divisor so that the quotient and remainder from `div_rem`
481        // (truncated division: the remainder carries the dividend's sign) keep the value's
482        // sign in `q`, and `round_ratio` below sees a plain (integer + fraction) split.
483        // Negation is O(1) (a sign flip on the shared buffer).
484        let (num, den) = if rhs.significand.is_positive() {
485            (lhs.significand, rhs.significand)
486        } else {
487            (-lhs.significand, -rhs.significand)
488        };
489
490        let (mut q, mut r) = num.div_rem(&den);
491        let mut e = lhs
492            .exponent
493            .checked_add(k as isize)
494            .and_then(|e| e.checked_sub(rhs.exponent))
495            .ok_or({
496                // lhs.exponent >= 0 whenever the addition overflows, and < 0 whenever the
497                // subtraction underflows (see the digit bound on `k` above)
498                if lhs.exponent >= 0 {
499                    FpError::Overflow(sign)
500                } else {
501                    FpError::Underflow(sign)
502                }
503            })?;
504
505        // From here on the digit counts must be EXACT (the `digits_ub`/`digits_lb` estimates
506        // only bound a value): each padding shift below has to land the scaled operand on an
507        // exact digit position, which the rounding invariants below lean on. `q`, `r` and
508        // `den` are plain IBigs here (the bounds API lives on Repr), so `digit_len` is also
509        // the cheapest exact source for them.
510        let mut qdigits = digit_len::<B>(&q);
511
512        // Exact division with the quotient already within the precision: nothing to scale and
513        // nothing to round (the `lo` sticky part is empty exactly when k == 0).
514        if r.is_zero() && k == 0 && qdigits <= self.precision {
515            return Ok(Approximation::Exact(
516                make_div_repr(sign_negative, q, e).check_finite_exponent()?,
517            ));
518        }
519
520        if q.is_zero() {
521            // num < den: scale the remainder up so the quotient has ~precision digits
522            let ddigits = digit_len::<B>(&den);
523            let rdigits = digit_len::<B>(&r); // rdigits <= ddigits
524            let shift = ddigits + self.precision - rdigits;
525            shl_digits_in_place::<B>(&mut r, shift);
526            e = e
527                .checked_sub(shift as isize)
528                .ok_or(FpError::Underflow(sign))?;
529            let (q0, r0) = r.div_rem(&den);
530            q = q0;
531            r = r0;
532            // the scaled dividend has exactly ddigits+precision digits, so the quotient has
533            // `precision` or `precision + 1` digits; with rdigits == ddigits the remainder is
534            // still strictly below the divisor, which rules out the carry to B^precision
535            qdigits = if rdigits == ddigits {
536                self.precision
537            } else {
538                digit_len::<B>(&q)
539            };
540        } else if qdigits < self.precision {
541            // TODO: here the operations can be optimized: 1. prevent double power, 2. q += q0 can be |= if B is power of 2
542            let shift = self.precision - qdigits;
543            shl_digits_in_place::<B>(&mut q, shift);
544            shl_digits_in_place::<B>(&mut r, shift);
545            e = e
546                .checked_sub(shift as isize)
547                .ok_or(FpError::Underflow(sign))?;
548
549            let (q0, r0) = r.div_rem(&den);
550            q += q0;
551            r = r0;
552            // q·B^shift ≤ B^precision − B^shift and q0 < B^shift, so the sum stays below
553            // B^precision: the scaled quotient has exactly `precision` digits
554            qdigits = self.precision;
555        }
556
557        // At this point the quotient is `(q + num_f/den_f)·B^e` with `den_f = den·B^k` and
558        // `num_f = r·B^k + lo` (the remainder plus the dividend's sticky low part), where
559        // `|num_f| < den_f` and `q` has at most `precision + 1` digits.
560        let den_f = if k > 0 { shl_digits::<B>(&den, k) } else { den };
561        let num_f = if k > 0 {
562            shl_digits::<B>(&r, k) + lo
563        } else {
564            r
565        };
566
567        // An over-wide quotient (an exact division such as 15/3 at precision 2, or the p+1
568        // digit quotient the scaling above can produce) must be rounded to the precision —
569        // but in a *single* step: the dropped digit joins the fraction, so no information is
570        // double-rounded away. The dividend bound guarantees the quotient carries at most one
571        // extra digit, so exactly one digit is dropped here.
572        let repr = if qdigits > self.precision {
573            debug_assert_eq!(qdigits, self.precision + 1);
574            let (qh, ql) = split_digits::<B>(q, 1);
575            let e2 = e.saturating_add(1);
576
577            // The fraction is (ql + num_f/den_f)/B; ql (when nonzero) and num_f both carry
578            // the dividend's sign, and |num_f| < den_f, so the fraction's sign is ql's, or
579            // num_f's when ql = 0. Its magnitude against the half is
580            //   |ql·den_f + num_f|·2  vs  B·den_f,
581            // and since ql and num_f share their sign, |ql·den_f + num_f| rearranges to a
582            // comparison of |num_f| alone:
583            //   |num_f|·2  vs  (B − 2·|ql|)·den_f
584            // (halving both sides when B − 2·|ql| is even — always, for an even base).
585            // The comparison lives in the closure so the directed modes (which ignore it)
586            // never pay for it.
587            let ql_is_zero = ql.is_zero();
588            let low_sign = if ql_is_zero { num_f.sign() } else { ql.sign() };
589
590            if ql_is_zero && num_f.is_zero() {
591                Approximation::Exact(make_div_repr(sign_negative, qh, e2))
592            } else {
593                let adjust = R::round_low_part(&qh, low_sign, || {
594                    // ql is a single base-B digit (|ql| < B), so its arithmetic runs in Word;
595                    // on the comparison path below 2·|ql| ≤ B, so the subtraction cannot
596                    // underflow.
597                    let ql_mag: Word = ql.unsigned_abs().try_into().unwrap();
598                    if ql_mag > B - ql_mag {
599                        // 2·|ql| > B: the fraction's magnitude is past the half regardless of
600                        // the remainder
601                        Ordering::Greater
602                    } else {
603                        let diff = B - ql_mag * 2; // B − 2·|ql|
604                        if diff % 2 == 0 {
605                            // compare |num_f| against (B − 2·|ql|)/2 · den_f; the factors 0
606                            // and 1 (the base-2 hot path) skip the multiplication
607                            match diff / 2 {
608                                0 => num_f.abs_cmp(&IBig::ZERO),
609                                1 => num_f.abs_cmp(&den_f),
610                                f => num_f.abs_cmp(&(f * &den_f)),
611                            }
612                        } else {
613                            // odd base: double the remainder side instead of halving
614                            (&num_f + &num_f).abs_cmp(&(diff * &den_f))
615                        }
616                    }
617                });
618                Approximation::Inexact(make_div_repr(sign_negative, qh + adjust, e2), adjust)
619            }
620        } else if num_f.is_zero() {
621            Approximation::Exact(make_div_repr(sign_negative, q, e))
622        } else {
623            let adjust = R::round_ratio(&q, num_f, &den_f);
624            Approximation::Inexact(make_div_repr(sign_negative, q + adjust, e), adjust)
625        };
626        let repr = match repr {
627            Approximation::Exact(v) => Approximation::Exact(v.check_finite_exponent()?),
628            Approximation::Inexact(v, flag) => {
629                Approximation::Inexact(v.check_finite_exponent()?, flag)
630            }
631        };
632        Ok(repr)
633    }
634
635    pub(crate) fn repr_rem<const B: Word>(&self, lhs: Repr<B>, rhs: Repr<B>) -> Rounded<Repr<B>> {
636        assert_finite_operands(&lhs, &rhs);
637
638        let lhs_is_neg_zero = lhs.is_neg_zero();
639        let (lhs_sign, lhs_signif) = lhs.significand.into_parts();
640        let (rhs_sign, rhs_signif) = rhs.significand.into_parts();
641        let quotient_sign = lhs_sign * rhs_sign;
642
643        use core::cmp::Ordering;
644        let significand = match lhs.exponent.cmp(&rhs.exponent) {
645            Ordering::Equal => {
646                // one division yields both the truncated quotient and remainder;
647                // the quotient's parity breaks exact ties under HalfEven
648                let (quotient, r1) = lhs_signif.div_rem(&rhs_signif);
649                let r2 = rhs_signif - &r1;
650                if rem_rounds_away::<R>(quotient_sign, r1.is_zero(), r1.cmp(&r2), quotient.bit(0)) {
651                    IBig::from_parts(-lhs_sign, r2)
652                } else {
653                    IBig::from_parts(lhs_sign, r1)
654                }
655            }
656            Ordering::Greater => {
657                // if the least significant digit of lhs is higher than rhs, then we can
658                // align lhs to rhs and do simple modulo operations. Reduce modulo
659                // 2|rhs| rather than |rhs|: a single residue then yields the remainder
660                // candidates *and* the parity of the truncated quotient, without
661                // materializing the B^shift-aligned dividend (the exponent gap can be
662                // too large for that).
663                let modulo = ConstDivisor::new(&rhs_signif << 1);
664                let shift = (lhs.exponent - rhs.exponent) as usize;
665                let scaling = if B == 2 {
666                    (UBig::ONE << shift).into_ring(&modulo)
667                } else {
668                    UBig::from_word(B).into_ring(&modulo).pow(&shift.into())
669                };
670                let r_full = (lhs_signif.into_ring(&modulo) * scaling).residue(); // |lhs| mod 2|rhs|
671                let quotient_odd = r_full >= rhs_signif;
672                let r1 = if quotient_odd {
673                    r_full - &rhs_signif
674                } else {
675                    r_full
676                };
677                let r2 = rhs_signif - &r1;
678                if rem_rounds_away::<R>(quotient_sign, r1.is_zero(), r1.cmp(&r2), quotient_odd) {
679                    IBig::from_parts(-lhs_sign, r2)
680                } else {
681                    IBig::from_parts(lhs_sign, r1)
682                }
683            }
684            Ordering::Less => {
685                // otherwise we have to split lhs into two parts
686                let shift = (rhs.exponent - lhs.exponent) as usize;
687                let (hi, lo) = split_digits::<B>(lhs_signif.into(), shift);
688
689                // the truncated quotient is hi div |rhs| and the high part of the
690                // truncated remainder is hi mod |rhs| — both from one division
691                let (quotient, mut r1) = hi.div_rem(&rhs_signif);
692                let mut r2 = rhs_signif - &r1;
693
694                shl_digits_in_place::<B>(&mut r1, shift);
695                r1 += &lo;
696
697                shl_digits_in_place::<B>(&mut r2, shift);
698                r2 -= lo;
699
700                if rem_rounds_away::<R>(quotient_sign, r1.is_zero(), r1.cmp(&r2), quotient.bit(0)) {
701                    (-lhs_sign) * r2
702                } else {
703                    lhs_sign * r1
704                }
705            }
706        };
707
708        let exponent = lhs.exponent.min(rhs.exponent);
709        if significand.is_zero() {
710            // the sign of a zero remainder follows the dividend (±0)
711            Approximation::Exact(if lhs_is_neg_zero {
712                Repr::neg_zero()
713            } else {
714                Repr::zero()
715            })
716        } else {
717            match Repr::new(significand, exponent).check_finite_exponent() {
718                Ok(repr) => self.repr_round(repr),
719                Err(e) => match e {
720                    FpError::Overflow(sign) => {
721                        Approximation::Inexact(Repr::infinity_with_sign(sign), Rounding::NoOp)
722                    }
723                    FpError::Underflow(sign) => {
724                        Approximation::Inexact(Repr::zero_with_sign(sign), Rounding::NoOp)
725                    }
726                    _ => unreachable!(),
727                },
728            }
729        }
730    }
731
732    /// Divide two floating point numbers under this context.
733    ///
734    /// # Examples
735    ///
736    /// ```
737    /// # use core::str::FromStr;
738    /// # use dashu_base::ParseError;
739    /// # use dashu_float::DBig;
740    /// use dashu_base::Approximation::*;
741    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
742    ///
743    /// let context = Context::<HalfAway>::new(2);
744    /// let a = DBig::from_str("-1.234")?;
745    /// let b = DBig::from_str("6.789")?;
746    /// assert_eq!(context.div(&a.repr(), &b.repr()), Ok(Inexact(DBig::from_str("-0.18")?, NoOp)));
747    /// # Ok::<(), ParseError>(())
748    /// ```
749    ///
750    /// # Euclidean Division
751    ///
752    /// To do euclidean division on the float numbers (get an integer quotient and remainder, equivalent to C99's
753    /// `fmod` and `remquo`), please use the methods provided by traits [DivEuclid], [RemEuclid] and [DivRemEuclid].
754    ///
755    pub fn div<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> FpResult<FBig<R, B>> {
756        if lhs.is_infinite() || rhs.is_infinite() {
757            return Err(FpError::InfiniteInput);
758        }
759        if lhs.significand.is_zero() && rhs.significand.is_zero() {
760            return Err(FpError::Indeterminate); // 0/0
761        }
762
763        // No operand pre-shrinking here: `repr_div_any_width` bounds an over-wide dividend by
764        // an exact split, keeping the dropped digits as rounding information. Rounding the
765        // dividend *before* dividing — as this used to do — corrupts the result: the rounded
766        // dividend can divide exactly where the true one doesn't (a false `Exact` flag),
767        // land the quotient on a false midpoint, or invert the direction under directed
768        // modes.
769        Ok(self
770            .repr_div_any_width(lhs.clone(), rhs.clone())?
771            .map(|v| FBig::new(v, *self)))
772    }
773
774    /// Calculate the remainder of `lhs / rhs`, with the quotient rounded to an integer
775    /// by this context's rounding mode.
776    ///
777    /// The remainder is `r = lhs - n * rhs`, where `n` is the quotient rounded under
778    /// the mode (e.g. `Zero` truncates `n`, so `r` keeps the dividend's sign; the half
779    /// modes bound `|r|` by `|rhs|/2`). The remainder value is exact; the returned flag
780    /// only reports whether it had to be rounded down to the context precision.
781    ///
782    /// # Examples
783    ///
784    /// ```
785    /// # use core::str::FromStr;
786    /// # use dashu_base::ParseError;
787    /// # use dashu_float::{DBig, FBig};
788    /// use dashu_base::Approximation::*;
789    /// use dashu_float::{Context, round::{mode::{HalfAway, Zero}, Rounding::*}};
790    ///
791    /// let a = DBig::from_str("6.789")?;
792    /// let b = DBig::from_str("-1.234")?;
793    ///
794    /// // the quotient −5.503… rounds to −6 half away from zero
795    /// let context = Context::<HalfAway>::new(3);
796    /// assert_eq!(context.rem(&a.repr(), &b.repr()), Ok(Exact(DBig::from_str("-0.615")?)));
797    ///
798    /// // truncating the quotient instead: 6.789 = (−5)·(−1.234) + 0.619
799    /// let context = Context::<Zero>::new(3);
800    /// assert_eq!(
801    ///     context.rem(&a.repr(), &b.repr()),
802    ///     Ok(Exact(FBig::<Zero, 10>::from_str("0.619")?))
803    /// );
804    /// # Ok::<(), ParseError>(())
805    /// ```
806    pub fn rem<const B: Word>(&self, lhs: &Repr<B>, rhs: &Repr<B>) -> FpResult<FBig<R, B>> {
807        if lhs.is_infinite() || rhs.is_infinite() {
808            return Err(FpError::InfiniteInput);
809        }
810        Ok(self
811            .repr_rem(lhs.clone(), rhs.clone())
812            .map(|v| FBig::new(v, *self)))
813    }
814
815    /// Compute the multiplicative inverse of an `FBig`
816    ///
817    /// ```
818    /// # use core::str::FromStr;
819    /// # use dashu_base::ParseError;
820    /// # use dashu_float::DBig;
821    /// use dashu_base::Approximation::*;
822    /// use dashu_float::{Context, round::{mode::HalfAway, Rounding::*}};
823    ///
824    /// let context = Context::<HalfAway>::new(2);
825    /// let a = DBig::from_str("-1.234")?;
826    /// assert_eq!(context.inv(&a.repr()), Ok(Inexact(DBig::from_str("-0.81")?, NoOp)));
827    /// # Ok::<(), ParseError>(())
828    /// ```
829    #[inline]
830    pub fn inv<const B: Word>(&self, f: &Repr<B>) -> FpResult<FBig<R, B>> {
831        if f.is_infinite() {
832            return Err(FpError::InfiniteInput);
833        }
834        // inv(±0) = ±inf (produced as a value by repr_div)
835        Ok(self
836            .repr_div(Repr::one(), f.clone())?
837            .map(|v| FBig::new(v, *self)))
838    }
839}
840
841#[cfg(test)]
842mod tests {
843    use super::*;
844    use crate::round::mode;
845    use dashu_base::Approximation::*;
846
847    fn r2(sig: i32, exp: isize) -> Repr<2> {
848        Repr::new(sig.into(), exp)
849    }
850
851    // Regression tests for the rounding bugs reported in issue #100 (and two same-class value
852    // bugs found while investigating): `Context::div` used to round an over-wide dividend
853    // *before* dividing (perturbing ties, direction and the exactness flag), and `repr_div`
854    // returned exact quotients unreduced and rounded p+1-digit quotients on the wrong grid.
855    #[test]
856    fn test_div_quotient_reduced_and_flagged() {
857        // 15/3 = 5 needs three digits at precision 2: the exact quotient must be rounded
858        // (Zero -> 4), not returned unreduced with an Exact flag.
859        let r = Context::<mode::Zero>::new(2)
860            .div(&r2(15, 0), &r2(3, 0))
861            .unwrap();
862        assert!(matches!(r, Inexact(..)), "15/3 @ p2 must be inexact");
863        assert_eq!(r.value().repr(), &r2(1, 2));
864
865        // 5/1 at precision 1: same unreduced-quotient bug — 5 is not representable at p1.
866        let r = Context::<mode::Zero>::new(1)
867            .div(&r2(5, 0), &r2(1, 0))
868            .unwrap();
869        assert!(matches!(r, Inexact(..)), "5/1 @ p1 must be inexact");
870        assert_eq!(r.value().repr(), &r2(1, 2));
871
872        // 63/3 = 21 at precision 2 under Zero: the dividend is pre-split (not pre-rounded!),
873        // and 21 truncates onto the 2-digit grid as 1·2^4 = 16.
874        let v = Context::<mode::Zero>::new(2)
875            .div(&r2(63, 0), &r2(3, 0))
876            .unwrap()
877            .value();
878        assert_eq!(v.repr(), &r2(1, 4));
879    }
880
881    // The p+1-digit quotient rounding under a nearest mode, on both signs and an odd base.
882    // The negative non-binary cases regress-checked here were mis-rounded by an early
883    // version of the half comparison (a sign flip of B − 2·ql that should never happen:
884    // only |ql| enters the rearranged comparison).
885    #[test]
886    fn test_div_overwide_quotient_nearest_modes() {
887        // -21/2 = -10.5 in base 10 at precision 1: the p1 neighbours are -10 and -20 with
888        // midpoint -15, so -10.5 rounds to -10 under every mode except Away (-11 is NOT on
889        // the p1 grid; away from zero at this magnitude steps to -20).
890        let r10 = |sig: i32, exp: isize| Repr::<10>::new(IBig::from(sig), exp);
891        let v = Context::<mode::HalfEven>::new(1)
892            .div(&r10(-21, 0), &r10(2, 0))
893            .unwrap()
894            .value();
895        assert_eq!(v.repr(), &r10(-1, 1)); // -10
896
897        // positive mirror: 21/2 = 10.5 -> 10
898        let v = Context::<mode::HalfEven>::new(1)
899            .div(&r10(21, 0), &r10(2, 0))
900            .unwrap()
901            .value();
902        assert_eq!(v.repr(), &r10(1, 1));
903
904        // -95/2 = -47.5: neighbours -40/-50, midpoint -45 -> -50 under HalfEven
905        let v = Context::<mode::HalfEven>::new(1)
906            .div(&r10(-95, 0), &r10(2, 0))
907            .unwrap()
908            .value();
909        assert_eq!(v.repr(), &r10(-5, 1)); // -50
910
911        // -41/2 = -20.5: neighbours -20/-30, midpoint -25 -> -20 under HalfEven
912        let v = Context::<mode::HalfEven>::new(1)
913            .div(&r10(-41, 0), &r10(2, 0))
914            .unwrap()
915            .value();
916        assert_eq!(v.repr(), &r10(-2, 1)); // -20
917
918        // odd base (exercises the doubled-remainder comparison arm): -8/2 = -4 in base 3
919        // at precision 1 — neighbours -3/-6 with midpoint -4.5, so -4 rounds to -3;
920        // -11/2 = -5.5 is past the midpoint and rounds to -6
921        let r3 = |sig: i32, exp: isize| Repr::<3>::new(IBig::from(sig), exp);
922        let v = Context::<mode::HalfEven>::new(1)
923            .div(&r3(-8, 0), &r3(2, 0))
924            .unwrap()
925            .value();
926        assert_eq!(v.repr(), &r3(-1, 1)); // -3
927        let v = Context::<mode::HalfEven>::new(1)
928            .div(&r3(-11, 0), &r3(2, 0))
929            .unwrap()
930            .value();
931        assert_eq!(v.repr(), &r3(-2, 1)); // -6
932    }
933
934    #[test]
935    fn test_div_directed_modes_on_wide_dividend() {
936        // 7/-1 = -7 at precision 1 under Up must round toward +inf (-4). The old pre-shrink
937        // rounded the dividend 7 up to 8 first, and 8/-1 = -8 then rounded *down*.
938        let v = Context::<mode::Up>::new(1)
939            .div(&r2(7, 0), &r2(-1, 0))
940            .unwrap()
941            .value();
942        assert_eq!(v.repr(), &r2(-1, 2));
943
944        // 31/4 = 7.75 in base 3 at precision 1 under HalfEven: the old pre-shrink turned it
945        // into 30/4 = 7.5, a false midpoint that tied down to 6; the true value is above the
946        // 7.5 midpoint of 6 and 9, so it rounds to 9.
947        let v = Context::<mode::HalfEven>::new(1)
948            .div(&Repr::<3>::new(IBig::from(31), 0), &Repr::<3>::new(IBig::from(4), 0))
949            .unwrap()
950            .value();
951        assert_eq!(v.repr(), &Repr::<3>::new(IBig::from(1), 2));
952    }
953
954    // Two same-class value bugs in the p+1-digit quotient paths (found while investigating
955    // #100): the final rounding used the integer grid instead of the precision-digit grid.
956    #[test]
957    fn test_div_p1_digit_quotient_rounds_on_precision_grid() {
958        // 3/5 = 0.6 at precision 2 under HalfEven: 0.6·2^3 = 4.8 used to be rounded on the
959        // integer grid to 5 (= 0.625, not even representable at p2); the p2 neighbours are
960        // 0.5 and 0.75, so the result is 0.5.
961        let v = Context::<mode::HalfEven>::new(2)
962            .div(&r2(3, 0), &r2(5, 0))
963            .unwrap()
964            .value();
965        assert_eq!(v.repr(), &r2(1, -1));
966
967        // 14/3 = 4.67 at precision 2 under HalfEven: the quotient 100₂ carries p+1 digits;
968        // the p2 grid is 100/110 (4 and 6) with midpoint 5, so 4.67 rounds to 4 (the integer
969        // grid would give 5).
970        let v = Context::<mode::HalfEven>::new(2)
971            .div(&r2(14, 0), &r2(3, 0))
972            .unwrap()
973            .value();
974        assert_eq!(v.repr(), &r2(1, 2));
975
976        // 7/2 = 3.5 at precision 2 under HalfEven: the divisor normalizes to 1·2^1, so the
977        // exact quotient 111·2^-1 used to be returned unreduced. 3.5 is the exact midpoint of
978        // 11 (3) and 100 (4); ties to even gives 4.
979        let v = Context::<mode::HalfEven>::new(2)
980            .div(&r2(7, 0), &r2(2, 0))
981            .unwrap()
982            .value();
983        assert_eq!(v.repr(), &r2(1, 2));
984    }
985
986    // The dividend's sticky low digits (split off by the width bound) must participate in the
987    // rounding: they resolve exact ties and push directed modes the right way.
988    #[test]
989    fn test_div_sticky_low_digits() {
990        // 15·2^-1 / 5 = 1.5 at precision 1 under HalfEven: exact tie between 1 and 2 -> 2.
991        let v = Context::<mode::HalfEven>::new(1)
992            .div(&r2(15, -1), &r2(5, 0))
993            .unwrap()
994            .value();
995        assert_eq!(v.repr(), &r2(1, 1));
996
997        // Same input under Up: the tie pushes up to 2.
998        let v = Context::<mode::Up>::new(1)
999            .div(&r2(15, -1), &r2(5, 0))
1000            .unwrap()
1001            .value();
1002        assert_eq!(v.repr(), &r2(1, 1));
1003
1004        // 11/1 at precision 2 under HalfEven: the dividend 1011₂ is split at 1 digit; without
1005        // the sticky low bit the rounding would see an exact tie (10|1 -> even -> 10), with it
1006        // the fraction is 3/4 and the result is 1100₂ = 12.
1007        let v = Context::<mode::HalfEven>::new(2)
1008            .div(&r2(11, 0), &r2(1, 0))
1009            .unwrap()
1010            .value();
1011        assert_eq!(v.repr(), &r2(3, 2));
1012
1013        // 25·2^-1 / 5 = 2.5 at precision 1 under Up: the p1 neighbours are 2 and 4, and a
1014        // directed mode picks the one in its direction regardless of the (midpoint 3).
1015        let v = Context::<mode::Up>::new(1)
1016            .div(&r2(25, -1), &r2(5, 0))
1017            .unwrap()
1018            .value();
1019        assert_eq!(v.repr(), &r2(1, 2));
1020    }
1021
1022    #[test]
1023    fn test_div_by_zero_is_infinity() {
1024        let ctx = Context::<mode::HalfEven>::new(53);
1025        // finite / 0 = ±inf (a value, not an error); sign = XOR
1026        let pos = ctx.div::<2>(&r2(1, 0), &Repr::<2>::zero()).unwrap().value();
1027        assert!(pos.repr().is_infinite());
1028        assert_eq!(pos.repr().sign(), Sign::Positive);
1029
1030        let neg = ctx
1031            .div::<2>(&r2(-1, 0), &Repr::<2>::zero())
1032            .unwrap()
1033            .value();
1034        assert_eq!(neg.repr().sign(), Sign::Negative);
1035
1036        // 1 / -0 = -inf
1037        let neg2 = ctx
1038            .div::<2>(&r2(1, 0), &Repr::<2>::neg_zero())
1039            .unwrap()
1040            .value();
1041        assert_eq!(neg2.repr().sign(), Sign::Negative);
1042    }
1043
1044    #[test]
1045    fn test_zero_over_zero_is_indeterminate() {
1046        let ctx = Context::<mode::HalfEven>::new(53);
1047        assert_eq!(
1048            ctx.div::<2>(&Repr::<2>::zero(), &Repr::<2>::zero()),
1049            Err(FpError::Indeterminate)
1050        );
1051    }
1052
1053    #[test]
1054    fn test_inv_zero_is_infinity() {
1055        let ctx = Context::<mode::HalfEven>::new(53);
1056        let r = ctx.inv::<2>(&Repr::<2>::zero()).unwrap().value();
1057        assert!(r.repr().is_infinite());
1058        assert_eq!(r.repr().sign(), Sign::Positive);
1059    }
1060
1061    #[test]
1062    fn test_fbig_div_zero_produces_infinity() {
1063        // FBig convenience layer: 1 / 0 yields an infinity-valued FBig (no panic).
1064        let one = FBig::<mode::HalfEven>::try_from(1.0f64).unwrap();
1065        let zero = FBig::<mode::HalfEven>::try_from(0.0f64).unwrap();
1066        let inf = one / zero;
1067        assert!(inf.repr().is_infinite());
1068    }
1069
1070    #[test]
1071    #[should_panic]
1072    fn test_fbig_zero_over_zero_panics() {
1073        // 0 / 0 is indeterminate; the FBig layer panics.
1074        let zero = FBig::<mode::HalfEven>::try_from(0.0f64).unwrap();
1075        let _ = zero.clone() / zero;
1076    }
1077
1078    // The `FBig / FBig` operator routes through `unwrap_fp`, so an exponent underflow saturates to
1079    // the directed endpoint (not a mode-blind signed zero): 2^isize::MIN / 3 ≈ 2^(isize::MIN − 2)
1080    // underflows; Up → smallest positive, Down → +0.
1081    #[test]
1082    fn test_div_directed_underflow() {
1083        use dashu_int::IBig;
1084        let p = 53;
1085        let floor_up = FBig::<mode::Up, 2>::from_parts(IBig::ONE, isize::MIN)
1086            .with_precision(p)
1087            .value();
1088        let floor_down = FBig::<mode::Down, 2>::from_parts(IBig::ONE, isize::MIN)
1089            .with_precision(p)
1090            .value();
1091        let three_up = FBig::<mode::Up, 2>::from_parts(IBig::from(3), 0)
1092            .with_precision(p)
1093            .value();
1094        let three_down = FBig::<mode::Down, 2>::from_parts(IBig::from(3), 0)
1095            .with_precision(p)
1096            .value();
1097        let up = floor_up / &three_up;
1098        let down = floor_down / &three_down;
1099        assert_eq!(up.repr().significand(), &IBig::ONE);
1100        assert_eq!(up.repr().exponent(), isize::MIN);
1101        assert!(down.repr().is_pos_zero());
1102        assert!(up > down);
1103    }
1104}