#! /usr/bin/perl
# Copyright (C) 2003 Nick Urbanik
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package HKID;
use warnings;
use strict;
use vars qw( $VERSION );
$VERSION = "0.01";
use constant DIVISOR => 11;
use constant TEST_map_letter => 0;
use constant TEST_calc_check_digit => 0;
use constant TEST_verify_hkids => 0;
sub map_letter($) {
my $letter = uc shift;
my $num = ord( $letter ) - ord "A";
$num %= DIVISOR;
return $num + 1;
}
sub calc_check_digit($) {
my $hkid = uc shift;
return unless length $hkid >= 7;
my @digits = split "", substr $hkid, 0, 7;
$digits[ 0 ] = map_letter $digits[ 0 ];
my $total = 0;
for ( my $i = 0; $i < 7; ++$i ) {
$total += $digits[ $i ] * ( reverse 2..8 )[ $i ];
}
my $digit = $total % DIVISOR;
$digit = DIVISOR - $digit if $digit;
$digit = 'A' if $digit == 10;
return wantarray ? ( $digit, $total ) : $digit;
}
sub verify_hkids {
my @fixed_ids = @_;
foreach my $hkid ( @fixed_ids ) {
$hkid = uc $hkid;
my $current_check_digit;
if ( $hkid =~ /[A-Z]\d{6}\(([A\d?])\)/ ) {
$current_check_digit = $1;
} else {
undef $hkid;
next;
}
my ( $correct_check_digit, $t ) = calc_check_digit $hkid;
unless ( $current_check_digit eq $correct_check_digit ) {
# print "$current_check_digit, $correct_check_digit, $t, $hkid, ";
substr( $hkid, 8, 1 ) = $correct_check_digit;
# print "$hkid\n";
}
}
return wantarray ? @fixed_ids : $fixed_ids[ 0 ];
}
if ( TEST_map_letter ) {
foreach ( 'A'..'Z' ) {
print "$_ => ", map_letter $_, "\n";
}
}
if ( TEST_calc_check_digit ) {
foreach ( @ARGV ) {
print "$_, check digit = ", calc_check_digit $_, "\n";
}
}
if ( TEST_verify_hkids ) {
my @hkids = verify_hkids @ARGV;
foreach ( @ARGV ) {
my $correct_id = shift @hkids;
if ( ! $correct_id ) {
print "$_ is not recognisable as a HKID.\n";
}
elsif ( uc eq $correct_id ) {
print "$correct_id has a correct check digit.\n";
} else {
print "$_ should be $correct_id\n";
}
}
}
1;
=pod
Date: Tue, 28 Oct 2003 22:28:50 +0800
From: KEUNG Kai Chung
Subject: Check-Digit in HKID
To: nicku@vtc.edu.hk
Hello Nick,
Here is the calculation of check-digit in HKID.
First, convert the first letter into a number according to the following:
A, L, W >> 1
B, M, X >> 2
C, N, Y >> 3
D, O, Z >> 4
E, P >> 5
F, Q >> 6
G, R >> 7
H, S >> 8
I, T >> 9
J, U >> 10
K, V >> 11
Then, multiply the first seven numbers by 8, 7, 6, 5, 4, 3 and 2
respectively. After that, calculate the sum. Find the remainder when
divided by 11. Finally, the check-digit is the result of subtract the
remainder from 11. (if the check-digit is 10, represented by A)
[Note by Nick: that should really be:
Find the smallest positive integer that must be added so that it
becomes a mulitple of 11]
For example, the HKID is H123456(?)
1. convert 'H' to 8
2. (8*8) + (1*7) + (2*6) + (3*5) + (4*4) + (5*3) + (6*2) = 141
3. 141/11 = 12, remainder is 9
4. 11 - 9 = 2
so, the HKID is H123456(2)
Hope the above information is useful for you.
Chung
=cut