Skip to main content

karpal_algebra/
field.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::ring::Ring;
5
6/// A `Ring` with multiplicative inverses for all non-zero elements.
7pub trait Field: Ring {
8    fn reciprocal(self) -> Self;
9
10    fn div(self, other: Self) -> Self
11    where
12        Self: Sized,
13    {
14        self.mul(other.reciprocal())
15    }
16}
17
18impl Field for f32 {
19    fn reciprocal(self) -> Self {
20        1.0 / self
21    }
22}
23
24impl Field for f64 {
25    fn reciprocal(self) -> Self {
26        1.0 / self
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    macro_rules! assert_approx_eq {
34        ($a:expr, $b:expr) => {
35            assert_approx_eq!($a, $b, 1e-10)
36        };
37        ($a:expr, $b:expr, $eps:expr) => {
38            let (a, b) = ($a as f64, $b as f64);
39            assert!(
40                (a - b).abs() < $eps,
41                "assertion failed: |{} - {}| = {} >= {}",
42                a,
43                b,
44                (a - b).abs(),
45                $eps
46            );
47        };
48    }
49
50    #[test]
51    fn f64_reciprocal() {
52        assert_approx_eq!(2.0f64.reciprocal(), 0.5);
53        assert_approx_eq!(4.0f64.reciprocal(), 0.25);
54    }
55
56    #[test]
57    fn f64_div() {
58        assert_approx_eq!(10.0f64.div(4.0), 2.5);
59    }
60
61    #[test]
62    fn f32_reciprocal() {
63        assert!((2.0f32.reciprocal() - 0.5).abs() < 1e-6);
64    }
65}
66
67#[cfg(test)]
68mod law_tests {
69    use super::*;
70    use crate::semiring::Semiring;
71    use proptest::prelude::*;
72
73    proptest! {
74        #[test]
75        fn f64_multiplicative_inverse(a in proptest::num::f64::NORMAL.prop_filter("non-zero", |x| x.abs() > 1e-10)) {
76            let result = a.mul(a.reciprocal());
77            prop_assert!((result - 1.0).abs() < 1e-6, "a={}, a*a^-1={}", a, result);
78        }
79
80        #[test]
81        fn f64_div_is_mul_reciprocal(
82            a in proptest::num::f64::NORMAL.prop_filter("bounded", |x| x.abs() < 1e6),
83            b in proptest::num::f64::NORMAL.prop_filter("non-zero bounded", |x| x.abs() > 1e-10 && x.abs() < 1e6)
84        ) {
85            let left = a.div(b);
86            let right = a.mul(b.reciprocal());
87            prop_assert!((left - right).abs() < 1e-6, "a/b={}, a*b^-1={}", left, right);
88        }
89    }
90}