Skip to main content

amari_core/gf2/
scalar.rs

1//! GF(2) scalar type — the Galois field with two elements.
2
3use core::fmt;
4use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
5
6/// Element of the Galois field GF(2) = {0, 1}.
7///
8/// The smallest finite field. Addition is XOR, multiplication is AND.
9/// Every nonzero element is its own inverse (both additive and multiplicative).
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
11#[repr(transparent)]
12pub struct GF2(u8);
13
14impl GF2 {
15    pub const ZERO: Self = GF2(0);
16    pub const ONE: Self = GF2(1);
17
18    /// Create from a u8 value (reduces mod 2).
19    #[inline]
20    #[must_use]
21    pub const fn new(value: u8) -> Self {
22        GF2(value & 1)
23    }
24
25    /// Create from a bool.
26    #[inline]
27    #[must_use]
28    pub const fn from_bool(value: bool) -> Self {
29        GF2(value as u8)
30    }
31
32    /// The underlying value (0 or 1).
33    #[inline]
34    #[must_use]
35    pub const fn value(self) -> u8 {
36        self.0
37    }
38
39    /// Whether this is the zero element.
40    #[inline]
41    #[must_use]
42    pub const fn is_zero_element(self) -> bool {
43        self.0 == 0
44    }
45
46    /// Whether this is the one (unity) element.
47    #[inline]
48    #[must_use]
49    pub const fn is_one(self) -> bool {
50        self.0 == 1
51    }
52
53    /// Multiplicative inverse. Returns None for zero.
54    #[inline]
55    #[must_use]
56    pub const fn inverse(self) -> Option<Self> {
57        if self.0 == 1 {
58            Some(self)
59        } else {
60            None
61        }
62    }
63}
64
65// --- Arithmetic trait impls ---
66
67// GF(2) arithmetic: addition = XOR, multiplication = AND. These are intentional.
68#[allow(clippy::suspicious_arithmetic_impl)]
69impl Add for GF2 {
70    type Output = Self;
71    #[inline]
72    fn add(self, rhs: Self) -> Self {
73        GF2(self.0 ^ rhs.0)
74    }
75}
76
77#[allow(clippy::suspicious_arithmetic_impl)]
78impl Sub for GF2 {
79    type Output = Self;
80    #[inline]
81    fn sub(self, rhs: Self) -> Self {
82        GF2(self.0 ^ rhs.0) // same as add in GF(2)
83    }
84}
85
86#[allow(clippy::suspicious_arithmetic_impl)]
87impl Mul for GF2 {
88    type Output = Self;
89    #[inline]
90    fn mul(self, rhs: Self) -> Self {
91        GF2(self.0 & rhs.0)
92    }
93}
94
95impl Neg for GF2 {
96    type Output = Self;
97    #[inline]
98    fn neg(self) -> Self {
99        self // -a = a in GF(2)
100    }
101}
102
103#[allow(clippy::suspicious_op_assign_impl)]
104impl AddAssign for GF2 {
105    #[inline]
106    fn add_assign(&mut self, rhs: Self) {
107        self.0 ^= rhs.0;
108    }
109}
110
111#[allow(clippy::suspicious_op_assign_impl)]
112impl SubAssign for GF2 {
113    #[inline]
114    fn sub_assign(&mut self, rhs: Self) {
115        self.0 ^= rhs.0;
116    }
117}
118
119#[allow(clippy::suspicious_op_assign_impl)]
120impl MulAssign for GF2 {
121    #[inline]
122    fn mul_assign(&mut self, rhs: Self) {
123        self.0 &= rhs.0;
124    }
125}
126
127// --- Conversion impls ---
128
129impl From<bool> for GF2 {
130    #[inline]
131    fn from(value: bool) -> Self {
132        GF2(value as u8)
133    }
134}
135
136impl From<u8> for GF2 {
137    #[inline]
138    fn from(value: u8) -> Self {
139        GF2(value & 1)
140    }
141}
142
143impl From<GF2> for bool {
144    #[inline]
145    fn from(value: GF2) -> Self {
146        value.0 != 0
147    }
148}
149
150impl From<GF2> for u8 {
151    #[inline]
152    fn from(value: GF2) -> Self {
153        value.0
154    }
155}
156
157// --- Display ---
158
159impl fmt::Display for GF2 {
160    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161        write!(f, "{}", self.0)
162    }
163}
164
165// --- num_traits ---
166
167impl num_traits::Zero for GF2 {
168    #[inline]
169    fn zero() -> Self {
170        GF2::ZERO
171    }
172    #[inline]
173    fn is_zero(&self) -> bool {
174        self.0 == 0
175    }
176}
177
178impl num_traits::One for GF2 {
179    #[inline]
180    fn one() -> Self {
181        GF2::ONE
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_field_addition() {
191        assert_eq!(GF2::ZERO + GF2::ZERO, GF2::ZERO);
192        assert_eq!(GF2::ZERO + GF2::ONE, GF2::ONE);
193        assert_eq!(GF2::ONE + GF2::ZERO, GF2::ONE);
194        assert_eq!(GF2::ONE + GF2::ONE, GF2::ZERO);
195    }
196
197    #[test]
198    fn test_field_multiplication() {
199        assert_eq!(GF2::ZERO * GF2::ZERO, GF2::ZERO);
200        assert_eq!(GF2::ZERO * GF2::ONE, GF2::ZERO);
201        assert_eq!(GF2::ONE * GF2::ZERO, GF2::ZERO);
202        assert_eq!(GF2::ONE * GF2::ONE, GF2::ONE);
203    }
204
205    #[test]
206    fn test_distributivity() {
207        for &a in &[GF2::ZERO, GF2::ONE] {
208            for &b in &[GF2::ZERO, GF2::ONE] {
209                for &c in &[GF2::ZERO, GF2::ONE] {
210                    assert_eq!(a * (b + c), a * b + a * c);
211                }
212            }
213        }
214    }
215
216    #[test]
217    fn test_self_inverse() {
218        // a + a = 0 for all a in GF(2)
219        assert_eq!(GF2::ZERO + GF2::ZERO, GF2::ZERO);
220        assert_eq!(GF2::ONE + GF2::ONE, GF2::ZERO);
221        // -a = a
222        assert_eq!(-GF2::ZERO, GF2::ZERO);
223        assert_eq!(-GF2::ONE, GF2::ONE);
224    }
225
226    #[test]
227    fn test_multiplicative_inverse() {
228        assert_eq!(GF2::ONE.inverse(), Some(GF2::ONE));
229        assert_eq!(GF2::ZERO.inverse(), None);
230    }
231
232    #[test]
233    fn test_conversions() {
234        assert_eq!(GF2::from(true), GF2::ONE);
235        assert_eq!(GF2::from(false), GF2::ZERO);
236        assert_eq!(GF2::new(5), GF2::ONE); // 5 & 1 = 1
237        assert_eq!(GF2::new(4), GF2::ZERO); // 4 & 1 = 0
238        assert!(bool::from(GF2::ONE));
239        assert!(!bool::from(GF2::ZERO));
240    }
241}