Skip to main content

dashu_float/
div.rs

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