~mendelmaleh/checksum

e71e2f6f91dd70e3fd9fce07672614204fce5348 — Mendel E 1 year, 1 month ago
Initial commit
7 files changed, 388 insertions(+), 0 deletions(-)

A damm.go
A damm_test.go
A luhn.go
A luhn_test.go
A utils.go
A verhoeff.go
A verhoeff_test.go
A  => damm.go +28 -0
@@ 1,28 @@
package checksum

var quasi = [10][10]int{
	{0, 3, 1, 7, 5, 9, 8, 6, 4, 2},
	{7, 0, 9, 2, 1, 5, 4, 8, 6, 3},
	{4, 2, 0, 6, 8, 7, 1, 3, 5, 9},
	{1, 7, 5, 0, 9, 8, 3, 4, 2, 6},
	{6, 1, 2, 3, 0, 4, 5, 9, 7, 8},
	{3, 6, 7, 4, 2, 0, 9, 5, 8, 1},
	{5, 8, 6, 9, 7, 2, 0, 1, 3, 4},
	{8, 9, 4, 5, 3, 6, 2, 0, 1, 7},
	{9, 4, 3, 8, 6, 1, 7, 2, 0, 5},
	{2, 5, 8, 1, 4, 3, 6, 7, 9, 0},
}

func damm(i, n, c int) int {
	return quasi[c][n]
}

// Damm checksum validation
func Damm(num int) bool {
	return IterOppositeInt(num, damm) == 0
}

// GetDamm calculates the Damm checksum digit for num.
func GetDamm(num int) int {
	return IterOppositeInt(num, damm)
}

A  => damm_test.go +50 -0
@@ 1,50 @@
package checksum

import (
	"math/rand"
	"testing"
)

func TestDamm(t *testing.T) {
	cases := []struct {
		num int
		ok  bool
	}{
		{5724, true},
	}

	for i, test := range cases {
		if Damm(test.num) != test.ok {
			t.Errorf("TestDamm [%d] (%d): expected test to return %t", i, test.num, test.ok)
		}
	}
}

func TestGetDamm(t *testing.T) {
	cases := []struct {
		num, check int
	}{
		{5, 9},
		{57, 7},
		{572, 4},
		{5720, 6},
	}

	for i, test := range cases {
		if res := GetDamm(test.num); res != test.check {
			t.Errorf("TestGetDamm [%d] (%d): expected check %d, got %d", i, test.num, test.check, res)
		}
	}
}

func BenchmarkDamm(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Damm(rand.Int())
	}
}

func BenchmarkGetDamm(b *testing.B) {
	for n := 0; n < b.N; n++ {
		GetDamm(rand.Int())
	}
}

A  => luhn.go +39 -0
@@ 1,39 @@
package checksum

func luhn(i, n, c int) int {
	if i%2 == 0 {
		return c + n
	}

	return c + n*2/10 + n*2%10
}

func getluhn(i, n, c int) int {
	if i%2 == 1 {
		return c + n
	}

	return c + n*2/10 + n*2%10
}

// Luhn checksum validation
func Luhn(num int) bool {
	return IterInt(num, luhn)%10 == 0
}

// GetLuhn calculates the Luhn check digit for num.
func GetLuhn(num int) int {
	return IterInt(num, getluhn) * 9 % 10
}

// LuhnString checksum validation
func LuhnString(num string) (bool, error) {
	sum, err := IterString(num, luhn)
	return sum%10 == 0, err
}

// GetLuhnString calculates the Luhn check digit for num.
func GetLuhnString(num string) (int, error) {
	sum, err := IterString(num, getluhn)
	return sum * 9 % 10, err
}

A  => luhn_test.go +127 -0
@@ 1,127 @@
package checksum

import (
	"math/rand"
	"strconv"
	"testing"
)

func TestLuhn(t *testing.T) {
	cases := []int{18, 273, 79927398713}

	for i, test := range cases {
		if !Luhn(test) {
			t.Errorf("Luhn [%d] (%d) failed", i, test)
		}
	}
}

func TestGetLuhn(t *testing.T) {
	cases := []struct {
		num, check int
	}{
		{1, 8},
		{27, 3},
		{7992739871, 3},
	}

	for i, test := range cases {
		if res := GetLuhn(test.num); res != test.check {
			t.Errorf("GetLuhn [%d] (%d): expected check %d, got %d", i, test.num, test.check, res)
		}
	}
}

func TestLuhnString(t *testing.T) {
	cases := []string{"18", "273", "79927398713", "79 92   7398   713"}

	for i, test := range cases {
		res, err := LuhnString(test)
		if err != nil {
			t.Error(err)
		}

		if !res {
			t.Errorf("LuhnString [%d] (%s) failed", i, test)
		}
	}
}

func TestGetLuhnString(t *testing.T) {
	cases := []struct {
		num   string
		check int
	}{
		{"1", 8},
		{"27", 3},
		{"7992739871", 3},
		{"79 92   7398   71", 3},
	}

	for i, test := range cases {
		res, err := GetLuhnString(test.num)
		if err != nil {
			t.Error(err)
		}

		if res != test.check {
			t.Errorf("GetLuhnString [%d] (%s): expected check %d, got %d", i, test.num, test.check, res)
		}
	}
}

