Skip to main content

la_stack/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![doc = include_str!("../README.md")]
4
5#[cfg(doc)]
6mod readme_doctests {
7    //! Executable versions of README examples.
8    /// ```rust
9    /// use la_stack::prelude::*;
10    ///
11    /// # fn main() -> Result<(), LaError> {
12    /// // This system requires pivoting (a[0][0] = 0), so it's a good LU demo.
13    /// let a = Matrix::<5>::try_from_rows([
14    ///     [0.0, 1.0, 1.0, 1.0, 1.0],
15    ///     [1.0, 0.0, 1.0, 1.0, 1.0],
16    ///     [1.0, 1.0, 0.0, 1.0, 1.0],
17    ///     [1.0, 1.0, 1.0, 0.0, 1.0],
18    ///     [1.0, 1.0, 1.0, 1.0, 0.0],
19    /// ])?;
20    ///
21    /// let b = Vector::<5>::try_new([14.0, 13.0, 12.0, 11.0, 10.0])?;
22    ///
23    /// let lu = a.lu(DEFAULT_SINGULAR_TOL)?;
24    /// let x = lu.solve(b)?.into_array();
25    ///
26    /// // Floating-point rounding is expected; compare with a tolerance.
27    /// let expected = [1.0, 2.0, 3.0, 4.0, 5.0];
28    /// for (x_i, e_i) in x.iter().zip(expected.iter()) {
29    ///     assert!((*x_i - *e_i).abs() <= 1e-12);
30    /// }
31    /// # Ok(())
32    /// # }
33    /// ```
34    fn solve_5x5_example() {}
35
36    /// ```rust
37    /// use la_stack::prelude::*;
38    ///
39    /// # fn main() -> Result<(), LaError> {
40    /// // This matrix is symmetric positive-definite (A = L*L^T) so LDLT works without pivoting.
41    /// let a = Matrix::<5>::try_from_rows([
42    ///     [1.0, 1.0, 0.0, 0.0, 0.0],
43    ///     [1.0, 2.0, 1.0, 0.0, 0.0],
44    ///     [0.0, 1.0, 2.0, 1.0, 0.0],
45    ///     [0.0, 0.0, 1.0, 2.0, 1.0],
46    ///     [0.0, 0.0, 0.0, 1.0, 2.0],
47    /// ])?;
48    ///
49    /// let ldlt = match a.ldlt(DEFAULT_SINGULAR_TOL) {
50    ///     Ok(ldlt) => ldlt,
51    ///     Err(err @ LaError::Asymmetric { row, col, .. }) => {
52    ///         eprintln!("LDLT requires symmetry; first mismatch at ({row}, {col})");
53    ///         return Err(err);
54    ///     }
55    ///     Err(err) => return Err(err),
56    /// };
57    ///
58    /// let det = ldlt.det()?;
59    /// assert!((det - 1.0).abs() <= 1e-12);
60    /// # Ok(())
61    /// # }
62    /// ```
63    fn det_5x5_ldlt_example() {}
64
65    /// ```rust
66    /// use la_stack::prelude::*;
67    ///
68    /// // Evaluated entirely at compile time — no runtime cost.
69    /// const DET: Result<Option<f64>, LaError> = match Matrix::<4>::try_from_rows([
70    ///     [2.0, 0.0, 0.0, 0.0],
71    ///     [0.0, 3.0, 0.0, 0.0],
72    ///     [0.0, 0.0, 5.0, 0.0],
73    ///     [0.0, 0.0, 0.0, 7.0],
74    /// ]) {
75    ///     Ok(matrix) => matrix.det_direct(),
76    ///     Err(err) => Err(err),
77    /// };
78    ///
79    /// # fn main() -> Result<(), LaError> {
80    /// assert_eq!(DET?, Some(210.0));
81    /// # Ok(())
82    /// # }
83    /// ```
84    fn det_direct_4x4_const_example() {}
85
86    #[cfg(feature = "exact")]
87    /// ```rust
88    /// use la_stack::prelude::*;
89    ///
90    /// # fn main() -> Result<(), LaError> {
91    /// // Exact determinant
92    /// let m = Matrix::<3>::try_from_rows([
93    ///     [1.0, 2.0, 3.0],
94    ///     [4.0, 5.0, 6.0],
95    ///     [7.0, 8.0, 9.0],
96    /// ])?;
97    /// assert_eq!(m.det_sign_exact()?, 0); // exactly singular
98    ///
99    /// let det = m.det_exact()?;
100    /// assert_eq!(det, BigRational::from_integer(0.into())); // exact zero
101    /// let det_f64 = m.det_exact_f64()?;
102    /// assert_eq!(det_f64, 0.0);
103    ///
104    /// // If strict exact-to-f64 conversion would require rounding, opt in
105    /// // explicitly with the rounded API.
106    /// let inexact = Matrix::<2>::try_from_rows([
107    ///     [1.0 + f64::EPSILON, 0.0],
108    ///     [0.0, 1.0 - f64::EPSILON],
109    /// ])?;
110    /// let rounded_det = match inexact.det_exact_f64() {
111    ///     Ok(det) => det,
112    ///     Err(err) if err.requires_rounding() => inexact.det_exact_rounded_f64()?,
113    ///     Err(err) => return Err(err),
114    /// };
115    /// assert_eq!(rounded_det.to_bits(), 1.0f64.to_bits());
116    ///
117    /// // If the exact determinant cannot fit in f64, keep the BigRational value.
118    /// let big = f64::MAX / 2.0;
119    /// let huge = Matrix::<3>::try_from_rows([
120    ///     [0.0, 0.0, 1.0],
121    ///     [big, 0.0, 1.0],
122    ///     [0.0, big, 1.0],
123    /// ])?;
124    /// let huge_det = huge.det_exact()?;
125    /// assert_eq!(
126    ///     huge.det_exact_f64()
127    ///         .err()
128    ///         .and_then(|err| err.unrepresentable_reason()),
129    ///     Some(UnrepresentableReason::NotFinite)
130    /// );
131    /// println!("exact determinant = {huge_det}");
132    ///
133    /// // Exact linear system solve
134    /// let a = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]])?;
135    /// let b = Vector::<2>::try_new([5.0, 11.0])?;
136    /// let x = a.solve_exact_f64(b)?.into_array();
137    /// assert!((x[0] - 1.0).abs() <= f64::EPSILON);
138    /// assert!((x[1] - 2.0).abs() <= f64::EPSILON);
139    /// # Ok(())
140    /// # }
141    /// ```
142    fn exact_arithmetic_example() {}
143}
144
145mod error;
146#[cfg(feature = "exact")]
147mod exact;
148mod ldlt;
149mod lu;
150mod matrix;
151mod tolerance;
152mod vector;
153
154#[cfg(feature = "exact")]
155pub use num_bigint::BigInt;
156#[cfg(feature = "exact")]
157pub use num_rational::BigRational;
158#[cfg(feature = "exact")]
159pub use num_traits::{FromPrimitive, Signed, ToPrimitive};
160
161// ---------------------------------------------------------------------------
162// Error-bound constants for `Matrix::det_errbound()`.
163//
164// For `D ∈ {2, 3, 4}`, `Matrix::det_direct()` evaluates the Leibniz expansion
165// of the determinant as a tree of f64 multiplies and fused multiply-adds
166// (FMAs).  Following Shewchuk's error-analysis methodology (REFERENCES.md
167// [8]), the absolute error of that computation is bounded by
168//
169//     |det_direct(A) - det_exact(A)|  ≤  ERR_COEFF_D · p(|A|)
170//
171// where `p(|A|)` is the **absolute Leibniz sum**
172//
173//     p(|A|) = Σ_σ ∏ᵢ |A[i, σ(i)]|,
174//
175// i.e. the same cofactor-expansion tree as `det_direct` but with each
176// entry replaced by its magnitude.  Note that `p(|A|)` is *not* the
177// combinatorial matrix permanent — the name "permanent" appears in the
178// source for brevity and to match the cited literature.
179//
180// Each constant has the shape `a · EPS + b · EPS²`: the linear term bounds
181// the first-order rounding and the quadratic term absorbs the interaction
182// of errors in nested FMAs.  The coefficients `a` and `b` are conservative
183// over-estimates derived from the longest dependency chain of `det_direct`
184// at that dimension.
185//
186// These constants are NOT feature-gated — they rely only on f64 arithmetic
187// and are useful for adaptive-precision logic even without the `exact`
188// feature.  Most callers should prefer `Matrix::det_errbound()`, which
189// applies these constants to the actual matrix; the raw constants are
190// exposed for advanced use cases (composing the bound with a pre-reduced
191// permanent, rolling a custom adaptive filter, etc.).  See
192// `Matrix::det_sign_exact()` (behind the `exact` feature) for the
193// reference adaptive-filter that consumes these internally.
194// ---------------------------------------------------------------------------
195
196const EPS: f64 = f64::EPSILON; // 2^-52
197
198/// Absolute error coefficient for [`Matrix::<2>::det_direct`](crate::Matrix::det_direct).
199///
200/// This constant is not a caller-tuned tolerance. It is the dimension-specific
201/// multiplier that turns the matrix's absolute Leibniz sum into a conservative
202/// bound on floating-point roundoff in the closed-form 2×2 determinant formula.
203///
204/// For any 2×2 matrix `A = [[a, b], [c, d]]` with finite f64 entries,
205///
206/// ```text
207/// |A.det_direct() - det_exact(A)|  ≤  ERR_COEFF_2 · (|a·d| + |b·c|)
208/// ```
209///
210/// `det_direct` evaluates `a·d - b·c` as one multiply followed by one FMA
211/// (2 rounding events); the linear `3·EPS` term bounds those roundings
212/// and the quadratic `16·EPS²` term is a conservative cushion for their
213/// interaction.  Derivation follows Shewchuk's framework; see
214/// `REFERENCES.md` \[8\].
215///
216/// Prefer [`Matrix::det_errbound`](crate::Matrix::det_errbound) unless
217/// you already have the absolute-Leibniz sum available; see
218/// `Matrix::det_sign_exact` (under the `exact` feature) for the reference
219/// adaptive-precision filter.
220///
221/// # Example
222/// ```
223/// use la_stack::prelude::*;
224///
225/// # fn main() -> Result<(), LaError> {
226/// let m = Matrix::<2>::try_from_rows([[1.0, 2.0], [3.0, 4.0]])?;
227/// let Some(det) = m.det_direct()? else {
228///     return Ok(());
229/// };
230/// assert_eq!(det, -2.0);
231/// // Compute the bound from the raw constant for illustration; most
232/// // callers would match on `m.det_errbound()?` instead.
233/// let p = (1.0_f64 * 4.0).abs() + (2.0_f64 * 3.0).abs();
234/// let bound = ERR_COEFF_2 * p;
235/// if det.abs() > bound {
236///     // The f64 sign is provably correct without exact arithmetic.
237/// }
238/// # Ok(())
239/// # }
240/// ```
241pub const ERR_COEFF_2: f64 = 3.0 * EPS + 16.0 * EPS * EPS;
242
243/// Absolute error coefficient for [`Matrix::<3>::det_direct`](crate::Matrix::det_direct).
244///
245/// This constant is not a caller-tuned tolerance. It is the dimension-specific
246/// multiplier that turns the matrix's absolute Leibniz sum into a conservative
247/// bound on floating-point roundoff in the closed-form 3×3 determinant formula.
248///
249/// For any 3×3 matrix `A` with finite f64 entries,
250///
251/// ```text
252/// |A.det_direct() - det_exact(A)|  ≤  ERR_COEFF_3 · p(|A|)
253/// ```
254///
255/// where `p(|A|)` is the absolute Leibniz sum (the same cofactor
256/// expansion as `det_direct` but with `|·|` at every leaf).
257/// `det_direct` for D=3 uses three 2×2 FMA minors combined by a nested
258/// FMA, yielding the `8·EPS + 64·EPS²` bound.  See `REFERENCES.md`
259/// \[8\] for the Shewchuk framework these bounds follow.
260///
261/// Prefer [`Matrix::det_errbound`](crate::Matrix::det_errbound) over this
262/// constant for typical use; see [`ERR_COEFF_2`] for a worked example.
263pub const ERR_COEFF_3: f64 = 8.0 * EPS + 64.0 * EPS * EPS;
264
265/// Absolute error coefficient for [`Matrix::<4>::det_direct`](crate::Matrix::det_direct).
266///
267/// This constant is not a caller-tuned tolerance. It is the dimension-specific
268/// multiplier that turns the matrix's absolute Leibniz sum into a conservative
269/// bound on floating-point roundoff in the closed-form 4×4 determinant formula.
270///
271/// For any 4×4 matrix `A` with finite f64 entries,
272///
273/// ```text
274/// |A.det_direct() - det_exact(A)|  ≤  ERR_COEFF_4 · p(|A|)
275/// ```
276///
277/// where `p(|A|)` is the absolute Leibniz sum.  `det_direct` for D=4
278/// hoists six 2×2 minors, combines them into four 3×3 cofactors, then
279/// reduces those with an FMA row combination, yielding the
280/// `12·EPS + 128·EPS²` bound.  See `REFERENCES.md` \[8\] for the
281/// Shewchuk framework these bounds follow.
282///
283/// Prefer [`Matrix::det_errbound`](crate::Matrix::det_errbound) over this
284/// constant for typical use; see [`ERR_COEFF_2`] for a worked example.
285pub const ERR_COEFF_4: f64 = 12.0 * EPS + 128.0 * EPS * EPS;
286
287/// Largest dimension supported by [`try_with_stack_matrix!`].
288///
289/// The crate can represent `Matrix<D>` for any compile-time `D`, but runtime
290/// dispatch must enumerate a finite set of concrete stack types.  Dimensions
291/// `0..=7` cover downstream geometric predicate matrices while keeping the
292/// dispatch surface explicit.
293pub const MAX_STACK_MATRIX_DISPATCH_DIM: usize = 7;
294
295pub use error::{LaError, UnrepresentableReason};
296pub use ldlt::Ldlt;
297pub use lu::Lu;
298pub use matrix::Matrix;
299pub(crate) use tolerance::LDLT_SYMMETRY_REL_TOL;
300pub use tolerance::{DEFAULT_SINGULAR_TOL, Tolerance};
301pub use vector::Vector;
302
303/// Fallibly dispatch a runtime dimension to a concrete stack-allocated matrix.
304///
305/// The macro creates a zero matrix with type `Matrix<N>` for the selected
306/// runtime dimension `N`, then evaluates the supplied closure body.  Supported
307/// runtime dimensions run from `0` through [`MAX_STACK_MATRIX_DISPATCH_DIM`].
308/// Unsupported dimensions return
309/// `Err(LaError::UnsupportedDimension { requested, max })` converted with
310/// `From<LaError>`, so downstream crates can use their own public error type.
311///
312/// # Errors
313/// Returns [`LaError::UnsupportedDimension`] (converted through `From<LaError>`)
314/// when the requested runtime dimension is greater than
315/// [`MAX_STACK_MATRIX_DISPATCH_DIM`].  The closure body may return any other
316/// error representable by its declared `Result` type.
317///
318/// # Examples
319/// ```
320/// use la_stack::prelude::*;
321///
322/// # fn main() -> Result<(), LaError> {
323/// let requested = 2usize;
324/// let det = try_with_stack_matrix!(requested, |mut m| -> Result<f64, LaError> {
325///     m.set_checked(0, 0, 1.0)?;
326///     m.set_checked(1, 1, 1.0)?;
327///     m.det()
328/// })?;
329///
330/// assert_eq!(det, 1.0);
331/// # Ok(())
332/// # }
333/// ```
334#[macro_export]
335macro_rules! try_with_stack_matrix {
336    ($dim:expr, |$matrix:ident| -> $ret:ty $body:block $(,)?) => {{
337        let __la_stack_requested_dim: usize = $dim;
338        match __la_stack_requested_dim {
339            0 => $crate::try_with_stack_matrix!(@arm 0, $matrix, $ret, $body),
340            1 => $crate::try_with_stack_matrix!(@arm 1, $matrix, $ret, $body),
341            2 => $crate::try_with_stack_matrix!(@arm 2, $matrix, $ret, $body),
342            3 => $crate::try_with_stack_matrix!(@arm 3, $matrix, $ret, $body),
343            4 => $crate::try_with_stack_matrix!(@arm 4, $matrix, $ret, $body),
344            5 => $crate::try_with_stack_matrix!(@arm 5, $matrix, $ret, $body),
345            6 => $crate::try_with_stack_matrix!(@arm 6, $matrix, $ret, $body),
346            7 => $crate::try_with_stack_matrix!(@arm 7, $matrix, $ret, $body),
347            requested => Err(::core::convert::From::from(
348                $crate::LaError::unsupported_dimension(
349                    requested,
350                    $crate::MAX_STACK_MATRIX_DISPATCH_DIM,
351                ),
352            )),
353        }
354    }};
355    ($dim:expr, |mut $matrix:ident| -> $ret:ty $body:block $(,)?) => {{
356        let __la_stack_requested_dim: usize = $dim;
357        match __la_stack_requested_dim {
358            0 => $crate::try_with_stack_matrix!(@arm_mut 0, $matrix, $ret, $body),
359            1 => $crate::try_with_stack_matrix!(@arm_mut 1, $matrix, $ret, $body),
360            2 => $crate::try_with_stack_matrix!(@arm_mut 2, $matrix, $ret, $body),
361            3 => $crate::try_with_stack_matrix!(@arm_mut 3, $matrix, $ret, $body),
362            4 => $crate::try_with_stack_matrix!(@arm_mut 4, $matrix, $ret, $body),
363            5 => $crate::try_with_stack_matrix!(@arm_mut 5, $matrix, $ret, $body),
364            6 => $crate::try_with_stack_matrix!(@arm_mut 6, $matrix, $ret, $body),
365            7 => $crate::try_with_stack_matrix!(@arm_mut 7, $matrix, $ret, $body),
366            requested => Err(::core::convert::From::from(
367                $crate::LaError::unsupported_dimension(
368                    requested,
369                    $crate::MAX_STACK_MATRIX_DISPATCH_DIM,
370                ),
371            )),
372        }
373    }};
374    (@arm $d:literal, $matrix:ident, $ret:ty, $body:block) => {{
375        let __la_stack_body = |$matrix: $crate::Matrix<$d>| -> $ret { $body };
376        __la_stack_body($crate::Matrix::<$d>::zero())
377    }};
378    (@arm_mut $d:literal, $matrix:ident, $ret:ty, $body:block) => {{
379        let __la_stack_body = |mut $matrix: $crate::Matrix<$d>| -> $ret { $body };
380        __la_stack_body($crate::Matrix::<$d>::zero())
381    }};
382}
383
384/// Common imports for ergonomic usage.
385///
386/// This prelude re-exports the primary types and constants: [`Matrix`],
387/// [`Vector`], [`Lu`], [`Ldlt`], [`Tolerance`], [`LaError`],
388/// [`UnrepresentableReason`], [`DEFAULT_SINGULAR_TOL`], and the determinant
389/// error bound coefficients [`ERR_COEFF_2`], [`ERR_COEFF_3`], and
390/// [`ERR_COEFF_4`]. It also re-exports [`MAX_STACK_MATRIX_DISPATCH_DIM`] and
391/// [`try_with_stack_matrix!`] for runtime-to-const matrix dispatch.
392///
393/// When the `exact` feature is enabled, `BigInt` and `BigRational` are also
394/// re-exported so callers can construct exact values (e.g. as the expected
395/// result of `Matrix::det_exact`) without adding `num-bigint` / `num-rational`
396/// to their own dependencies. The most commonly needed `num-traits` items are
397/// re-exported alongside them: `FromPrimitive` for `BigRational::from_f64` /
398/// `from_i64`, `ToPrimitive` for `BigRational::to_f64` / `to_i64`, and `Signed`
399/// for `.is_positive()` / `.is_negative()` / `.abs()`.
400pub mod prelude {
401    pub use crate::{
402        DEFAULT_SINGULAR_TOL, ERR_COEFF_2, ERR_COEFF_3, ERR_COEFF_4, LaError, Ldlt, Lu,
403        MAX_STACK_MATRIX_DISPATCH_DIM, Matrix, Tolerance, UnrepresentableReason, Vector,
404        try_with_stack_matrix,
405    };
406
407    #[cfg(feature = "exact")]
408    pub use crate::{BigInt, BigRational, FromPrimitive, Signed, ToPrimitive};
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414
415    use approx::assert_abs_diff_eq;
416
417    mod prelude_tests {
418        use approx::assert_abs_diff_eq;
419
420        use crate::prelude::*;
421
422        #[test]
423        fn prelude_reexports_compile_and_work() -> Result<(), LaError> {
424            // Use the items so we know they are in scope and usable.
425            let m = Matrix::<2>::identity();
426            let v = Vector::<2>::try_new([1.0, 2.0])?;
427            let tol = Tolerance::new(0.0)?;
428            assert_abs_diff_eq!(tol.get(), 0.0, epsilon = 0.0);
429            assert_abs_diff_eq!(m.inf_norm()?, 1.0, epsilon = 0.0);
430            assert_abs_diff_eq!(v.norm2_sq()?, 5.0, epsilon = 0.0);
431            let _ = m.lu(DEFAULT_SINGULAR_TOL)?.solve(v)?;
432            let _ = m.ldlt(DEFAULT_SINGULAR_TOL)?.solve(v)?;
433            assert_eq!(
434                LaError::unrepresentable(None, UnrepresentableReason::RequiresRounding),
435                LaError::Unrepresentable {
436                    index: None,
437                    reason: UnrepresentableReason::RequiresRounding,
438                }
439            );
440            assert_eq!(MAX_STACK_MATRIX_DISPATCH_DIM, 7);
441            Ok(())
442        }
443    }
444
445    macro_rules! gen_stack_matrix_dispatch_tests {
446        ($d:literal) => {
447            pastey::paste! {
448                #[test]
449                fn [<try_with_stack_matrix_dispatches_ $d d>]() {
450                    let requested = $d;
451                    let got = try_with_stack_matrix!(requested, |mut m| -> Result<usize, LaError> {
452                        if $d > 0 {
453                            m.set_checked($d - 1, $d - 1, f64::from($d))?;
454                            assert_abs_diff_eq!(
455                                m.get_checked($d - 1, $d - 1)?,
456                                f64::from($d),
457                                epsilon = 0.0
458                            );
459                        }
460                        Ok($d)
461                    });
462
463                    assert_eq!(got, Ok($d));
464                }
465            }
466        };
467    }
468
469    gen_stack_matrix_dispatch_tests!(2);
470    gen_stack_matrix_dispatch_tests!(3);
471    gen_stack_matrix_dispatch_tests!(4);
472    gen_stack_matrix_dispatch_tests!(5);
473    gen_stack_matrix_dispatch_tests!(6);
474    gen_stack_matrix_dispatch_tests!(7);
475
476    #[test]
477    fn try_with_stack_matrix_supports_zero_dimension() {
478        let got = try_with_stack_matrix!(0usize, |m| -> Result<Option<f64>, LaError> {
479            m.det_direct()
480        });
481
482        assert_eq!(got, Ok(Some(1.0)));
483    }
484
485    #[test]
486    fn try_with_stack_matrix_reports_unsupported_dimension() {
487        let got = try_with_stack_matrix!(8usize, |m| -> Result<f64, LaError> { m.det() });
488
489        assert_eq!(
490            got,
491            Err(LaError::UnsupportedDimension {
492                requested: 8,
493                max: MAX_STACK_MATRIX_DISPATCH_DIM,
494            })
495        );
496    }
497
498    #[derive(Debug, PartialEq)]
499    struct DownstreamError(LaError);
500
501    impl From<LaError> for DownstreamError {
502        fn from(err: LaError) -> Self {
503            Self(err)
504        }
505    }
506
507    #[test]
508    fn try_with_stack_matrix_converts_unsupported_dimension_error() {
509        let got = try_with_stack_matrix!(9usize, |m| -> Result<usize, DownstreamError> {
510            assert_abs_diff_eq!(m.inf_norm()?, 0.0, epsilon = 0.0);
511            Ok(0)
512        });
513
514        assert_eq!(
515            got,
516            Err(DownstreamError(LaError::UnsupportedDimension {
517                requested: 9,
518                max: MAX_STACK_MATRIX_DISPATCH_DIM,
519            }))
520        );
521    }
522
523    /// Exercise every exact-feature re-export via the prelude so a future
524    /// refactor that drops one (e.g. removing `Signed` from the prelude
525    /// list) fails to compile rather than silently breaking downstream.
526    #[cfg(feature = "exact")]
527    #[test]
528    fn prelude_exact_reexports_compile_and_work() {
529        use crate::prelude::*;
530
531        // `BigInt` and `BigRational` constructors.
532        let n = BigInt::from(7);
533        let r = BigRational::from_integer(n.clone());
534        assert_eq!(*r.numer(), n);
535
536        // `FromPrimitive::from_f64` / `from_i64` on `BigRational`.
537        let half = BigRational::new(BigInt::from(1), BigInt::from(2));
538        let two = BigRational::from_integer(BigInt::from(2));
539        assert_eq!(BigRational::from_f64(0.5), Some(half.clone()));
540        assert_eq!(BigRational::from_i64(2), Some(two.clone()));
541        assert_eq!(
542            half.clone() + half.clone(),
543            BigRational::from_integer(BigInt::from(1))
544        );
545
546        // `Signed::is_positive` / `is_negative` / `abs`.
547        assert!(half.is_positive());
548        assert!(!half.is_negative());
549        let neg = -half.clone();
550        assert!(neg.is_negative());
551        assert_eq!(neg.abs(), half);
552
553        // `ToPrimitive::to_f64` / `to_i64`.
554        assert_eq!(half.to_f64(), Some(0.5));
555        assert_eq!(two.to_i64(), Some(2));
556    }
557}