Skip to main content

rust_par2/
gf.rs

1//! GF(2^16) Galois field arithmetic.
2//!
3//! PAR2 mandates the irreducible polynomial `0x1100B`:
4//!   x^16 + x^12 + x^3 + x + 1
5//!
6//! All arithmetic uses log/antilog (exp) tables for O(1) multiply/divide.
7//! The generator element is 2 (as per the PAR2 spec).
8
9use std::sync::OnceLock;
10
11/// The irreducible polynomial for GF(2^16) as used by PAR2.
12/// x^16 + x^12 + x^3 + x + 1 = 0x1100B
13const POLYNOMIAL: u32 = 0x1100B;
14
15/// Field size: 2^16 = 65536 elements.
16const FIELD_SIZE: usize = 65536;
17
18/// Order of the multiplicative group: 2^16 - 1 = 65535.
19const FIELD_ORDER: usize = 65535;
20
21/// Precomputed tables for GF(2^16) arithmetic.
22struct GfTables {
23    /// log_table[x] = discrete log base 2 of x (undefined for x=0).
24    log_table: Vec<u16>,
25    /// exp_table[i] = 2^i mod polynomial. Double-sized for modular index wrapping.
26    exp_table: Vec<u16>,
27}
28
29static TABLES: OnceLock<GfTables> = OnceLock::new();
30
31fn tables() -> &'static GfTables {
32    TABLES.get_or_init(|| {
33        let mut log_table = vec![0u16; FIELD_SIZE];
34        let mut exp_table = vec![0u16; FIELD_ORDER * 2];
35
36        // Build exp table: exp[i] = g^i where g = 2 (the generator)
37        let mut val: u32 = 1;
38        for entry in exp_table.iter_mut().take(FIELD_ORDER) {
39            *entry = val as u16;
40            // Multiply by generator (2) in GF(2^16)
41            val <<= 1;
42            if val & 0x10000 != 0 {
43                val ^= POLYNOMIAL;
44            }
45        }
46
47        // Extend exp table for easy modular reduction: exp[i + FIELD_ORDER] = exp[i]
48        for i in 0..FIELD_ORDER {
49            exp_table[i + FIELD_ORDER] = exp_table[i];
50        }
51
52        // Build log table: log[exp[i]] = i
53        for i in 0..FIELD_ORDER {
54            log_table[exp_table[i] as usize] = i as u16;
55        }
56        // log[0] is undefined but we set it to 0 for safety
57        log_table[0] = 0;
58
59        GfTables {
60            log_table,
61            exp_table,
62        }
63    })
64}
65
66/// Addition in GF(2^16) is XOR.
67#[inline]
68pub fn add(a: u16, b: u16) -> u16 {
69    a ^ b
70}
71
72/// Subtraction in GF(2^16) is the same as addition (XOR).
73#[inline]
74pub fn sub(a: u16, b: u16) -> u16 {
75    a ^ b
76}
77
78/// Multiply two elements in GF(2^16).
79/// Returns 0 if either operand is 0.
80#[inline]
81pub fn mul(a: u16, b: u16) -> u16 {
82    if a == 0 || b == 0 {
83        return 0;
84    }
85    let t = tables();
86    let log_sum = t.log_table[a as usize] as usize + t.log_table[b as usize] as usize;
87    t.exp_table[log_sum]
88}
89
90/// Divide a by b in GF(2^16).
91/// Panics if b is 0 (division by zero).
92#[inline]
93pub fn div(a: u16, b: u16) -> u16 {
94    assert!(b != 0, "GF(2^16) division by zero");
95    if a == 0 {
96        return 0;
97    }
98    let t = tables();
99    let log_a = t.log_table[a as usize] as usize;
100    let log_b = t.log_table[b as usize] as usize;
101    // Use extended table to avoid negative index: log_a + (FIELD_ORDER - log_b)
102    let idx = log_a + FIELD_ORDER - log_b;
103    t.exp_table[idx]
104}
105
106/// Multiplicative inverse: inv(a) = a^(FIELD_ORDER-1) = a^65534.
107/// Panics if a is 0.
108#[inline]
109pub fn inv(a: u16) -> u16 {
110    assert!(a != 0, "GF(2^16) inverse of zero");
111    let t = tables();
112    let log_a = t.log_table[a as usize] as usize;
113    t.exp_table[FIELD_ORDER - log_a]
114}
115
116/// Raise a to the power exp in GF(2^16).
117#[inline]
118pub fn pow(a: u16, exp: u32) -> u16 {
119    if a == 0 {
120        return 0;
121    }
122    if exp == 0 {
123        return 1;
124    }
125    let t = tables();
126    let log_a = t.log_table[a as usize] as u64;
127    let log_result = (log_a * exp as u64) % FIELD_ORDER as u64;
128    t.exp_table[log_result as usize]
129}
130
131/// Return 2^exp in GF(2^16). The generator is 2.
132#[inline]
133pub fn exp2(exp: u32) -> u16 {
134    let t = tables();
135    t.exp_table[exp as usize % FIELD_ORDER]
136}
137
138// ---------------------------------------------------------------------------
139// Tests
140// ---------------------------------------------------------------------------
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_add_is_xor() {
148        assert_eq!(add(0, 0), 0);
149        assert_eq!(add(1, 1), 0);
150        assert_eq!(add(0xFFFF, 0), 0xFFFF);
151        assert_eq!(add(0xAAAA, 0x5555), 0xFFFF);
152    }
153
154    #[test]
155    fn test_mul_identity() {
156        // Multiply by 1 is identity
157        for x in [0u16, 1, 2, 100, 1000, 65535] {
158            assert_eq!(mul(x, 1), x, "mul({x}, 1) should be {x}");
159            assert_eq!(mul(1, x), x, "mul(1, {x}) should be {x}");
160        }
161    }
162
163    #[test]
164    fn test_mul_zero() {
165        for x in [0u16, 1, 2, 100, 65535] {
166            assert_eq!(mul(x, 0), 0);
167            assert_eq!(mul(0, x), 0);
168        }
169    }
170
171    #[test]
172    fn test_mul_commutative() {
173        for a in [2u16, 3, 100, 1000, 32768, 65535] {
174            for b in [2u16, 3, 100, 1000, 32768, 65535] {
175                assert_eq!(mul(a, b), mul(b, a), "mul({a}, {b}) should be commutative");
176            }
177        }
178    }
179
180    #[test]
181    fn test_inverse() {
182        // a * inv(a) should always equal 1
183        for a in [1u16, 2, 3, 100, 1000, 32768, 65535] {
184            let a_inv = inv(a);
185            assert_eq!(
186                mul(a, a_inv),
187                1,
188                "a={a}, inv(a)={a_inv}: a*inv(a) should be 1"
189            );
190        }
191    }
192
193    #[test]
194    fn test_div_inverse_of_mul() {
195        for a in [1u16, 2, 42, 1000, 65535] {
196            for b in [1u16, 2, 42, 1000, 65535] {
197                let product = mul(a, b);
198                assert_eq!(div(product, b), a, "div(mul({a},{b}), {b}) should be {a}");
199            }
200        }
201    }
202
203    #[test]
204    fn test_pow_basics() {
205        // a^0 = 1
206        assert_eq!(pow(42, 0), 1);
207        // a^1 = a
208        assert_eq!(pow(42, 1), 42);
209        // 0^n = 0 for n > 0
210        assert_eq!(pow(0, 5), 0);
211        // 1^n = 1
212        assert_eq!(pow(1, 999), 1);
213    }
214
215    #[test]
216    fn test_pow_consistent_with_mul() {
217        // a^2 = a * a
218        for a in [2u16, 3, 100, 65535] {
219            assert_eq!(pow(a, 2), mul(a, a));
220            assert_eq!(pow(a, 3), mul(mul(a, a), a));
221        }
222    }
223
224    #[test]
225    fn test_generator_order() {
226        // The generator 2 should have order FIELD_ORDER = 65535
227        // 2^65535 = 1
228        assert_eq!(pow(2, FIELD_ORDER as u32), 1);
229        // 2^32768 != 1 (not a subgroup generator)
230        assert_ne!(pow(2, 32768), 1);
231    }
232
233    #[test]
234    fn test_exp2() {
235        assert_eq!(exp2(0), 1);
236        assert_eq!(exp2(1), 2);
237        assert_eq!(exp2(2), 4);
238        // exp2 should wrap around
239        assert_eq!(exp2(FIELD_ORDER as u32), 1);
240    }
241
242    #[test]
243    fn test_field_closure() {
244        // Multiply all non-zero elements by a fixed non-zero element
245        // should produce a permutation of all non-zero elements
246        let a = 42u16;
247        let mut seen = vec![false; FIELD_SIZE];
248        for x in 1..FIELD_SIZE as u16 {
249            let product = mul(a, x);
250            assert!(
251                !seen[product as usize],
252                "duplicate product for a={a}, x={x}"
253            );
254            seen[product as usize] = true;
255        }
256        // 0 should not be produced (since a != 0 and x != 0)
257        assert!(!seen[0]);
258    }
259
260    /// Verify against known par2cmdline values.
261    /// These were computed using par2cmdline-turbo's galois.cpp.
262    #[test]
263    fn test_known_par2_values() {
264        // Generator powers
265        assert_eq!(pow(2, 0), 1);
266        assert_eq!(pow(2, 1), 2);
267        assert_eq!(pow(2, 8), 256);
268        assert_eq!(pow(2, 16), 0x100B); // x^16 mod polynomial = x^12 + x^3 + x + 1 = 0x100B
269    }
270}