package Algorithm::Verhoeff; use 5.0; use strict; use warnings; #use bignum; # Needed so large numbers don't get turned into standard form require Exporter; our @ISA = qw(Exporter); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use Algorithm::Verhoeff ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'all' => [ qw( verhoeff_get verhoeff_check ) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = qw( verhoeff_get verhoeff_check ); our $VERSION = '0.3'; # Preloaded methods go here. our $di; #Dihedral matrix our @inverted = (0, 4, 3, 2, 1, 5, 6, 7, 8, 9 ); our $f; # First, build $f according to a simple(?) equation BEGIN{ $f->[0] = [(0 .. 9)]; $f->[1] = [( 1, 5, 7, 6, 2, 8, 3, 0, 9, 4 )]; my $i=2; my $j=0; while($i < 8) { while($j < 10) { $f->[$i]->[$j] = $f->[$i - 1]->[$f->[1]->[$j]]; $j++; } $j = 0; $i++; } # This is defined $di->[0] = [( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 )]; $di->[1] = [( 1, 2, 3, 4, 0, 6, 7, 8, 9, 5 )]; $di->[2] = [( 2, 3, 4, 0, 1, 7, 8, 9, 5, 6 )]; $di->[3] = [( 3, 4, 0, 1, 2, 8, 9, 5, 6, 7 )]; $di->[4] = [( 4, 0, 1, 2, 3, 9, 5, 6, 7, 8 )]; $di->[5] = [( 5, 9, 8, 7, 6, 0, 4, 3, 2, 1 )]; $di->[6] = [( 6, 5, 9, 8, 7, 1, 0, 4, 3, 2 )]; $di->[7] = [( 7, 6, 5, 9, 8, 2, 1, 0, 4, 3 )]; $di->[8] = [( 8, 7, 6, 5, 9, 3, 2, 1, 0, 4 )]; $di->[9] = [( 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 )]; } # Now that's all set up, we can actually do stuff. sub verhoeff_check { my $input = shift; my $c = 0; # initialize check at 0 my $digit; my $i = 0; foreach $digit (reverse split(//, $input)) { $c = $di->[$c]->[$f->[$i % 8]->[$digit]]; # did you catch that? $i++; } if($c) { return 0; # a non-zero value of $c is a check failure } return 1; } sub verhoeff_get { my $input = shift; my $c = 0; # initialize check at 0 my $digit = 0; my $i = 0; my $r; foreach $digit (reverse split(//, $input)) { $c = $di->[$c]->[$f->[($i+1) % 8]->[$digit]]; # not quite the same... $i++; } return $inverted[$c]; } 1; __END__ =head1 NAME Algorithm::Verhoeff - Perl extension for checking and computing Verhoeff check digits =head1 SYNOPSIS use Algorithm::Verhoeff; my $long_number = 1456789; # add a check digit to that to catch typos! $long_number .= verhoeff_get($long_number); # note - append don't add! print $long_number; #prints 14567894 # Lets see if I can re-type that accurately my $test = '14657894'; # oops! unless (verhoeff_check($test)) { print "Failed check - typo?"; } =head1 DESCRIPTION This implements the Verhoeff check digit algorithm. It's a single digit checksum designed specifically for catching data entry mistakes in number sequences. It catches the vast majority of common mistakes such as transposed digits, ommitted digits, double entered digits and so on. =head2 EXPORT By default, this module will export verhoeff_check() verhoeff_get() Into the current package. =head1 USAGE Using numbers that pass the verhoeff check is useful for things like product codes. This is because such numbers almost never pass the verhoeff check if they as mis-typed. This includes common typos such as ommitted or repeated digits, transposed digits and so on. Since it only adds a single digit onto what might already be a longish number, it's a good algorithm for use where humans need to enter or read the numbers. When we say 'number' we really mean 'string of digits' since that is what the Verhoeff algorithm works on. To generate such a number, pick a starting number, call verhoeff_check() to get a check digit, and then APPEND that digit to the end of the original number. Do NOT add the digit arithmetically to the original number. The new number will how pass the verhoeff_check(). In other words, if verhoeff_check() is called with the new number as its argument, it will return 1. If it is called with a mistyped version of the original number it will (very probably) return 0. For common forms of typo such as ommitted digits, accuracy is over 99%. =head2 verhoeff_get($number) verhoeff_get accepts a number or string as an argument, and returns a check digit between 0 and 9. =head2 verhoeff_check($number) verhoeff_check accepts a number of string as an argument, and returns 1 if it passes the check, or 0 otherwise. =head1 WARNING Both functions convert their argument to a string internally. This will break for large (<32 bit)numbers due to Perl representing them in standard form when stringified. To get round this, either pass the number as a string to begin with, or use the bignum module in your program. Thus: my $num = 57382957482395748329574923; # Big number! verhoeff_get($num); # Fatal error unless program uses bignum module. But: my $num = '57382957482395748329574923'; # Long string! verhoeff_get($num); # Works fine. =head1 SEE ALSO L =head1 AUTHOR Jon Peterson (jon -at- snowdrift.org) =head1 COPYRIGHT AND LICENSE Copyright (C) 2004 by Jon Peterson This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.4 or, at your option, any later version of Perl 5 you may have available. =cut