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}