Skip to main content

comnum_math/
gf2m.rs

1//! The Galois fields $\mathrm{GF}(2^{m})$: the arithmetic on which `comnum_fec` builds the
2//! BCH and Reed–Solomon codes.
3//!
4//! # Model
5//!
6//! The field is generated by a primitive element $\alpha$, a root of a primitive
7//! polynomial $p(x)$ of degree $m$ over $\mathrm{GF}(2)$: its $2^{m} - 1$ non-zero
8//! elements are the powers $\alpha^{0}, \dots, \alpha^{2^{m}-2}$. An element is stored as
9//! the $m$ bits of its polynomial representation (bit $i$ the coefficient of
10//! $\alpha^{i}$), so that the addition is the exclusive or, and a table of the powers
11//! and of the logarithms gives the product
12//!
13//! $$
14//! \alpha^{i} \alpha^{j} = \alpha^{(i + j) \bmod (2^{m} - 1)}.
15//! $$
16//!
17//! The minimal polynomial of $\alpha^{i}$ is the product over its conjugates,
18//! $m_{i}(x) = \prod_{j} \left(x - \alpha^{i 2^{j}}\right)$, a binary polynomial.
19//!
20//! [`primitive_polynomial`] gives the conventional primitive polynomial of every degree
21//! 2 to 16: the ones of the tables of Lin & Costello (Table 2.7) and of the coding
22//! standards (DVB, CD, QR codes use $x^{8} + x^{4} + x^{3} + x^{2} + 1$ for $m = 8$).
23//!
24//! # Examples
25//! ```
26//! use comnum_math::gf2m::{Field, primitive_polynomial};
27//! // GF(16) with p(x) = 1 + x + x⁴: α⁴ = α + 1 = 0b0011
28//! let f = Field::new(4, primitive_polynomial(4).unwrap())?;
29//! assert_eq!(f.pow(4), 0b0011);
30//! assert_eq!(f.mul(f.pow(7), f.pow(12)), f.pow(4));          // α⁷ α¹² = α¹⁹ = α⁴
31//! assert_eq!(f.minimal_polynomial(1), vec![1, 1, 0, 0, 1]);  // p(x) itself
32//! # Ok::<(), comnum_core::Error>(())
33//! ```
34//!
35//! # References
36//!
37//! 1. S. Lin and D. J. Costello, *Error Control Coding*, 2nd ed., Pearson, 2004, ch. 2
38//!    (Table 2.7: primitive polynomials, Table 2.8: GF($2^{4}$)).
39
40use comnum_core::{Result, ensure};
41
42/// The conventional primitive polynomial of degree `m` (bit $i$ the coefficient of
43/// $x^{i}$) for $2 \le m \le 16$, `None` otherwise.
44///
45/// Lin & Costello, Table 2.7, except $m = 8$ where the polynomial of the coding
46/// standards (ETSI EN 300 744, ISO/IEC 18004, the compact disc) is given,
47/// $x^{8} + x^{4} + x^{3} + x^{2} + 1$.
48pub fn primitive_polynomial(m: u32) -> Option<u32> {
49    const POLYS: [u32; 15] = [
50        0x7,     // x² + x + 1
51        0xB,     // x³ + x + 1
52        0x13,    // x⁴ + x + 1
53        0x25,    // x⁵ + x² + 1
54        0x43,    // x⁶ + x + 1
55        0x89,    // x⁷ + x³ + 1
56        0x11D,   // x⁸ + x⁴ + x³ + x² + 1
57        0x211,   // x⁹ + x⁴ + 1
58        0x409,   // x¹⁰ + x³ + 1
59        0x805,   // x¹¹ + x² + 1
60        0x1053,  // x¹² + x⁶ + x⁴ + x + 1
61        0x201B,  // x¹³ + x⁴ + x³ + x + 1
62        0x4443,  // x¹⁴ + x¹⁰ + x⁶ + x + 1
63        0x8003,  // x¹⁵ + x + 1
64        0x1100B, // x¹⁶ + x¹² + x³ + x + 1
65    ];
66    (2..=16).contains(&m).then(|| POLYS[m as usize - 2])
67}
68
69/// The Galois field $\mathrm{GF}(2^{m})$ by exponential and logarithm tables.
70#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct Field {
72    m: u32,
73    exp: Vec<u32>,
74    log: Vec<u32>,
75}
76
77impl Field {
78    /// The field of degree `m` (2 to 20) generated by the primitive polynomial `poly`
79    /// (bit $i$ the coefficient of $x^{i}$).
80    ///
81    /// # Errors
82    /// [`comnum_core::Error::Config`] for a degree outside 2 to 20, a polynomial of another
83    /// degree, or one that is not primitive (its powers of $x$ do not run through every
84    /// non-zero element).
85    pub fn new(m: u32, poly: u32) -> Result<Field> {
86        ensure((2..=20).contains(&m), || format!("GF(2^m): the field degree must be 2 to 20, got {m}"))?;
87        ensure(poly >> m == 1, || format!("GF(2^m): {poly:#x} is not a polynomial of degree {m}"))?;
88        let q = 1usize << m;
89        let mut exp = vec![0u32; 2 * q];
90        let mut log = vec![0u32; q];
91        let mut x = 1u32;
92        for (i, e) in exp.iter_mut().enumerate().take(q - 1) {
93            *e = x;
94            ensure(i == 0 || x != 1, || format!("GF(2^m): {poly:#x} is not a primitive polynomial of degree {m}"))?;
95            log[x as usize] = i as u32;
96            x <<= 1;
97            if x & (1 << m) != 0 {
98                x ^= poly;
99            }
100        }
101        ensure(x == 1, || format!("GF(2^m): {poly:#x} is not a primitive polynomial of degree {m}"))?;
102        for i in q - 1..2 * q {
103            exp[i] = exp[i - (q - 1)];
104        }
105        Ok(Field { m, exp, log })
106    }
107
108    /// The degree $m$.
109    pub fn m(&self) -> u32 {
110        self.m
111    }
112
113    /// The number of non-zero elements $2^{m} - 1$.
114    pub fn order(&self) -> usize {
115        (1 << self.m) - 1
116    }
117
118    /// $\alpha^{e}$ for any non-negative exponent.
119    pub fn pow(&self, e: usize) -> u32 {
120        self.exp[e % self.order()]
121    }
122
123    /// The exponent $e$ of a non-zero element $\alpha^{e}$ (0 for the element 0).
124    pub fn log(&self, a: u32) -> usize {
125        self.log[a as usize] as usize
126    }
127
128    /// The product $ab$.
129    pub fn mul(&self, a: u32, b: u32) -> u32 {
130        if a == 0 || b == 0 { 0 } else { self.exp[(self.log[a as usize] + self.log[b as usize]) as usize] }
131    }
132
133    /// The quotient $a / b$ of a non-zero `b` ($0 / b = 0$).
134    ///
135    /// # Panics
136    /// In debug builds, for `b = 0` (a division by zero is a broken invariant of the
137    /// caller: release builds return a meaningless element instead of checking it in the
138    /// inner loops of the decoders).
139    pub fn div(&self, a: u32, b: u32) -> u32 {
140        debug_assert!(b != 0, "division by zero in GF(2^{})", self.m());
141        if a == 0 {
142            0
143        } else {
144            let n = self.order() as u32;
145            self.exp[((self.log[a as usize] + n - self.log[b as usize]) % n) as usize]
146        }
147    }
148
149    /// The inverse $a^{-1}$ of a non-zero element.
150    ///
151    /// # Panics
152    /// In debug builds, for `a = 0` (as [`Field::div`]).
153    pub fn inv(&self, a: u32) -> u32 {
154        self.div(1, a)
155    }
156
157    /// The minimal polynomial of $\alpha^{i}$ (binary coefficients, lowest degree first).
158    pub fn minimal_polynomial(&self, i: usize) -> Vec<u8> {
159        // the conjugates α^{i 2^j}
160        let n = self.order();
161        let mut conj = vec![i % n];
162        let mut e = (2 * i) % n;
163        while e != i % n {
164            conj.push(e);
165            e = (2 * e) % n;
166        }
167        // Π (x - α^e) over GF(2^m)
168        let mut p = vec![1u32];
169        for &c in &conj {
170            let r = self.pow(c);
171            let mut next = vec![0u32; p.len() + 1];
172            for (d, &a) in p.iter().enumerate() {
173                next[d + 1] ^= a;
174                next[d] ^= self.mul(a, r);
175            }
176            p = next;
177        }
178        p.iter().map(|&a| u8::from(a == 1)).collect()
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    #[cfg(debug_assertions)]
188    #[should_panic(expected = "division by zero")]
189    fn the_inverse_of_zero_is_a_broken_invariant_in_debug_builds() {
190        let _ = Field::new(4, 0x13).unwrap().inv(0);
191    }
192
193    #[test]
194    fn a_polynomial_of_the_wrong_degree_is_an_error_not_a_panic() {
195        // x⁸ + x⁴ + x³ + x² + 1 for GF(16), and x³ + x + 1 for GF(16)
196        assert!(Field::new(4, 0x11D).is_err());
197        assert!(Field::new(4, 0xB).is_err());
198        assert!(Field::new(4, 0).is_err());
199        assert!(Field::new(8, u32::MAX).is_err());
200        // x⁴ + x³ + x² + x + 1 has the degree but is not primitive (order 5)
201        assert!(Field::new(4, 0x1F).is_err());
202        assert!(Field::new(4, 0x13).is_ok());
203    }
204
205    #[test]
206    fn every_default_polynomial_is_primitive_and_the_arithmetic_is_consistent() {
207        for m in 2..=16 {
208            let f = Field::new(m, primitive_polynomial(m).unwrap()).unwrap();
209            assert_eq!(f.order(), (1 << m) - 1);
210            let a = f.pow(5);
211            let b = f.pow(f.order() - 2);
212            assert_eq!(f.div(f.mul(a, b), b), a, "m = {m}");
213            assert_eq!(f.mul(a, f.inv(a)), 1, "m = {m}");
214            assert_eq!(f.log(f.pow(5)), 5 % f.order());
215        }
216        assert!(primitive_polynomial(1).is_none() && primitive_polynomial(17).is_none());
217        // x⁴ + x³ + x² + x + 1 has order 5: not primitive
218        assert!(Field::new(4, 0b1_1111).is_err());
219    }
220}