Validate International Securities Identification Number: Difference between revisions
(→{{header|Perl 6}}: Use the existing function from the Luhn task. Also, properly solve this task (including character class validation), and use non-obfuscated code.) |
(major rewrite of the task description, making it better formatted, less redundant with the existing Luhn task, and presenting more edge cases) |
||
Line 1: | Line 1: | ||
{{draft task}} |
|||
⚫ | |||
⚫ | |||
⚫ | |||
* separate the checksum digit from the ISIN |
|||
* convert the 11 resulting characters from base 36 to base 10 |
|||
* separate the digits in odd positions from the ones in even positions |
|||
* if the length of the converted ISIN is even: multiply each even digit by two |
|||
* if the length of the converted ISIN is odd, multiply each odd digit by two |
|||
* sum all the even and odd digits; if a number is higher then 9, for instance 14, then sum the individual digits: 1 + 4 |
|||
* apply mod 10 to the sum |
|||
* subtract the result from 10 |
|||
* apply mod 10 again |
|||
* compare the result with the checksum digit |
|||
{{introheader|Task}} |
|||
⚫ | |||
⚫ | |||
Write a function or program that takes a string as input, and checks whether it is a valid ISIN.<br> |
|||
It is only valid if it has the correct format, ''and'' the embedded checksum is correct. |
|||
Demonstrate that your code passes the test-cases listed below. |
|||
;Task: |
|||
Validate the following ISIN numbers, and print the results for: |
|||
{{introheader|Details}} |
|||
<code>US0378331005, US0373831009, D56000543287, AU0000XVGZA3, AU0000VXGZA3, GB0002634946, US0373831005</code>. |
|||
The format of an ISIN is as follows: |
|||
<!-- BEGIN DIAGRAM --> |
|||
⚫ | |||
<div style="margin:0.5em; white-space:nowrap; line-height:20px"> |
|||
<div><span style="font-size:20px; font-family:'Lucida Console',Monaco,monospace"><span style="color:green; margin:0 0 0 10px">┌───────────── </span></span><span style="color:green">a 2-character ISO country code (A-Z)</span></div> |
|||
<div><span style="font-size:20px; font-family:'Lucida Console',Monaco,monospace"><span style="color:green; margin:0 -10px 0 10px">│</span> <span style="color:blue; margin:0 0 0 10px">┌─────────── </span></span><span style="color:blue">a 9-character security code (A-Z, 0-9)</span></div> |
|||
<div><span style="font-size:20px; font-family:'Lucida Console',Monaco,monospace"><span style="color:green; margin:0 -10px 0 10px">│</span> <span style="color:blue; margin:0 -5px 0 10px">│</span> <span style="color:red; margin:0 0 0 5px">┌── </span></span><span style="color:red">a checksum digit</span></div> |
|||
<div style="font-size:20px; font-family:'Lucida Console',Monaco,monospace"><span style="background: #d9ebd9; color:green; border:solid 1px green; margin:0 1px">AU</span><span style="background:#e0e0ff; color:blue; border:solid 1px blue; margin:0 1px">0000XVGZA</span><span style="background:#feefef; color:red; border:solid 1px red; margin:0 1px">3</span></div> |
|||
</div> |
|||
<!-- END DIAGRAM --> |
|||
⚫ | |||
⚫ | |||
For instance: |
|||
* Replace letters with digits, by converting each character from base 36 to base 10, e.g. <code>AU0000XVGZA3</code> →<code>1030000033311635103</code>. |
|||
⚫ | |||
* Perform the Luhn test on this base-10 number. There is a separate task for this test: ''[[Luhn test of credit card numbers]]''. You don't have to replicate it here – you can just call the function from that task. (Add a comment stating if you did this.) |
|||
US0373831009 is valid |
|||
D56000543287 is not valid |
|||
⚫ | |||
AU0000VXGZA3 is valid |
|||
GB0002634946 is valid |
|||
US0373831005 is not valid</pre> |
|||
{{introheader|Test-cases}} |
|||
;See also: |
|||
⚫ | |||
{| class="wikitable" |
|||
⚫ | |||
! ISIN |
|||
! Validity |
|||
! Comment |
|||
|- |
|||
⚫ | |||
|- |
|||
| <tt>US0373831005</tt> || not valid || The transposition typo is caught by the checksum constraint. |
|||
|- |
|||
| <tt>US03378331005</tt> || not valid || The duplication typo is caught by the format constraint. |
|||
|- |
|||
| <tt>U50378331005</tt> || not valid || The substitution typo is caught by the format constraint. |
|||
|- |
|||
⚫ | |||
|- |
|||
| <tt>AU0000VXGZA3</tt> || valid || Unfortunately, not ''all'' transposition typos are caught by the checksum constraint. |
|||
|- |
|||
| <tt>FR0000988040</tt> || valid || |
|||
|} |
|||
(The comments are just informational. Your function should simply return a Boolean result.) |
|||
{{introheader|See also}} |
|||
Related tasks: |
|||
* [[Luhn test of credit card numbers]] |
|||
⚫ | |||
Useful resources: |
|||
⚫ | |||
⚫ | |||
⚫ | |||
<hr> |
|||
=={{header|Ada}}== |
=={{header|Ada}}== |
Revision as of 11:58, 7 August 2016
An International Securities Identification Number (ISIN) is a unique international identifier for a financial security such as a stock or bond.
Write a function or program that takes a string as input, and checks whether it is a valid ISIN.
It is only valid if it has the correct format, and the embedded checksum is correct.
Demonstrate that your code passes the test-cases listed below.
The format of an ISIN is as follows:
For this task, you may assume that any 2-character alphabetic sequence is a valid country code.
The checksum can be validated as follows:
- Replace letters with digits, by converting each character from base 36 to base 10, e.g.
AU0000XVGZA3
→1030000033311635103
. - Perform the Luhn test on this base-10 number. There is a separate task for this test: Luhn test of credit card numbers. You don't have to replicate it here – you can just call the function from that task. (Add a comment stating if you did this.)
ISIN | Validity | Comment |
---|---|---|
US0378331005 | valid | |
US0373831005 | not valid | The transposition typo is caught by the checksum constraint. |
US03378331005 | not valid | The duplication typo is caught by the format constraint. |
U50378331005 | not valid | The substitution typo is caught by the format constraint. |
AU0000XVGZA3 | valid | |
AU0000VXGZA3 | valid | Unfortunately, not all transposition typos are caught by the checksum constraint. |
FR0000988040 | valid |
(The comments are just informational. Your function should simply return a Boolean result.)
Related tasks:
Useful resources:
- Interactive online ISIN validator
- Wikipedia article: International Securities Identification Number
Ada
package ISIN
We start with specifying an Ada package (a collection of subprograms) to compute the checksum digit for a given ISIN (without checksum), and to check the ISIN (when given with the checksum).
<lang Ada>package ISIN is
subtype Decimal is Character range '0' .. '9'; subtype Letter is Character range 'A' .. 'Z'; Invalid_Character: exception; function Checksum(S: String) return Decimal; function Valid(S: String) return Boolean is (Checksum(S(S'First .. S'Last-1)) = S(S'Last));
end ISIN;</lang>
The implementation of the package is as follows.
<lang Ada>package body ISIN is
function To_Digits(S: String) return String is -- converts a string of decimals and letters into a string of decimals Offset: constant Integer := Character'Pos('A')-10; -- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ... begin if S = "" then
return "";
elsif S(S'First) = ' ' then -- skip blanks
return To_Digits(S(S'First+1 .. S'Last));
elsif S(S'First) in Decimal then
return S(S'First) & To_Digits(S(S'First+1 .. S'Last));
elsif S(S'First) in Letter then
return To_Digits(Integer'Image(Character'Pos(S(S'First))-Offset)) & To_Digits(S(S'First+1 .. S'Last));
else
raise Invalid_Character;
end if; end To_Digits; function Checksum(S: String) return Decimal is T: String := To_Digits(S); -- first convert letters to numbers by adding their ordinal position Double: Boolean := True; Sum: Integer range 0 .. 9 := 0; Add: Integer range 0 .. 18; Result: String(1 .. 2); begin for I in reverse T'Range loop
Add := Integer'Value(T(I .. I)); if Double then -- starting with the rightmost digit, every other digit is doubled Add := Add * 2; if Add > 8 then -- if Add is 1X (*10, 12, ..., 18*), add X+1 Add := (Add mod 10) + 1; end if; end if; Double := not Double; Sum := (Sum + Add) mod 10;
end loop; Result:= Integer'Image((10-Sum) mod 10); -- result is " X", with Decimal X return Result(2); end Checksum;
end ISIN;</lang>
Computing Checksums
Now the main program is easy. It reads a couple of ISINs (without checksum) from the command line and outputs the checksum digits.
<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;
procedure Compute_ISIN is begin
for I in 1 .. Ada.Command_Line.Argument_Count loop Ada.Text_IO.Put_Line("The Checksum for " &
Ada.Command_Line.Argument(I) & " is " & ISIN.Checksum(Ada.Command_Line.Argument(I)));
end loop;
end Compute_ISIN;</lang>
We compute the ISIN-Checksums for Apple, Apple with two digits swapped, the Treasury Corporation of Victoria, and the Treasury Corporation of Victoria with two digits swapped. Note that the first swap does actually change the checksum, while the second one does not. I.e., the ISIN checksums don't always discover flaws, such as swapping two adjacent digits.
./compute_isin US037833100 US037383100 AU0000XVGZA AU0000VXGZA The Checksum for US037833100 is 5 The Checksum for US037383100 is 9 The Checksum for AU0000XVGZA is 3 The Checksum for AU0000VXGZA is 3
Verifying ISINs with given Checksums
Similarily to the above, we check if an ISIN with checksum is valid.
<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;
procedure Check_ISIN is begin
for I in 1 .. Ada.Command_Line.Argument_Count loop if ISIN.Valid(Ada.Command_Line.Argument(I)) then Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " OK!"); else Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " ** Fail! **"); end if; end loop;
end Check_ISIN;</lang>
We check Apple's ISIN, and two "misspellings" of Apple's ISIN, we got by permuting two digits or letters. The error of permuting "US" to "SU" is not discovered by the algorithm, the error of permuting 83 to 38 is.
./check_isin US0378331005 SU0378331005 US0373831005 US0378331005 OK! SU0378331005 OK! US0373831005 ** Fail! **
Go
<lang go>package main
import (
"fmt" "regexp"
)
var numbers = []string{
"US0378331005", "US0373831009", "D56000543287", "AU0000XVGZA3", "AU0000VXGZA3", "GB0002634946", "US0373831005",
}
var r = regexp.MustCompile(`^[A-Z]{2}[A-Z0-9]{9}\d$`)
var inc = [2][10]int{
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 2, 4, 6, 8, 1, 3, 5, 7, 9},
}
func valid(n string) bool {
if !r.MatchString(n) { return false } var sum, p int for i := 10; i >= 0; i-- { p = 1 - p if d := n[i]; d < 'A' { sum += inc[p][d-'0'] } else { d -= 'A' sum += inc[p][d%10] p = 1 - p sum += inc[p][d/10+1] } } return (140-sum)%10 == int(n[11])-'0'
}
func main() {
for _, n := range numbers { if valid(n) { fmt.Println(n, "is valid") } else { fmt.Println(n, "is not valid") } }
}</lang>
- Output:
US0378331005 is valid US0373831009 is valid D56000543287 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid GB0002634946 is valid US0373831005 is not valid
Groovy
<lang groovy>CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
int checksum(String prefix) {
def digits = prefix.toUpperCase().collect { CHARS.indexOf(it).toString() }.sum() def groups = digits.collect { CHARS.indexOf(it) }.inject([[], []]) { acc, i -> [acc[1], acc[0] + i] } def ds = groups[1].collect { (2 * it).toString() }.sum().collect { CHARS.indexOf(it) } + groups[0] (10 - ds.sum() % 10) % 10
}
assert checksum('AU0000VXGZA') == 3 assert checksum('GB000263494') == 6 assert checksum('US037833100') == 5 assert checksum('US037833107') == 0</lang>
Haskell
<lang Haskell>import Data.Char ( isUpper , isDigit , digitToInt )
verifyISIN :: String -> Bool verifyISIN isin = (digitToInt $ last isin) == mod (10 - (digitSum `mod` 10)) 10
where firstEleven = take 11 isin converted = convertToNumber firstEleven multiplied = multiplyDigits converted digitSum = addUpDigits multiplied
convertToNumber :: String -> String convertToNumber str = concat $ map convert str
where convert :: Char -> String convert c = if isDigit c then show $ digitToInt c else show ( fromEnum c - 55 )
collectOddandEven :: String -> (String , String ) collectOddandEven term
|odd $ length term = (concat [take 1 $ drop n term | n <- [0,2..length term - 1]] , concat [take 1 $ drop d term | d <- [1,3..length term - 2]] ) |otherwise = (concat [take 1 $ drop n term | n <- [0,2..length term -2]] , concat [take 1 $ drop d term | d <- [1,3..length term - 1]] )
multiplyDigits :: String -> [Int] multiplyDigits digits
|odd $ length digits = (map ( (* 2) . digitToInt ) $ fst $ collectOddandEven digits) ++ ( map digitToInt $ snd $ collectOddandEven digits ) |otherwise = (map digitToInt $ fst $ collectOddandEven digits ) ++ (map ( (* 2) . digitToInt ) $ snd $ collectOddandEven digits)
addUpDigits :: [Int] -> Int addUpDigits list = sum $ map (\d -> if d > 9 then sum $ map digitToInt $ show d else d ) list
printSolution :: String -> IO ( ) printSolution str = do
putStr $ str ++ " is" if verifyISIN str == True then putStrLn " valid" else putStrLn " not valid"
main :: IO ( ) main = do
let isinnumbers = ["US0378331005" , "US0373831009" , "D56000543287" , "AU0000XVGZA3" , "AU0000VXGZA3" , "GB0002634946" , "US0373831005"] mapM_ printSolution isinnumbers</lang>
- Output:
US0378331005 is valid US0373831009 is valid D56000543287 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid GB0002634946 is valid US0373831005 is not valid
J
<lang j>splt=: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' i. ' ' -.~ ": checksum=: 3 : '10| - +/ splt (* 2 1 $~ #) |. splt splt y'
assert 5 = checksum 'US037833100' assert 0 = checksum 'US037833107' assert 3 = checksum 'AU0000VXGZA' assert 6 = checksum 'GB000263494'</lang>
Java
<lang java>public class ISIN {
public static void main(String[] args) { String[] isins = {"US0378331005", "US0373831009", "D56000543287", "AU0000XVGZA3", "AU0000VXGZA3", "GB0002634946", "US0373831005"}; for (String isin : isins) System.out.printf("%s is %s%n", isin, ISINtest(isin) ? "valid" : "not valid"); }
static boolean ISINtest(String isin) { isin = isin.trim().toUpperCase();
if (!isin.matches("^[A-Z]{2}[A-Z0-9]{9}\\d$")) return false;
int checkDigit = Character.digit(isin.charAt(11), 10);
StringBuilder sb = new StringBuilder(); for (char c : isin.substring(0, 11).toCharArray()) sb.append(Character.digit(c, 36));
return checkDigit == checkDigit(sb); }
static int checkDigit(StringBuilder sb) { int sum = 0; int len = sb.length(); for (int i = 1; i <= len; i++) { int ordinal = Character.digit(sb.charAt(i - 1), 10); if ((len % 2 == 0 && i % 2 == 0) || (len % 2 == 1 && i % 2 == 1)) { sum += (ordinal / 5) + (2 * ordinal) % 10; } else { sum += ordinal; } } return (10 - (sum % 10)) % 10; }
}</lang>
US0378331005 is valid US0373831009 is valid D56000543287 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid GB0002634946 is valid US0373831005 is not valid
Perl 6
Using the luhn-test function defined at Luhn test of credit card numbers#Perl 6:
<lang perl6>sub validate-isin (Str $isin --> Bool) {
$isin ~~ /<[A..Z]>**2 <[A..Z0..9]>**9 <[0..9]>/ or return False; my $base10 = $isin.comb.map({ :36($_) }).join; luhn-test $base10;
}</lang>
Testing:
<lang perl6>say "$_ is {'not ' unless validate-isin $_}valid"
for <US0378331005 US0373831009 D56000543285 AU0000XVGZA3 AU0000VXGZA3 GB0002634946 US0373831005></lang>
- Output:
US0378331005 is valid US0373831009 is valid D56000543285 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid GB0002634946 is valid US0373831005 is not valid
Python
<lang python>def check_isin(a):
s = "".join(str(int(c, 36)) for c in a[:-1]) return ord(a[-1]) - 48 == (- sum(sum(divmod(2 * (ord(c) - 48), 10)) for c in s[-1::-2]) - sum(ord(c) - 48 for c in s[-2::-2])) % 10
list(map(check_isin, ["US0378331005", "US0373831009", "D56000543287", "AU0000XVGZA3",
"AU0000VXGZA3", "GB0002634946", "US0373831005"]))
- [True, True, False, True, True, True, False]</lang>
Racket
<lang racket>#lang racket
(define-logger issn) (current-logger issn-logger)
(define ((letter-char->digits a-point-integer) c)
(call-with-values (λ () (quotient/remainder (+ 10 (- (char->integer c) a-point-integer)) 10)) list))
(define char->digits
(let ((char-code:A (char->integer #\A)) (char-code:a (char->integer #\a)) (char-code:0 (char->integer #\0))) (match-lambda [(? char-whitespace?) null] [(? char-numeric? c) (list (- (char->integer c) char-code:0))] [(? char-upper-case? (app (letter-char->digits char-code:A) dd)) dd] [(? char-lower-case? (app (letter-char->digits char-code:a) dd)) dd])))
(define (string->ISIN-digits s)
(apply append (map char->digits (string->list s))))
(define (ISIN-checksum s)
(define (mod-10 n) (modulo n 10)) (define sum (for/fold ((sum 0)) ((d (reverse (string->ISIN-digits s))) (i (in-naturals))) (mod-10 (+ sum (cond [(odd? i) d] ;; (even? i) henceforth... [(> d 4) (+ 1 (mod-10 (* d 2)))] [else (* d 2)]))))) (mod-10 (- 10 sum)))
(define check-ISIN
(match-lambda [(regexp #rx"(...........)(.)" (list isin (and body (app ISIN-checksum sum)) (app string->number cksum))) (define good? (= sum cksum)) (log-debug "check-ISIN: ~s. ~s wants check sum ~a. got ~a [~a]" isin body sum cksum good?) good?]))
(module+ test
(require tests/eli-tester) (test (char->digits #\A) => '(1 0) (char->digits #\a) => '(1 0) (char->digits #\Z) => '(3 5) (char->digits #\z) => '(3 5) (char->digits #\0) => '(0) (char->digits #\9) => '(9) (char->digits #\space) => '() (string->ISIN-digits "US037833100") => '(3 0 2 8 0 3 7 8 3 3 1 0 0) (ISIN-checksum "US037833100") => 5 (ISIN-checksum "US037383100") => 9 (ISIN-checksum "AU0000XVGZA") => 3 (ISIN-checksum "AU0000VXGZA") => 3 (check-ISIN "US0378331005") => #t (check-ISIN "SU0378331005") => #t (check-ISIN "US0373831005") => #f))</lang>
All tests pass.
REXX
<lang rexx>/*REXX program calculates the checksum digit for an International Securities ID number.*/ parse arg z /*obtain optional ISINs from the C.L.*/ if z= then z= "US037833100 US037383100 AU0000XVGZA AU0000VXGZA GB000263494"
/* [↑] ISINs given? Then use default.*/ do n=1 for words(z); x=word(z, n) /*process each of the specified ISINs. */ if length(x) < 9 then x=right(z, 9, 0) /*ISIN too short? Then pad with zeroes.*/ $= /* [↓] construct list of ISIN digits. */ do k=1 for length(x) /*the ISIN may contain alphabetic chars*/ $=$ || pos( substr(x, k, 1), '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') end /*k*/ /* [↑] convert alphabetic ──► digits.*/ @.= /* [↓] construct two groups of digits.*/ do g=1 for length($); e=g // 2 /*e: the oddness/evenness of the group.*/ @.e=@.e || substr($, g, 1) /* [↑] e=1, it's odd; e=0, it's even.*/ end /*g*/ /* [↑] // is the remainder in REXX. */ /* ┌── double the digits of the group*/ @.e=translate(@.e, 246813579, 123456789) /* ◄─┘ that contains the last ISIN dig*/ s=0 /*initialize the sum of decimal digits.*/ do m=0 for 2 /* [↓] sum both groups of ISIN digits.*/ do i=1 for length(@.m) /* [↓] sum the ISIN digits for a group*/ s=s + substr(@.m, i, 1) /*add a group's digit to the sum. */ end /*i*/ end /*m*/ /* [↑] calculate the checksum for ISIN*/ ch=right(10 - s//10, 1) /*final calculation for the checksum. */ say ' check sum is ' ch " for ISIN: " x /*display the checksum for the ISIN. */ end /*n*/ /*stick a fork in it, we're all done. */</lang>
output when using the defaults for input:
check sum is 5 for ISIN: US037833100 check sum is 9 for ISIN: US037383100 check sum is 3 for ISIN: AU0000XVGZA check sum is 3 for ISIN: AU0000VXGZA check sum is 6 for ISIN: GB000263494
Tcl
<lang Tcl>package require Tcl 8.6 ;# mostly needed for [assert]. Substitute a simpler one or a NOP if required.</lang>
A proc like assert is always good to have around. This one tries to report values used in its expression using subst:
<lang Tcl>proc assert {expr} { ;# for "static" assertions that throw nice errors
if {![uplevel 1 [list expr $expr]]} { set msg "{$expr}" catch {append msg " {[uplevel 1 [list subst -noc $expr]]}"} tailcall throw {ASSERT ERROR} $msg }
}</lang>
isin itself is a simple package. We compute the alphabet when the package is loaded in _init, because that's more fun than typing out the table:
<lang Tcl>namespace eval isin {
proc _init {} { ;# sets up the map used on every call variable map set alphabet abcdefghijklmnopqrstuvwxyz set n 9 lmap c [split $alphabet ""] { lappend map $c [incr n] } } _init
proc normalize {isin} { variable map string map $map [string tolower [string trim $isin]] }
proc cksum {isin} { set isin [normalize $isin] assert {[string is digit -strict $isin]} set digits [split $isin ""] if {[llength $digits] % 2} { set digits [list 0 {*}$digits] } foreach {o e} $digits { incr sum [expr {$o + ($e * 2) % 9}] } expr {(10 - ($sum % 10)) % 10} }
proc validate {isin} { set isin [normalize $isin] regexp {^(.*)(.)$} $isin -> body sum expr {$sum eq [cksum $body]} }
}</lang>
Finally, some tcltests pinched from other examples in this page:
<lang Tcl>package require tcltest tcltest::test isin-1 "Test isin validation" -body {
foreach {str sum} { US037833100 5 US037383100 9 SU037833100 5 AU0000XVGZA 3 AU0000VXGZA 3 GB000263494 6 } { assert {[isin::cksum $str] eq $sum} assert {![isin::validate $str$sum]}
set err [expr {1+int(rand()*8)}] ;# create a random checksum error set sum [expr {$sum + $err % 10}] assert {![isin::validate $str$sum]} } return ok
} -result ok </lang>
Visual Basic
<lang vb> Option Explicit
Function MakeIsinCode(Exchange As String, security As String)
Dim numLeadingZeroes As Integer numLeadingZeroes = 9 - Len(security) Dim leader As String leader = Exchange & String(numLeadingZeroes, "0") & security MakeIsinCode = leader & CStr(IsinCheckDigit(leader))
End Function
Function IsinCheckDigit(ByVal security As String) As Integer
Dim digits As String Dim i As Integer For i = 1 To Len(security) Dim ch As String ch = UCase(Mid(security, i, 1)) If ch >= "A" And ch <= "Z" Then ' A to Z translated to "10", "11", .. "35" digits = digits & CStr(Asc(ch) - 55) ElseIf ch >= "0" And ch <= "9" Then digits = digits & ch Else Err.Raise 50001, , "Security must contain only letters and digits" End If Next Dim total As Integer Dim tmp As Integer total = 0 'If rightmost even, "other" digits for doubling are 2,4,6. If rightmost odd, they're 1,3,5. 'rightmost digit is always doubled, so start with it and work backwards Dim other As Boolean other = True For i = Len(digits) To 1 Step -1 tmp = CInt(Mid(digits, i, 1)) If other Then If tmp < 5 Then ' 0 to 4 map to 0,2,4,6,8 total = total + (tmp * 2) Else ' 5 to 9 map to 1,3,5,7,9 total = total + ((tmp * 2) - 9) End If Else total = total + tmp End If 'Toggle doubling flag other = Not other Next 'Last Mod 10 is to wrap 10 to zero IsinCheckDigit = (10 - (total Mod 10)) Mod 10
End Function </lang>
zkl
<lang zkl>const CHARS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
fcn checksum(prefix){
digits:=prefix.toUpper().reduce(fcn(s,it){ s + CHARS.index(it) },""); groups:=digits.split("").reduce(fcn(acc,i){ T(acc[1],acc[0] + i) },T(T,T)); ds:=groups[1].reduce(fcn(s,it){ s + 2*it },"").split("").extend(groups[0]); (10 - ds.sum(0) % 10) % 10
}</lang> <lang zkl>foreach prefix in (T("AU0000VXGZA","GB000263494","US037833100","US037833107")){
println(prefix," --> ",checksum(prefix));
}</lang>
- Output:
AU0000VXGZA --> 3 GB000263494 --> 6 US037833100 --> 5 US037833107 --> 0