check_digit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGVlNWIwODBlZTE0NzI4Y2IxOGJiYTUyZjA4NWJhMGVlZmMyMzcwMQ==
5
+ data.tar.gz: !binary |-
6
+ NDQ3NmJhMThkZDIzMTZiZjk3OGRmNDVmM2MyNjdhNjBmYTM3ZjQ3Yw==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTdmNzhiZjhlMGJlOTk3YzlmMTQ3MjZjYmU4YTFhMjM5YmQ1MThlODkzMmE0
10
+ ZDJiODFhZGMzZmNmMzY4YmMzZGJmNjE2Zjg3ZWQ2MGExMjJiNzhjNWRmZGU5
11
+ YWJhYTRhZjNhMDk0NTJhOTExODNkMTEzYzhmZjMyMGM2ZDMyN2E=
12
+ data.tar.gz: !binary |-
13
+ ZjBmYWIzMmM3NjEzMzI4YjZjMGRmYTAxOTI0OWUxNjkxOTg5MzVhZWJjMjhk
14
+ ODdjMDM5OTE1MzZhNmU5ZWY5YmE3MDI5YTVkODhkMjcyZTMyNGU1ODUyMjgz
15
+ YzY3YTAwNDg2OGRkMzNkMzVlMDEyNmFiNmY5ODA1YmJmZGM5YjY=
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ *~
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in check_digit.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Eric Litwin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,74 @@
1
+ # CheckDigit
2
+
3
+ Ruby implementation of the Luhn, Damm and Verhoeff check digit algorithms
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'check_digit'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install check_digit
18
+
19
+ ## Usage
20
+
21
+ Each implementation provides the same 2 class methods:
22
+ * checksum(num) - returns a new number with the checksum digit appended at the end
23
+ * valid?(num) - returns true if the number has a valid checksum digit, false if the number has an invalid checksum digit
24
+
25
+ Code sample:
26
+
27
+ require 'check_digit'
28
+
29
+ Damm.checksum(123) # returns 1234
30
+ Damm.valid?(1234) # returns true
31
+ Damm.valid?(1239) # returns false
32
+
33
+ ## Algorithm Comparison - Error Prevention
34
+ | Error Type | % of All Errors | Luhn | Damm | Verhoeff |
35
+ |:----------------------------------------- |:--------------- |:----:|:----:|:--------:|
36
+ | single errors: a becomes b | 60% to 95% | 1 | 1 | 1 |
37
+ | omitting or adding a digit | 10% to 20% | 1 | 1 | 1 |
38
+ | adjacent transpositions: *ab* becomes *ba* | 10% to 20% | 2 | 1 | 1 |
39
+ | twin errors: *aa* becomes *bb* | 0.5% to 1.5% | 3 | 1 | 2 |
40
+ | jump transpositions: *acb* becomes *bca* | 0.5% to 1.5% | 2 | 1 | 2 |
41
+ | jump twin errors: *aca* becomes *bcb* | below 1% | 1 | 1 | 2 |
42
+ | phonetic errors: *a0* becomes *0a* | 0.5% to 1.5% | 1* | 1 | 2 |
43
+
44
+ Source: http://www.augustana.ab.ca/~mohrj/algorithms/checkdigit.html
45
+
46
+ \* Not entirely sure if Luhn catches all phonetic errors, e.g. 13 instead of 30
47
+
48
+ #####*Legend*
49
+ 1. Prevents all errors
50
+ 2. Prevents most errors
51
+ 3. Prevents some errors
52
+ 4. Does not prevent any errors
53
+
54
+ ## Algorithm Comparison - Performance
55
+ #####50,000 iterations (time in seconds)*
56
+
57
+ | Algorithm | Generate | Validate |
58
+ |:--------- | --------:| --------:|
59
+ | Luhn | 6.630000 | 6.390000 |
60
+ | Damm | 1.700000 | 1.610000 |
61
+ | Verhoeff | 3.730000 | 3.720000 |
62
+
63
+ \* Test was on Linux 10.04 with 2.80GHz Intel Xeon E5-1603 - relative timing is more important to consider
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it ( https://github.com/elitwin/check_digit/fork )
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create a new Pull Request
72
+
73
+ ## Attributions
74
+ The Verhoeff implementation was provided from this repo (https://github.com/bai/verhoeff) and was the inspiration for the interface (checksum, valid?) used by all the implementations
@@ -0,0 +1,3 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ Dir.glob('tasks/**/*.rake').each(&method(:import))
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'check_digit/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "check_digit"
8
+ spec.version = CheckDigit::VERSION
9
+ spec.authors = ["Eric Litwin"]
10
+ spec.email = ["eric.litwin@pnmac.com"]
11
+ spec.summary = %q{Check Digit calculator/validator}
12
+ spec.description = %q{Ruby implementation of Luhn, Damm and Verhoeff check digit algorithms}
13
+ spec.homepage = "http://github.com/elitwin/check_digit"
14
+ spec.licenses = ["MIT"]
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ spec.platform = Gem::Platform::RUBY
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rspec", "~> 3.1"
24
+ end
@@ -0,0 +1,5 @@
1
+ require 'check_digit/version'
2
+ require 'check_digit/util'
3
+ require 'check_digit/verhoeff'
4
+ require 'check_digit/damm'
5
+ require 'check_digit/luhn'
@@ -0,0 +1,31 @@
1
+ module CheckDigit::Damm
2
+ D = [
3
+ [0, 3, 1, 7, 5, 9, 8, 6, 4, 2],
4
+ [7, 0, 9, 2, 1, 5, 4, 8, 6, 3],
5
+ [4, 2, 0, 6, 8, 7, 1, 3, 5, 9],
6
+ [1, 7, 5, 0, 9, 8, 3, 4, 2, 6],
7
+ [6, 1, 2, 3, 0, 4, 5, 9, 7, 8],
8
+ [3, 6, 7, 4, 2, 0, 9, 5, 8, 1],
9
+ [5, 8, 6, 9, 7, 2, 0, 1, 3, 4],
10
+ [8, 9, 4, 5, 3, 6, 2, 0, 1, 7],
11
+ [9, 4, 3, 8, 6, 1, 7, 2, 0, 5],
12
+ [2, 5, 8, 1, 4, 3, 6, 7, 9, 0]
13
+ ].freeze
14
+
15
+ def self.checksum(num)
16
+ CheckDigit::Util.valid_arg(num)
17
+ num.to_i * 10 + calc(num)
18
+ end
19
+
20
+ def self.valid?(num)
21
+ CheckDigit::Util.valid_arg(num)
22
+ calc(num.to_s[0..-2]) == num % 10
23
+ end
24
+
25
+ private
26
+ def self.calc(num)
27
+ i = 0
28
+ num.to_s.each_char {|c| i = D[i][c.to_i] }
29
+ i
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ module CheckDigit::Luhn
2
+ def self.checksum(num)
3
+ CheckDigit::Util.valid_arg(num)
4
+ num.to_i * 10 + calc(num)
5
+ end
6
+
7
+ def self.valid?(num)
8
+ CheckDigit::Util.valid_arg(num)
9
+ calc(num.to_s[0..-2]) == num % 10
10
+ end
11
+
12
+ private
13
+ def self.calc(num)
14
+ digits = num.to_s.reverse.scan(/\d/).map { |x| x.to_i }
15
+ digits = digits.each_with_index.map { |d, i|
16
+ d *= 2 if i.even?
17
+ d > 9 ? d - 9 : d
18
+ }
19
+ sum = digits.inject(0) { |m, x| m + x }
20
+ mod = 10 - sum % 10
21
+ mod==10 ? 0 : mod
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module CheckDigit::Util
2
+ def self.valid_arg(num)
3
+ raise ArgumentError, 'invalid argument' unless
4
+ num.is_a?(Integer) || (num.to_i.to_s == num)
5
+ end
6
+ end
@@ -0,0 +1,46 @@
1
+ module CheckDigit::Verhoeff
2
+ D = [
3
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
4
+ [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
5
+ [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
6
+ [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
7
+ [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
8
+ [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
9
+ [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
10
+ [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
11
+ [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
12
+ [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
13
+ ].freeze
14
+
15
+ P = [
16
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
17
+ [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
18
+ [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
19
+ [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
20
+ [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
21
+ [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
22
+ [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
23
+ [7, 0, 4, 6, 9, 1, 3, 2, 5, 8]
24
+ ].freeze
25
+
26
+ INV = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9].freeze
27
+
28
+ ZERO_ORDINAL = 48 # '0'.each_byte.first on 1.8 or '0'.ord on 1.9
29
+
30
+ def self.checksum(num)
31
+ CheckDigit::Util.valid_arg(num)
32
+ num.to_i * 10 + calc(num)
33
+ end
34
+
35
+ def self.valid?(num)
36
+ CheckDigit::Util.valid_arg(num)
37
+ calc(num.to_s[0..-2]) == num % 10
38
+ end
39
+
40
+ private
41
+ def self.calc(num)
42
+ INV[num.to_s.each_byte.reverse_each.with_index.inject(0) { |check,(x,i)|
43
+ D[check][P[i.next % 8][x - ZERO_ORDINAL]]
44
+ }]
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module CheckDigit
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ module CheckDigit
4
+ describe Damm do
5
+ it "generates checksum values" do
6
+ expect(Damm.checksum(100913449)).to eq(1009134493)
7
+ expect(Damm.checksum(123)).to eq(1234)
8
+ end
9
+
10
+ it "generates a checkum value for an integer string" do
11
+ expect(Damm.checksum("123")).to eq(1234)
12
+ end
13
+
14
+ it "raises an error for a non-integer string" do
15
+ expect {Damm.checksum("A123")}.
16
+ to raise_error(ArgumentError, 'invalid argument')
17
+
18
+ expect {Damm.valid?("A123")}.
19
+ to raise_error(ArgumentError, 'invalid argument')
20
+ end
21
+
22
+ it "raises an error for a non-integer numeric" do
23
+ expect {Damm.checksum("123.45")}.
24
+ to raise_error(ArgumentError, 'invalid argument')
25
+
26
+ expect {Damm.valid?("123.45")}.
27
+ to raise_error(ArgumentError, 'invalid argument')
28
+ end
29
+
30
+ it "tests an existing number for a valid checksum" do
31
+ expect(Damm.valid?(9990)).to be true
32
+ expect(Damm.valid?(9999)).to be false
33
+ end
34
+
35
+ it "benchmarks performance (#{BMITERS} iterations)" do
36
+ puts ' ' + '-' * 55
37
+ Benchmark.bm(10) do |x|
38
+ x.report(" generate:") {
39
+ i = BMITERS
40
+ while i > 1
41
+ Damm.checksum(i)
42
+ i-=1
43
+ end
44
+ }
45
+
46
+ x.report(" validate:") {
47
+ i = BMITERS
48
+ while i > 1
49
+ Damm.valid?(i)
50
+ i-=1
51
+ end
52
+ }
53
+ end
54
+ puts ' ' + '-' * 55
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ module CheckDigit
4
+ describe Luhn do
5
+ it "generates checksum values" do
6
+ val = (1..23).inject(1) { |val, i| Luhn.checksum val }
7
+ expect(val).to eq(182675918342675918342675)
8
+
9
+ expect(Luhn.checksum(100913449)).to eq(1009134493)
10
+ expect(Luhn.checksum(123)).to eq(1230)
11
+ end
12
+
13
+ it "generates a checkum value for an integer string" do
14
+ expect(Luhn.checksum("123")).to eq(1230)
15
+ end
16
+
17
+ it "raises an error for a non-integer string" do
18
+ expect {Luhn.checksum("A123")}.
19
+ to raise_error(ArgumentError, 'invalid argument')
20
+
21
+ expect {Luhn.valid?("A123")}.
22
+ to raise_error(ArgumentError, 'invalid argument')
23
+ end
24
+
25
+ it "raises an error for a non-integer numeric" do
26
+ expect {Luhn.checksum("123.45")}.
27
+ to raise_error(ArgumentError, 'invalid argument')
28
+
29
+ expect {Luhn.valid?("123.45")}.
30
+ to raise_error(ArgumentError, 'invalid argument')
31
+ end
32
+
33
+ it "tests an existing number for a valid checksum" do
34
+ expect(Luhn.valid?(9993)).to be true
35
+ expect(Luhn.valid?(9999)).to be false
36
+ end
37
+
38
+ it "benchmarks performance (#{BMITERS} iterations)" do
39
+ puts ' ' + '-' * 55
40
+ Benchmark.bm(10) do |x|
41
+ x.report(" generate:") {
42
+ i = BMITERS
43
+ while i > 1
44
+ Luhn.checksum(i)
45
+ i-=1
46
+ end
47
+ }
48
+
49
+ x.report(" validate:") {
50
+ i = BMITERS
51
+ while i > 1
52
+ Luhn.valid?(i)
53
+ i-=1
54
+ end
55
+ }
56
+ end
57
+ puts ' ' + '-' * 55
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,9 @@
1
+ require 'check_digit'
2
+ require 'benchmark'
3
+
4
+ # of iterations to benchmark
5
+ BMITERS = 500_000
6
+
7
+ RSpec.configure do |config|
8
+ config.order = "random"
9
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ module CheckDigit
4
+ describe Verhoeff do
5
+ it "generates checksum values" do
6
+ val = (1..23).inject(1) { |val, i| Verhoeff.checksum val }
7
+ expect(val).to eq(150493068613366131371194)
8
+
9
+ expect(Verhoeff.checksum(100913449)).to eq(1009134492)
10
+ expect(Verhoeff.checksum(123)).to eq(1233)
11
+ end
12
+
13
+ it "generates a checkum value for an integer string" do
14
+ expect(Verhoeff.checksum("123")).to eq(1233)
15
+ end
16
+
17
+ it "raises an error for a non-integer string" do
18
+ expect {Verhoeff.checksum("A123")}.
19
+ to raise_error(ArgumentError, 'invalid argument')
20
+
21
+ expect {Verhoeff.valid?("A123")}.
22
+ to raise_error(ArgumentError, 'invalid argument')
23
+ end
24
+
25
+ it "raises an error for a non-integer numeric" do
26
+ expect {Verhoeff.checksum("123.45")}.
27
+ to raise_error(ArgumentError, 'invalid argument')
28
+
29
+ expect {Verhoeff.valid?("123.45")}.
30
+ to raise_error(ArgumentError, 'invalid argument')
31
+ end
32
+
33
+ it "tests an existing number for a valid checksum" do
34
+ expect(Verhoeff.valid?(9998)).to be true
35
+ expect(Verhoeff.valid?(9999)).to be false
36
+ end
37
+
38
+ it "benchmarks performance (#{BMITERS} iterations)" do
39
+ puts ' ' + '-' * 55
40
+ Benchmark.bm(10) do |x|
41
+ x.report(" generate:") {
42
+ i = BMITERS
43
+ while i > 1
44
+ Verhoeff.checksum(i)
45
+ i-=1
46
+ end
47
+ }
48
+
49
+ x.report(" validate:") {
50
+ i = BMITERS
51
+ while i > 1
52
+ Verhoeff.valid?(i)
53
+ i-=1
54
+ end
55
+ }
56
+ end
57
+ puts ' ' + '-' * 55
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ desc "Run RSpec code examples in progress mode"
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.verbose = false
6
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: check_digit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eric Litwin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ description: Ruby implementation of Luhn, Damm and Verhoeff check digit algorithms
42
+ email:
43
+ - eric.litwin@pnmac.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - .rspec
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - check_digit.gemspec
55
+ - lib/check_digit.rb
56
+ - lib/check_digit/damm.rb
57
+ - lib/check_digit/luhn.rb
58
+ - lib/check_digit/util.rb
59
+ - lib/check_digit/verhoeff.rb
60
+ - lib/check_digit/version.rb
61
+ - spec/damm_spec.rb
62
+ - spec/luhn_spec.rb
63
+ - spec/spec_helper.rb
64
+ - spec/verhoeff_spec.rb
65
+ - tasks/rspec.rake
66
+ homepage: http://github.com/elitwin/check_digit
67
+ licenses:
68
+ - MIT
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.6
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Check Digit calculator/validator
90
+ test_files:
91
+ - spec/damm_spec.rb
92
+ - spec/luhn_spec.rb
93
+ - spec/spec_helper.rb
94
+ - spec/verhoeff_spec.rb