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::<_>("ient_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}