#! /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