func BenchmarkLuhn(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Luhn(rand.Int())
	}
}

func BenchmarkGetLuhn(b *testing.B) {
	for n := 0; n < b.N; n++ {
		GetLuhn(rand.Int())
	}
}

func BenchmarkLuhnString(b *testing.B) {
	for n := 0; n < b.N; n++ {
		LuhnString(strconv.Itoa(rand.Int()))
	}
}

func BenchmarkGetLuhnString(b *testing.B) {
	for n := 0; n < b.N; n++ {
		GetLuhnString(strconv.Itoa(rand.Int()))
	}
}

func BenchmarkLuhn_Atoi(b *testing.B) {
	for n := 0; n < b.N; n++ {
		s, _ := strconv.Atoi(strconv.Itoa(rand.Int()))
		Luhn(s)
	}
}

func BenchmarkGetLuhn_Atoi(b *testing.B) {
	for n := 0; n < b.N; n++ {
		s, _ := strconv.Atoi(strconv.Itoa(rand.Int()))
		GetLuhn(s)
	}
}

func Benchmark_randInt(b *testing.B) {
	for n := 0; n < b.N; n++ {
		rand.Int()
	}
}

func Benchmark_Itoa(b *testing.B) {
	for n := 0; n < b.N; n++ {
		strconv.Itoa(rand.Int())
	}
}

func Benchmark_Atoi(b *testing.B) {
	for n := 0; n < b.N; n++ {
		strconv.Atoi(strconv.Itoa(rand.Int()))
	}
}

A  => utils.go +58 -0
@@ 1,58 @@
package checksum

import (
	"fmt"
	"math"
)

// IterInt iterates smallest to largest, over each decimal digit in num,
// and sets fn(index, digit, checksum) as the new sum, and returns it.
func IterInt(num int, fn func(i, n, c int) int) (check int) {
	for i, n := 0, num; n > 0; i, n = i+1, n/10 {
		check = fn(i, n%10, check)
	}

	return check
}

// IterOppositeInt iterates largest to smallest, over each decimal digit in num,
// and sets fn(index, digit, checksum) as the new sum, and returns it.
func IterOppositeInt(num int, fn func(i, n, c int) int) (check int) {
	digits := int(math.Log10(float64(num))) + 1

	for i, n := 0, num; i < digits; i++ {
		p := int(math.Pow10(digits - i - 1))
		check, n = fn(i, n/p, check), n%p
	}

	return check
}

// IterString iterates over each decimal digit in num,
// and sets fn(index, digit, checksum) as the new sum, and returns it.
func IterString(num string, fn func(i, n, c int) int) (check int, err error) {
	for _, r := range num {
		if r == ' ' || r == '_' {
			continue
		}

		if r < '0' || '9' < r {
			return 0, fmt.Errorf("can't validate %q, invalid rune %c, should be a number", num, r)
		}
	}

	bnum := []byte(num)
	var index int
	for i := 0; i < len(bnum); i++ {
		r := bnum[len(bnum)-i-1]

		if r == ' ' || r == '_' {
			continue
		}

		check = fn(index, int(r-'0'), check)
		index++
	}

	return
}

A  => verhoeff.go +44 -0
@@ 1,44 @@
package checksum

// https://en.m.wikibooks.org/wiki/Algorithm_Implementation/Checksums/Verhoeff_Algorithm#C
var (
	mult = [10][10]int{
		{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},
	}

	perm = [8][10]int{
		{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},
	}

	inv = [10]int{0, 4, 3, 2, 1, 5, 6, 7, 8, 9}
)

func verhoeff(i, n, c int) int {
	return mult[c][perm[i%8][n]]
}

// Verhoeff checksum validation
func Verhoeff(num int) bool {
	return IterInt(num, verhoeff) == 0
}

// GetVerhoeff calculates the Verhoeff checksum digit for num.
func GetVerhoeff(num int) int {
	return inv[IterInt(num*10, verhoeff)]
}

A  => verhoeff_test.go +42 -0
@@ 1,42 @@
package checksum

import (
	"math/rand"
	"testing"
)

func TestVerhoeff(t *testing.T) {
	cases := []int{2363}

	for i, test := range cases {
		if !Verhoeff(test) {
			t.Errorf("Verhoeff [%d] (%d) failed", i, test)
		}
	}
}

func TestGetVerhoeff(t *testing.T) {
	cases := []struct {
		num, check int
	}{
		{236, 3},
	}

	for i, test := range cases {
		if res := GetVerhoeff(test.num); res != test.check {
			t.Errorf("GetVerhoeff [%d] (%d): expected check %d, got %d", i, test.num, test.check, res)
		}
	}
}

func BenchmarkVerhoeff(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Verhoeff(rand.Int())
	}
}

func BenchmarkGetVerhoeff(b *testing.B) {
	for n := 0; n < b.N; n++ {
		GetVerhoeff(rand.Int())
	}
}