pub(super) fn check_digit(digits: &str) -> u8 {
let mut c: u8 = 0;
for (i, ch) in digits.bytes().rev().enumerate() {
let n = ascii_digit_to_u8(ch);
let pos = (i + 1) % 8;
c = D[c as usize][P[pos][n as usize] as usize];
}
INV[c as usize]
}
pub(super) fn verify(digits_with_check: &str) -> bool {
let mut c: u8 = 0;
for (i, ch) in digits_with_check.bytes().rev().enumerate() {
let n = ascii_digit_to_u8(ch);
let pos = i % 8;
c = D[c as usize][P[pos][n as usize] as usize];
}
c == 0
}
fn ascii_digit_to_u8(b: u8) -> u8 {
if b.is_ascii_digit() {
b - b'0'
} else {
0
}
}
const D: [[u8; 10]; 10] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
[2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
[3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
[4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
[5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
[6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
[7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
[8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
];
const P: [[u8; 10]; 8] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
[5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
[8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
[9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
[4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
[2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
[7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
];
const INV: [u8; 10] = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::{check_digit, verify};
#[test]
fn wikipedia_example_236() {
assert_eq!(check_digit("236"), 3);
assert!(verify("2363"));
}
#[test]
fn detects_single_digit_swap() {
assert!(verify("2363"));
assert!(!verify("2463")); assert!(!verify("2373")); assert!(!verify("2364")); }
#[test]
fn detects_adjacent_transposition() {
assert!(verify("2363"));
assert!(!verify("3263")); assert!(!verify("2633")); }
#[test]
fn self_consistency_short() {
for n in 0..1000u32 {
let s = format!("{n:03}");
let with_check = format!("{s}{}", check_digit(&s));
assert!(verify(&with_check), "failed self-check for {s}");
}
}
#[test]
fn self_consistency_manual_code_length() {
let cases = [
"1234567890",
"0000000001",
"9999999998",
"3497011233", ];
for s in cases {
let with_check = format!("{s}{}", check_digit(s));
assert!(verify(&with_check), "failed for {s}");
}
}
}