extern crate test;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Element(u8);
impl std::ops::Add for Element {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self {
Self::new(self.0 ^ rhs.0)
}
}
impl std::ops::Neg for Element {
type Output = Self;
fn neg(self) -> Self {
self
}
}
impl std::ops::Sub for Element {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
self + -rhs
}
}
impl std::ops::Mul for Element {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self::ALL_PRODUCTS[self.value() as usize][rhs.value() as usize]
}
}
impl std::ops::Div for Element {
type Output = Option<Self>;
fn div(self, rhs: Self) -> Option<Self> {
Self::ALL_ELEMENT_INVERSES[rhs.value() as usize]
.map(|x| Self::ALL_PRODUCTS[self.value() as usize][x.value() as usize])
}
}
impl Element {
pub const IRREDUCTIBLE_POLYNOMIAL: u16 = 0x1cf;
pub const ZERO: Self = Self(0);
pub const ONE: Self = Self(1);
#[inline]
pub const fn new(value: u8) -> Self {
Self(value)
}
#[inline]
pub const fn value(self) -> u8 {
self.0
}
const ALL_PRODUCTS: [[Self; 256]; 256] = {
let mut all_products = [[Self::new(0); 256]; 256];
let mut i = 255_u8;
while i > 0 {
let mut j = 255_u8;
while j > 0 {
let mut product: u8 = 0;
let mut accumulator = i as u16;
let mut rhs_remainder = j;
while accumulator != 0 && rhs_remainder != 0 {
if rhs_remainder % 2 == 1 {
product ^= accumulator as u8;
}
rhs_remainder >>= 1;
accumulator <<= 1;
if accumulator >= 0b1_0000_0000 {
accumulator ^= Self::IRREDUCTIBLE_POLYNOMIAL;
}
if rhs_remainder == 0 {
break;
}
}
all_products[i as usize][j as usize] = Self::new(product);
j -= 1;
}
i -= 1;
}
all_products
};
const ALL_ELEMENT_INVERSES: [Option<Self>; 256] = {
let mut all_inverses = [None; 256];
let mut i = 255_u8;
while i > 0 {
let mut j: u8 = 255;
while j > 0 {
if Self::ALL_PRODUCTS[i as usize][j as usize].0 == Self::ONE.0 {
all_inverses[i as usize] = Some(Self::new(j));
break;
}
j -= 1;
}
i -= 1;
}
all_inverses
};
const ALL_POWERS: [[Self; 256]; 256] = {
let mut all_powers = [[Self::new(0); 256]; 256];
let mut i = 0_u8;
loop {
let mut accumulator = Self::ONE;
let mut j = 0;
loop {
all_powers[i as usize][j as usize] = accumulator;
if j == 255 {
break;
}
accumulator = Self::ALL_PRODUCTS[accumulator.value() as usize][i as usize];
j += 1;
}
if i == 255 {
break;
}
i += 1;
}
all_powers
};
#[inline]
pub const fn inverse(self) -> Option<Self> {
match self {
Self::ZERO => None,
_ => Self::ALL_ELEMENT_INVERSES[self.value() as usize],
}
}
#[inline]
pub const fn power(self, exponent: u8) -> Self {
Self::ALL_POWERS[self.value() as usize][exponent as usize]
}
}
#[allow(dead_code)]
pub fn compute_irreductible_polynomials() -> Vec<u16> {
let mut irreductible_polynomials = vec![];
for poly in 0x100_u16..=0x1ffu16 {
let mut factored = false;
'poly: for i in 1..=255 {
for j in i..=255 {
let mut product = 0_u16;
let mut accumulator = i as u16;
let mut remainder = j as u16;
while accumulator != 0 && accumulator < 0b10_0000_0000 && remainder != 0 {
if remainder % 2 == 1 {
product ^= accumulator;
}
accumulator <<= 1;
remainder >>= 1;
}
if product == poly {
factored = true;
break 'poly;
}
}
}
if !factored {
irreductible_polynomials.push(poly);
}
}
irreductible_polynomials
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn addition_is_commutative() {
for i in 0..=255 {
for j in 0..=255 {
assert_eq!(
Element::new(i) + Element::new(j),
Element::new(j) + Element::new(i),
"{0} + {1} = {2:?}, {1} + {0} = {3:?}",
i,
j,
Element::new(i) + Element::new(j),
Element::new(j) + Element::new(i),
);
}
}
}
#[test]
fn addition_is_associative() {
for i in 0..=255 {
for j in 0..=255 {
for k in 0..=255 {
let (i, j, k) = (Element::new(i), Element::new(j), Element::new(k));
assert_eq!(
i + (j + k),
(i + j) + k,
"{0:?} + ({1:?} + {2:?}) = {3:?}, ({0:?} + {1:?}) + {2:?} = {4:?}",
i,
j,
k,
i + (j + k),
(i + j) + k,
);
}
}
}
}
#[test]
fn zero_is_neutral_for_addition() {
for i in 0..=255 {
let i = Element::new(i);
assert_eq!(
i + Element::ZERO,
i,
"{0:?} + neutral = {1:?}",
i,
i + Element::ZERO,
);
}
}
#[test]
fn addition_table_is_unique() {
for i in 1..=255 {
for j in i..=255 {
for k in j..=255 {
if k == j {
continue;
}
let (i, j, k) = (Element::new(i), Element::new(j), Element::new(k));
assert_ne!(
i + j,
i + k,
"{0:?} + {1:?} = {0:?} + {2:?} = {3:?}",
i,
j,
k,
i + j,
);
}
}
}
}
#[test]
fn multiplication_is_commutative() {
for i in 0..=255 {
for j in 0..=255 {
let (i, j) = (Element::new(i), Element::new(j));
assert_eq!(
i * j,
j * i,
"{0:?} ร {1:?} = {2:?}, {1:?} * {0:?} = {3:?}",
i,
j,
i * j,
j * i,
);
}
}
}
#[test]
fn multiplication_is_associative() {
for i in 0..=255 {
for j in 0..=255 {
for k in 0..=255 {
let (i, j, k) = (Element::new(i), Element::new(j), Element::new(k));
assert_eq!(
i * (j * k),
(i * j) * k,
"{0:?} ร ({1:?} ร {2:?}) = {3:?}, ({0:?} ร {1:?}) ร {2:?} = {4:?}",
i,
j,
k,
i * (j * k),
(i * j) * k,
);
}
}
}
}
#[test]
fn one_is_neutral_for_multiplication() {
for i in 0..=255 {
let i = Element::new(i);
assert_eq!(
i * Element::ONE,
i,
"{0:?} ร neutral = {1:?}",
i,
i * Element::ONE,
);
}
}
#[test]
fn zero_is_sink_for_multiplication() {
for i in 0..=255 {
let i = Element::new(i);
assert_eq!(
i * Element::new(0),
Element::new(0),
"{0:?} ร 0 = {1:?}",
i,
i * Element::new(0),
);
}
}
#[test]
fn multiplication_table_is_unique() {
for i in 1..=255 {
for j in i..=255 {
for k in j..=255 {
if k == j {
continue;
}
let (i, j, k) = (Element::new(i), Element::new(j), Element::new(k));
assert_ne!(
i * j,
i * k,
"{0:?} ร {1:?} = {0:?} ร {2:?} = {3:?}",
i,
j,
k,
i * j,
);
}
}
}
}
#[test]
fn subtraction_is_addition() {
for i in 0..=255 {
for j in 0..=255 {
let (i, j) = (Element::new(i), Element::new(j));
assert_eq!(
i + j,
i - j,
"{0:?} + {1:?} = {2:?}, {0:?} - {1:?} = {3:?}",
i,
j,
i + j,
i - j,
);
}
}
}
#[test]
fn element_is_self_opposite() {
for i in 0..=255 {
let i = Element::new(i);
assert_eq!(-i, i, "-{0:?} = {1:?}", i, -i,);
}
}
#[test]
fn division_table_is_unique() {
for i in 1..=255 {
for j in i..=255 {
for k in j..=255 {
if k == j {
continue;
}
let (i, j, k) = (Element::new(i), Element::new(j), Element::new(k));
assert_ne!(
i / j,
i / k,
"{0:?} รท {1:?} = {0:?} รท {2:?} = {3:?}",
i,
j,
k,
(i / j).unwrap_or_else(|| Element::new(0)),
);
}
}
}
}
#[test]
fn division_is_inverse_of_multiplication() {
for i in 0..=255 {
for j in 1..=255 {
let (i, j) = (Element::new(i), Element::new(j));
assert_eq!(
((i * j) / j).unwrap(),
i,
"({0:?} ร {1:?}) รท {1:?} โ {0:?}",
i,
j,
);
assert_eq!(
((i / j).unwrap()) * j,
i,
"({0:?} รท {1:?}) ร {1:?} โ {0:?}",
i,
j,
);
}
}
}
#[test]
fn division_is_last_power() {
for i in 1..=255 {
let i = Element::new(i);
assert_eq!(
i.power(254),
i.inverse().unwrap(),
"({0:?}^254 != {0:?}^-1)",
i,
);
}
}
#[test]
fn division_by_self_is_one() {
for i in 1..=255 {
let i = Element::new(i);
assert_eq!(
i / i,
Some(Element::ONE),
"{0:?} รท {1:?} = {2:?}",
i,
i,
(i / i).unwrap(),
);
}
}
#[test]
fn multiplication_is_distributive_over_addition() {
for i in 0..=255 {
for j in 0..=255 {
for k in 0..=255 {
let (i, j, k) = (Element::new(i), Element::new(j), Element::new(k));
assert_eq!(
i * (j + k),
(i * j) + (i * k),
"{0:?} ร ({1:?} + {2:?}) = {3:?}, {0:?} ร {1:?} + {0:?} ร {2:?} = {4:?}",
i,
j,
k,
i * (j + k),
(i * j) + (i * k),
);
assert_eq!(
(i + j) * k,
(i * k) + (j * k),
"({0:?} + {1:?}) ร {2:?}) = {3:?}, {0:?} ร {2:?} + {1:?} ร {2:?} = {4:?}",
i,
j,
k,
(i + j) * k,
(i * k) + (j * k),
);
}
}
}
}
#[test]
fn power_recursion() {
for i in 0..=255 {
for k in 0..=254_u8 {
let i = Element::new(i);
assert_eq!(
i.power(k + 1),
i.power(k) * i,
"{0:?}^{1} ร {0:?} = {3:?}, {0:?}^{2} = {4:?}",
i,
k,
k + 1,
i.power(k) * i,
i.power(k + 1)
);
}
}
}
#[test]
fn constant_irreductible_polynomial_is_indeed_irreductible() {
for i in 1..=255 {
for j in i..=255 {
let mut product = 0_u16;
let mut accumulator = i as u16;
let mut remainder = j as u16;
while accumulator != 0 && accumulator < 0b10_0000_0000 && remainder != 0 {
if remainder % 2 == 1 {
product ^= accumulator;
}
accumulator <<= 1;
remainder >>= 1;
}
assert_ne!(
product,
Element::IRREDUCTIBLE_POLYNOMIAL,
"{0:#b} ร {1:#b} = {2:#b}",
i,
j,
Element::IRREDUCTIBLE_POLYNOMIAL
);
}
}
}
#[bench]
fn bench_addition(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
for j in 0..=255 {
test::black_box(Element::new(i) + Element::new(j));
}
}
});
}
#[bench]
fn bench_opposite(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
test::black_box(-Element::new(i));
}
});
}
#[bench]
fn bench_subtraction(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
for j in 0..=255 {
test::black_box(Element::new(i) - Element::new(j));
}
}
});
}
#[bench]
fn bench_multiplication(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
for j in 0..=255 {
test::black_box(Element::new(i) * Element::new(j));
}
}
});
}
#[bench]
fn bench_inverse(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
test::black_box(Element::new(i).inverse());
}
});
}
#[bench]
fn bench_division(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
for j in 0..=255 {
test::black_box(Element::new(i) / Element::new(j));
}
}
});
}
#[bench]
fn bench_power(b: &mut test::Bencher) {
b.iter(|| {
for i in 0..=255 {
for j in 0..=255 {
test::black_box(Element::new(i).power(j));
}
}
});
}
}