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