Validate International Securities Identification Number: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎{{header|zkl}}: rewrite to new spec)
Line 188: Line 188:


=={{header|Fortran}}==
=={{header|Fortran}}==

We need some functions equivalent to those in ctype.h in C. Here is a module.

<lang fortran>module ctype
implicit none
contains
elemental logical function isupper(c)
character, intent(in) :: c
integer :: n
n = iachar(c)
isupper = n >= 65 .and. n <= 90
end function
elemental logical function islower(c)
character, intent(in) :: c
integer :: n
n = iachar(c)
islower = n >= 97 .and. n <= 122
end function

elemental logical function isalpha(c)
character, intent(in) :: c
isalpha = isupper(c) .or. islower(c)
end function

elemental logical function isdigit(c)
character, intent(in) :: c
integer :: n
n = iachar(c)
isdigit = n >= 48 .and. n <= 57
end function
elemental logical function isalnum(c)
character, intent(in) :: c
isalnum = isdigit(c) .or. isalpha(c)
end function
end module</lang>

The main program follows.


<lang fortran>program isin
<lang fortran>program isin
Line 249: Line 210:
n = len_trim(a)
n = len_trim(a)
if (n /= 12) return
if (n /= 12) return
if (.not. isalpha(a(1:1)) .or. .not. isalpha(a(2:2))) return
do i = 3, 12
if (.not. isalnum(a(i:i))) return
end do
! Convert to an array of digits
! Convert to an array of digits
Line 260: Line 216:
k = iachar(a(i:i))
k = iachar(a(i:i))
if (k >= 48 .and. k <= 57) then
if (k >= 48 .and. k <= 57) then
if (i < 3) return
k = k - 48
k = k - 48
j = j + 1
j = j + 1
Line 265: Line 222:
else if (k >= 65 .and. k <= 90) then
else if (k >= 65 .and. k <= 90) then
k = k - 65 + 10
k = k - 65 + 10
j = j + 1
s(j) = k / 10
j = j + 1
s(j) = mod(k, 10)
else if (k >= 97 .and. k <= 122) then
k = k - 97 + 10
j = j + 1
j = j + 1
s(j) = k / 10
s(j) = k / 10

Revision as of 16:38, 7 August 2016

Validate International Securities Identification Number is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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.

Details

The format of an ISIN is as follows:

┌───────────── a 2-character ISO country code (A-Z)
 ┌─────────── a 9-character security code (A-Z, 0-9)
         ┌── a checksum digit (0-9)
AU0000XVGZA3

For this task, you may assume that any 2-character alphabetic sequence is a valid country code.

The checksum can be validated as follows:

  1. Replace letters with digits, by converting each character from base 36 to base 10, e.g. AU0000XVGZA31030000033311635103.
  2. 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 the implementation of this test here – you can just call the existing function from that task. (Add a comment stating if you did this.)


Test-cases
ISIN Validity Comment
US0378331005 valid
US0373831005 not valid The transposition typo is caught by the checksum constraint.
U50378331005 not valid The substitution typo is caught by the format constraint.
US03378331005 not valid The duplication 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. See #Perl_6 for a reference solution.)

See also

Useful resources:


Related tasks:



Ada

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

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! **

Fortran

<lang fortran>program isin

   use ctype
   implicit none
   character(20) :: test(7) = ["US0378331005        ", &
                               "US0373831005        ", &
                               "U50378331005        ", &
                               "US03378331005       ", &
                               "AU0000XVGZA3        ", &
                               "AU0000VXGZA3        ", &
                               "FR0000988040        "]
   print *, check_isin(test)

contains

   elemental logical function check_isin(a)
       character(*), intent(in) :: a
       integer :: s(24)
       integer :: i, j, k, n, v
       check_isin = .false.
       n = len_trim(a)
       if (n /= 12) return
       
       ! Convert to an array of digits
       j = 0
       do i = 1, n
           k = iachar(a(i:i))
           if (k >= 48 .and. k <= 57) then
               if (i < 3) return
               k = k - 48
               j = j + 1
               s(j) = k
           else if (k >= 65 .and. k <= 90) then
               k = k - 65 + 10
               j = j + 1
               s(j) = k / 10
               j = j + 1
               s(j) = mod(k, 10)
           else
               return
           end if
       end do
       ! Compute checksum
       v = 0
       do i = j - 1, 1, -2
           k = 2 * s(i)
           if (k > 9) k = k - 9
           v = v + k
       end do
       do i = j, 1, -2
           v = v + s(i)
       end do
       
       check_isin = 0 == mod(v, 10)
   end function

end program</lang>

Go

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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:

Works with: Rakudo version 2016.07

<lang perl6>sub is-valid-ISIN (Str $ISIN --> Bool) {

   $ISIN ~~ /^ <[A..Z]>**2 <[A..Z0..9]>**9 <[0..9]> $/ or return False;
   
   my $base10 = $ISIN.comb.map({ :36($_) }).join;
   
   return luhn-test $base10;

}</lang>

Testing:

<lang perl6>say "$_ is{' not' unless validate-ISIN $_} valid"

   for <US0378331005 US0373831005 U50378331005 US03378331005 AU0000XVGZA3 AU0000VXGZA3 FR0000988040></lang>
Output:
US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid

Python

<lang python>def check_isin(a):

   if len(a) != 12 or not all(c.isalpha() for c in a[:2]) or not all(c.isalnum() for c in a[2:]):
       return False
   s = "".join(str(int(c, 36)) for c in a)
   return 0 == (sum(sum(divmod(2 * (ord(c) - 48), 10)) for c in s[-2::-2]) +
                sum(ord(c) - 48 for c in s[-1::-2])) % 10

list(map(check_isin, ["US0378331005", "US0373831005", "U50378331005", "US03378331005",

                     "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"]))
  1. [True, False, False, False, True, True, True]</lang>

Racket

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

<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

This example needs updating due to a modification in the task. Please examine and fix the code if needed, then remove this message.

Details: Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task instead of duplicating it.

Works with: VB6

<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

Uses the luhn test from Luhn_test_of_credit_card_numbers#zkl (copied here as it is short). <lang zkl>fcn validateISIN(isin){

  isValid:=False;
  if(isin.len()==12){
     countryCode,securityCode,ckSum:=isin[0,2],isin[2,9],isin[-1];
     countryCode -=["A".."Z"].sink(String).walk();
     securityCode-=["A".."Z"].sink(String).walk() + "0123456789";
     ckSum       -="0123456789";
     if(not countryCode and not securityCode and not ckSum)  // valid format

isValid=luhnTest(isin.split("").apply("toInt",36).concat().toInt())

  }
  isValid

} fcn luhnTest(n){ // task uses 1 based index

  Utils.zipWith(fcn(n,odd){ if(odd) n else 2*n%10 + n/5 },
     n.toString().reverse().pump(List,"toInt"),
     Utils.Helpers.cycle(True,False))
  .sum() : return(_%10 == 0);

}</lang> <lang zkl>println(" ISIN Valid?"); foreach isin in (T("US0378331005","US0373831005","U50378331005", "US03378331005","AU0000XVGZA3","AU0000VXGZA3","FR0000988040")){

  println(isin," --> ",validateISIN(isin));

}</lang>

Output:
     ISIN       Valid?
US0378331005 --> True
US0373831005 --> False
U50378331005 --> False
US03378331005 --> False
AU0000XVGZA3 --> True
AU0000VXGZA3 --> True
FR0000988040 --> True