Validate International Securities Identification Number

From Rosetta Code
Task
Validate International Securities Identification Number
You are encouraged to solve this task according to the task description, using any language you may know.

An International Securities Identification Number (ISIN) is a unique international identifier for a financial security such as a stock or bond.


Task

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 #Raku for a reference solution.)


Related task:


Also see



11l[edit]

Translation of: Python
F check_isin(a)
   I a.len != 12
      R 0B
   [Int] s
   L(c) a
      I c.is_digit()
         I L.index < 2
            R 0B
         s.append(c.code - 48)
      E I c.is_uppercase()
         I L.index == 11
            R 0B
         V (d, m) = divmod(c.code - 55, 10)
         s [+]= [d, m]
      E
         R 0B
   V v = sum(s[((len)-1..).step(-2)])
   L(=k) s[((len)-2..).step(-2)]
      k = 2 * k
      v += I k > 9 {k - 9} E k
   R v % 10 == 0

print([‘US0378331005’, ‘US0373831005’, ‘U50378331005’, ‘US03378331005’,
       ‘AU0000XVGZA3’, ‘AU0000VXGZA3’, ‘FR0000988040’].map(s -> check_isin(s)))
Output:
[1B, 0B, 0B, 0B, 1B, 1B, 1B]

360 Assembly[edit]

*        Validate ISIN             08/03/2019
VALISIN  CSECT
         USING  VALISIN,R13        base register
         B      72(R15)            skip savearea
         DC     17F'0'             savearea
         SAVE   (14,12)            save previous context
         ST     R13,4(R15)         link backward
         ST     R15,8(R13)         link forward
         LR     R13,R15            set addressability
         LA     R7,1               j=1
       DO WHILE=(C,R7,LE,=A(NN))   do j=1 to hbound(tt)
         LR     R1,R7              j
         SLA    R1,4               ~
         LA     R4,TT-16(R1)       @tt(j)
         MVC    CC,0(R4)           cc=tt(j)
         MVC    C,=CL28' '         c=' '
         MVC    R,=CL28' '         r=' '
         MVI    ERR,X'00'          err=false
         MVC    LCC,=F'0'          lcc=0
         LA     R1,L'CC            i=length(cc)
LENTRIA  LA     R5,CC-1            @cc
         AR     R5,R1              +i
         CLI    0(R5),C' '         if cc[i]=' '
         BE     LENTRIB            then iterate loop
         ST     R1,LCC             lcc=lentrim(cc)
         B      LENTRIC            leave loop
LENTRIB  BCT    R1,LENTRIA         i--; if i<>0 then loop
LENTRIC  L      R4,LCC             lcc
       IF    CH,R4,EQ,=H'12' THEN  if lcc=12 then
         MVC    LC,=F'0'           lc=0
         MVC    WW,=CL28' '        ww='' 
         LA     R10,WW             @ww
         LA     R6,1               i=1 
       DO WHILE=(C,R6,LE,LCC)      do i=1 to lcc
         LA     R4,CC-1            @cc
         AR     R4,R6              +i 
         MVC    CI(1),0(R4)        ci=substr(cc,i,1)
         LA     R2,BASE36          @base36
         LA     R3,L'BASE36        length(base36)
         BAL    R14,INDEX          r0=index(base36,ci)
       IF   LTR,R0,NZ,R0 THEN      if p<>0 then
         LR     R1,R0                ip
         BCTR   R1,0                 -1
         XDECO  R1,XDEC              str(ip-1)
         MVC    0(2,R10),XDEC+10     ww=ww||str(p-1)
       ELSE     ,                  else
         MVI    ERR,X'FF'            err=true
       ENDIF    ,                  endif
         LA     R10,2(R10)           @ww+=2
         LA     R6,1(R6)             i++
       ENDDO    ,                  enddo i
         MVC    C,=CL28' '         c=''
         LA     R8,WW              @ww
         LA     R9,C               @c
         LA     R10,0              length(c)
         LA     R6,1               i=1 
       DO WHILE=(C,R6,LE,=A(L'WW)) do i=1 to length(ww)
       IF   CLI,0(R8),NE,C' ' THEN   if ww[i]<>' ' then
         MVC    0(1,R9),0(R8)          c=ww[i]
         LA     R9,1(R9)               @c++
         LA     R10,1(R10)             length(c)++
       ENDIF    ,                    endif
         LA     R8,1(R8)             @ww++
         LA     R6,1(R6)             i++
       ENDDO    ,                  enddo i
         ST     R10,LC             lc=length(c)
         LA     R6,1               i=1
       DO WHILE=(CH,R6,LE,=H'2')   do i=1 to 2
         LA     R4,CC-1              @cc
         AR     R4,R6                +i
         MVC    CI(1),0(R4)          ci=substr(cc,i,1)
         LA     R2,ALPHA             @alpha
         LA     R3,L'ALPHA           length(alpha)
         BAL    R14,INDEX            r0=index(alpha,ci)
       IF   LTR,R0,Z,R0 THEN         if index(alpha,ci)=0 then 
         MVI    ERR,X'FF'              err=true
       ENDIF    ,                    endif
         LA     R6,1(R6)             i++
       ENDDO    ,                  enddo i
         SR     R8,R8              i1=0
         SR     R9,R9              i2=0
       IF   CLI,ERR,EQ,X'00' THEN  if not err then
         SR     R0,R0              0
         L      R6,LC              i=lc
         MVC    R,=CL28' '         r=''
         LA     R10,C              @c
         LA     R11,R-1            @r
         A      R11,LC             @r=@r+length(strip((c))
       DO WHILE=(CH,R6,GE,=H'1')    do i=lc to 1 step -1
         MVC    0(1,R11),0(R10)      r[k]=c[i]
         BCTR   R11,0                @r--
         LA     R10,1(R10)           @c++
         BCTR   R6,0                 i--
       ENDDO    ,                  enddo i
         LA     R6,1               i=1
       DO WHILE=(C,R6,LE,LC)       do i=1 to lc step 2
         LA     R4,R-1               @r
         AR     R4,R6                +i
         MVC    CI(1),0(R4)          ci=substr(r,i,1)
         MVC    XDEC,=CL12' '        ~
         MVC    XDEC(L'CI),CI        ci
         XDECI  R2,XDEC              int(ci)
         AR     R8,R2                i1=i1+int(ci)
         LA     R6,2(R6)             i+=2
       ENDDO    ,                  enddo i
         LA     R6,2               i=2
       DO WHILE=(C,R6,LE,LC)       do i=2 to lc step 2
         LA     R4,R-1               @r
         AR     R4,R6                +i
         MVC    CI(1),0(R4)          ci=substr(r,i,1)
         MVC    XDEC,=CL12' '        ~
         MVC    XDEC(L'CI),CI        ci
         XDECI  R10,XDEC             int(ci)
         SLA    R10,1                ii=int(ci)*2
       IF CH,R10,GE,=H'10' THEN       if ii>=10 then
         SH     R10,=H'9'              ii=ii-9
       ENDIF     ,                   endif
         AR     R9,R10               i2=i2+ii
         LA     R6,2(R6)             i++
       ENDDO    ,                  enddo i
         LR     R2,R8              i1
         AR     R2,R9              +i2
         XDECO  R2,XDEC               s=str(i1+i2)
       IF CLI,XDEC+11,EQ,C'0' THEN if substr(s,length(s),1)='0' then
         MVC    MSG,=CL6'OK'         msg='ok'
       ELSE     ,                  else
         MVC    MSG,=CL6'?err1'      msg='?1'
       ENDIF    ,                  endif 
       ELSE     ,                  else
         MVC    MSG,=CL6'?err2'      msg='?2'
       ENDIF    ,                  endif 
       ELSE     ,                  else
         MVC    MSG,=CL6'?err3'      msg='?3'
       ENDIF    ,                  endif
         XDECO  R7,XDEC            edit j
         MVC    PG(2),XDEC+10      j
         MVC    PG+3(16),CC        cc
         MVC    PG+20(6),MSG       msg
         XPRNT  PG,L'PG            print buffer
         LA     R7,1(R7)           j++
       ENDDO    ,                  enddo j
         L      R13,4(0,R13)       restore previous savearea pointer
         RETURN (14,12),RC=0       restore registers from calling sav
MVCX     MVC    0(0,R4),0(R5)      pattern svc
INDEX    SR     R0,R0              index(r2,ci) r3=len
         LA     R1,1               k=1
SINDEXA  CR     R1,R3              do k=1 to length(ca)
         BH     SINDEXC              ~
         CLC    0(1,R2),CI           if ca[k]=ci
         BNE    SINDEXB              then iterate loop
         LR     R0,R1                ii=k
         B      SINDEXC              exit loop
SINDEXB  LA     R2,1(R2)             @ca++
         LA     R1,1(R1)             k++
         B      SINDEXA            enddo
SINDEXC  BR     R14                end index
NN       EQU    (BASE36-TT)/16     number of items
TT       DC     CL16'US0378331005',CL16'US0373831005'
         DC     CL16'U50378331005',CL16'US03378331005'
         DC     CL16'AU0000XVGZA3',CL16'AU0000VXGZA3'
         DC     CL16'FR0000988040'
BASE36   DC     CL36'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ALPHA    DC     CL26'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
ERR      DS     X                  error
LCC      DS     F                  length of cc
LC       DS     F                  length of c
CI       DS     CL1
CC       DS     CL16               current element of tt
C        DS     CL28
R        DS     CL28
WW       DS     CL28
MSG      DS     CL6                message
PG       DC     CL80' '            buffer
XDEC     DS     CL12               temp for xdeco and xdeci
         REGEQU
         END    VALISIN
Output:
 1 US0378331005     OK
 2 US0373831005     ?err1
 3 U50378331005     ?err2
 4 US03378331005    ?err3
 5 AU0000XVGZA3     OK
 6 AU0000VXGZA3     OK
 7 FR0000988040     OK

Ada[edit]

Calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task.

procedure ISIN is
   -- Luhn_Test copied from other Task
   function Luhn_Test (Number: String) return Boolean is
      Sum  : Natural := 0;
      Odd  : Boolean := True;
      Digit: Natural range 0 .. 9;
   begin
      for p in reverse Number'Range loop
         Digit := Integer'Value (Number (p..p));
         if Odd then
            Sum := Sum + Digit;
         else
            Sum := Sum + (Digit*2 mod 10) + (Digit / 5);
         end if;
         Odd := not Odd;
      end loop;
      return (Sum mod 10) = 0;
   end Luhn_Test;
   
   subtype Decimal   is Character range '0' .. '9';
   subtype Letter    is Character range 'A' .. 'Z';
   subtype ISIN_Type is String(1..12);
   
   -- converts a string of decimals and letters into a string of decimals
   function To_Digits(S: String) return String is
      -- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ...
      Offset: constant Integer := Character'Pos('A')-10;
   
      Invalid_Character: exception;
   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 Is_Valid_ISIN(S: ISIN_Type) return Boolean is
      Number : String := To_Digits(S);
   begin
      return S(S'First)   in Letter  and
             S(S'First+1) in Letter  and
             S(S'Last)    in Decimal and
             Luhn_Test(Number);
   end Is_Valid_ISIN;

   Test_Cases : constant Array(1..6) of ISIN_Type :=
      ("US0378331005",
       "US0373831005",
       "U50378331005",
       -- excluded by type with fixed length
       -- "US03378331005",
       "AU0000XVGZA3",
       "AU0000VXGZA3",
       "FR0000988040");
begin
   for I in Test_Cases'Range loop
      Ada.Text_IO.Put_Line(Test_Cases(I) & ":" &
        Boolean'Image(Is_Valid_ISIN(Test_Cases(I))));
   end loop;
   -- using wrong length will result in an exception:
   Ada.Text_IO.Put("US03378331005:");
   Ada.Text_IO.Put_Line(Boolean'Image(Is_Valid_Isin("US03378331005")));
exception
   when others =>
      Ada.Text_IO.Put_Line("Exception occured");
end ISIN;

Output:

US0378331005:TRUE
US0373831005:FALSE
U50378331005:FALSE
AU0000XVGZA3:TRUE
AU0000VXGZA3:TRUE
FR0000988040:TRUE
US03378331005:Exception occured

ALGOL W[edit]

Uses the LuhnTest procedure from the Luhn test of credit card numbers task.

begin
    % external procedure that returns true if ccNumber passes the Luhn test, false otherwise %
    logical procedure LuhnTest ( string(32) value ccNumber
                               ; integer    value ccLength
                               ) ; algol "LUHN" ;

    % returns true if isin is a valid ISIN, false otherwise    %
    logical procedure isIsin ( string(32) value isin ) ;
        if isin( 12 // 20 ) not = "" then false % code is too long %
        else begin
            % the first two characters must be upper-case letters %

            % returns the digit corresponding to a character of an ISIN %
            integer procedure isinDigit ( string(1) value iChar ) ;
                if      iChar >= "0" and iChar <= "9" then ( decode( iChar ) - decode( "0" ) )
                else if iChar >= "A" and iChar <= "Z" then ( decode( iChar ) - decode( "A" ) ) + 10
                else begin % invalid digit %
                    isValid := false;
                    -1
                end isinDigit ;

            integer d1, d2;
            logical isValid;
            isValid := true;
            d1      := isinDigit( isin( 0 // 1 ) );
            d2      := isinDigit( isin( 1 // 1 ) );
            if d1 < 10 or d1 > 35 or d2 < 10 or d2 > 35 then false % invalid first two characters %
            else begin
                % ok so far - conveet from base 36 to base 10 %
                string(24) base10Isin;
                integer    b10Pos;
                base10Isin := "";
                b10Pos     := 0;
                for cPos := 0 until 10 do begin
                    integer digit;
                    digit := isinDigit( isin( cPos // 1 ) );
                    if isValid then begin
                        % valid digit %
                        if digit > 9 then begin
                            base10Isin( b10Pos // 1 ) := code( ( digit div 10 ) + decode( "0" ) );
                            b10Pos                    := b10Pos + 1;
                        end if_digit_gt_9 ;
                        base10Isin( b10Pos // 1 )     := code( ( digit rem 10 ) + decode( "0" ) );
                        b10Pos                        := b10Pos + 1
                    end if_isValid
                end for_cPos ;
                % add the check digit as is %
                base10Isin( b10Pos // 1 ) := isin( 11 // 1 );
                isValid and LuhnTest( base10Isin, b10Pos + 1 )
            end
        end isIsin ;

    % task test cases %

    procedure testIsIsin ( string(32) value isin
                         ; logical    value expected
                         ) ;
    begin
        logical isValid;
        isValid := isIsin( isin );
        write( s_w := 0
             , isin
             , if isValid then " is valid" else " is invalid"
             , if isValid = expected   then "" else " NOT as expected ??"
             )
    end testIsin ;

    testIsIsin( "US0378331005",  true  );
    testIsIsin( "US0373831005",	 false );
    testIsIsin( "U50378331005",  false );
    testIsIsin( "US03378331005", false );
    testIsIsin( "AU0000XVGZA3",	 true  );
    testIsIsin( "AU0000VXGZA3",	 true  );
    testIsIsin( "FR0000988040",	 true  );
end.
Output:
US0378331005                     is valid
US0373831005                     is invalid
U50378331005                     is invalid
US03378331005                    is invalid
AU0000XVGZA3                     is valid
AU0000VXGZA3                     is valid
FR0000988040                     is valid

AppleScript[edit]

This script calls a handler posted for the Luhn test of credit card numbers task.

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"

on ISINTest(ISIN)
    -- Check that the input is both text and 12 characters long …
    if not ((ISIN's class is text) and ((count ISIN) is 12)) then return false
    -- … and that it has the required format.
    set ISIN to current application's class "NSMutableString"'s stringWithString:(ISIN)
    if ((ISIN's rangeOfString:("^[A-Z]{2}[0-9A-Z]{9}[0-9]$") options:(current application's NSRegularExpressionSearch) range:({0, ISIN's |length|()}))'s |length|() is 0) then return false
    -- Replace all letters with text representations of equivalent decimal numbers in the range 10 to 35.
    set letterCharacters to characters of "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    repeat with i from 1 to 26
        tell ISIN to replaceOccurrencesOfString:(item i of letterCharacters) withString:((i + 9) as text) options:(0) range:({0, its |length|()})
    end repeat
    
    -- Apply the Luhn test handler from the "Luhn test of credit card numbers" task.
    -- <https://www.rosettacode.org/wiki/Luhn_test_of_credit_card_numbers#Straightforward>
    return luhnTest(ISIN as text)
end ISINTest

-- Test code:
set testResults to {}
repeat with ISIN in {"US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"}
    set end of testResults to {testNumber:ISIN's contents, valid:ISINTest(ISIN)}
end repeat
return testResults
Output:
{{testNumber:"US0378331005", valid:true}, {testNumber:"US0373831005", valid:false}, {testNumber:"U50378331005", valid:false}, {testNumber:"US03378331005", valid:false}, {testNumber:"AU0000XVGZA3", valid:true}, {testNumber:"AU0000VXGZA3", valid:true}, {testNumber:"FR0000988040", valid:true}}

AWK[edit]

# syntax: GAWK -f VALIDATE_INTERNATIONAL_SECURITIES_IDENTIFICATION_NUMBER.AWK
# converted from Fortran
BEGIN {
    for (i=0; i<=255; i++) { ord_arr[sprintf("%c",i)] = i } # build array[character]=ordinal_value
    n = split("US0378331005,US0373831005,U50378331005,US03378331005,AU0000XVGZA3,AU0000VXGZA3,FR0000988040",arr,",")
    for (i=1; i<=n; i++) {
      printf("%s %s\n",is_isin(arr[i]),arr[i])
    }
    exit(0)
}
function is_isin(arg,  i,j,k,s,v) {
    for (i=1; i<=12; i++) { # convert to an array of digits
      k = ord_arr[substr(arg,i,1)]
      if (k >= 48 && k <= 57) {
        if (i < 3) { return(0) }
        k -= 48
        s[++j] = k
      } else if (k >= 65 && k <= 90) {
        if (i == 12) { return(0) }
        k = k - 65 + 10
        s[++j] = int(k / 10)
        s[++j] = k % 10
      } else {
        return(0)
      }
    }
    for (i=j-1; i>=1; i-=2) { # compute checksum
      k = 2 * s[i]
      if (k > 9) { k -= 9 }
      v += k
    }
    for (i=j; i>=1; i-=2) {
      v += s[i]
    }
    return(v % 10 == 0)
}
Output:
1 US0378331005
0 US0373831005
0 U50378331005
0 US03378331005
1 AU0000XVGZA3
1 AU0000VXGZA3
1 FR0000988040

C[edit]

#include <stdio.h>

int check_isin(char *a) {
    int i, j, k, v, s[24];
    
    j = 0;
    for(i = 0; i < 12; i++) {
        k = a[i];
        if(k >= '0' && k <= '9') {
            if(i < 2) return 0;
            s[j++] = k - '0';
        } else if(k >= 'A' && k <= 'Z') {
            if(i == 11) return 0;
            k -= 'A' - 10;
            s[j++] = k / 10;
            s[j++] = k % 10;
        } else {
            return 0;
        }
    }
    
    if(a[i]) return 0;
    
    v = 0;
    for(i = j - 2; i >= 0; i -= 2) {
        k = 2 * s[i];
        v += k > 9 ? k - 9 : k;
    }
    
    for(i = j - 1; i >= 0; i -= 2) {
        v += s[i];
    }
    
    return v % 10 == 0;
}

int main() {
    char *test[7] = {"US0378331005", "US0373831005", "U50378331005",
                     "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3",
                     "FR0000988040"};
    int i;
    for(i = 0; i < 7; i++) printf("%c%c", check_isin(test[i]) ? 'T' : 'F', i == 6 ? '\n' : ' ');
    return 0;
}

/* will print: T F F F T T T */

C#[edit]

{

using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace ValidateIsin
{
    public static class IsinValidator
    {
        public static bool IsValidIsin(string isin) => 
            IsinRegex.IsMatch(isin) && LuhnTest(Digitize(isin));

        private static readonly Regex IsinRegex = 
            new Regex("^[A-Z]{2}[A-Z0-9]{9}\\d$", RegexOptions.Compiled);

        private static string Digitize(string isin) =>
            string.Join("", isin.Select(c => $"{DigitValue(c)}"));

        private static bool LuhnTest(string number) => 
            number.Reverse().Select(DigitValue).Select(Summand).Sum() % 10 == 0;

        private static int Summand(int digit, int i) =>
            digit + (i % 2) * (digit - digit / 5 * 9);

        private static int DigitValue(char c) =>
            c >= '0' && c <= '9' 
                ? c - '0' 
                : c - 'A' + 10;
   }
	
   public class Program
   {
        public static void Main() 
        {
            string[] isins = 
            {
                "US0378331005",
                "US0373831005",
                "U50378331005",
                "US03378331005",
                "AU0000XVGZA3",
                "AU0000VXGZA3",
                "FR0000988040"
            };

            foreach (string isin in isins) {
                string validOrNot = IsinValidator.IsValidIsin(isin) ? "valid" : "not valid";
                Console.WriteLine($"{isin} is {validOrNot}");
            }
        }
    }
}
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

C++[edit]

#include <string>
#include <regex>
#include <algorithm>
#include <numeric>
#include <sstream>

bool CheckFormat(const std::string& isin)
{
	std::regex isinRegEpx(R"([A-Z]{2}[A-Z0-9]{9}[0-9])");
	std::smatch match;
	return std::regex_match(isin, match, isinRegEpx);
}

std::string CodeISIN(const std::string& isin)
{
	std::string coded;
	int offset = 'A' - 10;
	for (auto ch : isin)
	{
		if (ch >= 'A' && ch <= 'Z')
		{
			std::stringstream ss;
			ss << static_cast<int>(ch) - offset;
			coded += ss.str();
		}
		else
		{
			coded.push_back(ch);
		}
	}

	return std::move(coded);
}

bool CkeckISIN(const std::string& isin)
{
	if (!CheckFormat(isin))
		return false;

	std::string coded = CodeISIN(isin);
// from http://rosettacode.org/wiki/Luhn_test_of_credit_card_numbers#C.2B.2B11
	return luhn(coded);
}


#include <iomanip>
#include <iostream>

int main()
{
	std::string isins[] = { "US0378331005", "US0373831005", "U50378331005",
							"US03378331005", "AU0000XVGZA3", "AU0000VXGZA3",
							"FR0000988040" };
	for (const auto& isin : isins)
	{
		std::cout << isin << std::boolalpha << " - " << CkeckISIN(isin) <<std::endl;
	}
	return 0;
}

Caché ObjectScript[edit]

Class Utils.Check [ Abstract ]
{

ClassMethod ISIN(x As %String) As %Boolean
{
	// https://en.wikipedia.org/wiki/International_Securities_Identification_Number
	IF x'?2U9UN1N QUIT 0
	SET cd=$EXTRACT(x,*), x=$EXTRACT(x,1,*-1)
	FOR i=1:1 {
		SET n=$EXTRACT(x,i) IF n="" QUIT
		IF n'=+n SET $EXTRACT(x,i)=$CASE(n,"*":36,"@":37,"#":38,:$ASCII(n)-55)
	}
	// call into luhn check, appending check digit
	QUIT ..Luhn(x_cd)
}

ClassMethod Luhn(x As %String) As %Boolean
{
	// https://www.simple-talk.com/sql/t-sql-programming/calculating-and-verifying-check-digits-in-t-sql/
	SET x=$TRANSLATE(x," "), cd=$EXTRACT(x,*)
	SET x=$REVERSE($EXTRACT(x,1,*-1)), t=0
	FOR i=1:1:$LENGTH(x) {
		SET n=$EXTRACT(x,i)
		IF i#2 SET n=n*2 IF $LENGTH(n)>1 SET n=$EXTRACT(n,1)+$EXTRACT(n,2)
		SET t=t+n
	}
	QUIT cd=((t*9)#10)
}

}
Examples:
USER>For  { Read isin Quit:isin=""  Write ": "_##class(Utils.Check).ISIN(isin), ! }
US0378331005: 1
US0373831005: 0
U50378331005: 0
US03378331005: 0
AU0000XVGZA3: 1
AU0000VXGZA3: 1
FR0000988040: 1

USER>

Clojure[edit]

(defn luhn? [cc]
  (let [sum (->> cc
                 (map #(Character/digit ^char % 10))
                 reverse
                 (map * (cycle [1 2]))
                 (map #(+ (quot % 10) (mod % 10)))
                 (reduce +))]
    (zero? (mod sum 10))))

(defn is-valid-isin? [isin]
  (and (re-matches #"^[A-Z]{2}[A-Z0-9]{9}[0-9]$" isin)
       (->> isin
            (map #(Character/digit ^char % 36))
            (apply str)
            luhn?)))

(use 'clojure.pprint)
(doseq [isin ["US0378331005" "US0373831005" "U50378331005" "US03378331005"
              "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040"]]
  (cl-format *out* "~A: ~:[invalid~;valid~]~%" isin (is-valid-isin? isin)))

luhn? is based on Luhn test of credit card numbers#Clojure.

Output:
US0378331005: valid
US0373831005: invalid
U50378331005: invalid
US03378331005: invalid
AU0000XVGZA3: valid
AU0000VXGZA3: valid
FR0000988040: valid

COBOL[edit]

Works with: GnuCOBOL
        >>SOURCE FORMAT FREE
*>   this is gnucobol 2.0
identification division.
program-id. callISINtest.
data division.
working-storage section.
01  ISINtest-result binary-int.
procedure division.
start-callISINtest.
    display 'should be valid ' with no advancing
    call 'ISINtest' using 'US0378331005' ISINtest-result
    perform display-ISINtest-result
    display 'should not be valid ' with no advancing
    call 'ISINtest' using 'US0373831005' ISINtest-result
    perform display-ISINtest-result
    display 'should not be valid ' with no advancing
    call 'ISINtest' using 'U50378331005' ISINtest-result
    perform display-ISINtest-result
    display 'should not be valid ' with no advancing
    call 'ISINtest' using 'US03378331005' ISINtest-result
    perform display-ISINtest-result
    display 'should be valid ' with no advancing
    call 'ISINtest' using 'AU0000XVGZA3' ISINtest-result
    perform display-ISINtest-result
    display 'should be valid ' with no advancing
    call 'ISINtest' using 'AU0000VXGZA3' ISINtest-result
    perform display-ISINtest-result
    display 'should be valid ' with no advancing
    call 'ISINtest' using 'FR0000988040' ISINtest-result
    perform display-ISINtest-result
    stop run
    .
display-ISINtest-result.
    evaluate ISINtest-result
    when 0 
        display ' is valid'
    when -1
        display ' invalid length '
    when -2
        display ' invalid countrycode '
    when -3
        display ' invalid base36 digit '
    when -4
        display ' luhn test failed'
    when other
        display ' invalid return code ' ISINtest-result
    end-evaluate
    .
end program callISINtest.

identification division.
program-id. ISINtest.
data division.
working-storage section.
01  country-code-values value
    'ADAEAFAGAIALAMAOAQARASATAUAWAXAZBABBBDBEBFBGBHBIBJBLBMBNBOBQBRBS'
&   'BTBVBWBYBZCACCCDCFCGCHCICKCLCMCNCOCRCUCVCWCXCYCZDEDJDKDMDODZECEE'
&   'EGEHERESETFIFJFKFMFOFRGAGBGDGEGFGGGHGIGLGMGNGPGQGRGSGTGUGWGYHKHM'
&   'HNHRHTHUIDIEILIMINIOIQIRISITJEJMJOJPKEKGKHKIKMKNKPKRKWKYKZLALBLC'
&   'LILKLRLSLTLULVLYMAMCMDMEMFMGMHMKMLMMMNMOMPMQMRMSMTMUMVMWMXMYMZNA'
&   'NCNENFNGNINLNONPNRNUNZOMPAPEPFPGPHPKPLPMPNPRPSPTPWPYQARERORSRURW'
&   'SASBSCSDSESGSHSISJSKSLSMSNSOSRSSSTSVSXSYSZTCTDTFTGTHTJTKTLTMTNTO'
&   'TRTTTVTWTZUAUGUMUSUYUZVAVCVEVGVIVNVUWFWSYEYTZAZMZW'.
    03  country-codes occurs 249
        ascending key country-code
        indexed by cc-idx.
        05  country-code pic xx.

01  b pic 99.
01  base36-digits pic x(36) value
    '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.

01  i pic 99.
01  p pic 99.
01  luhn-number pic x(20).
01  luhntest-result binary-int.

linkage section.
01  test-number any length.
01  ISINtest-result binary-int.

procedure division using test-number ISINtest-result.
start-ISINtest.
    display space test-number with no advancing

    *> format test
    if function length(test-number) <> 12
        move -1 to ISINtest-result
        goback
    end-if

    *> countrycode test
    search all country-codes
    at end
        move -2 to ISINtest-result
        goback
    when test-number(1:2) = country-code(cc-idx)
        continue 
    end-search

    *> convert each character from base 36 to base 10
    *> and add to the luhn-number
    move 0 to p
    perform varying i from 1 by 1 until i > 12
        if test-number(i:1) >= '0' and <= '9'
            move test-number(i:1) to luhn-number(p + 1:1)
            add 1 to p
        else
            perform varying b from 9 by 1 until b > 35
            or base36-digits(b + 1:1) = test-number(i:1)
                continue
            end-perform
            if b > 35
                 move -3 to ISINtest-result
                 goback
            end-if
            move b to luhn-number(p + 1:2)
            add 2 to p
        end-if
    end-perform

    call 'luhntest' using luhn-number(1:p) luhntest-result
    if luhntest-result <> 0
        move -4 to ISINtest-result
        goback
    end-if

    move 0 to ISINtest-result
    goback
    .
end program ISINtest.

identification division.
program-id. luhntest.
data division.
working-storage section.
01  i pic S99.
01  check-sum pic 999.
linkage section.
01  test-number any length.
01  luhntest-result binary-int.
procedure division using test-number luhntest-result.
start-luhntest.
    display space test-number with no advancing
    move 0 to check-sum

    *> right to left sum the odd numbered digits
    compute i = function length(test-number)
    perform varying i from i by -2 until i < 1
        add function numval(test-number(i:1)) to check-sum
    end-perform
    display space check-sum with no advancing

    *> right to left double sum the even numbered digits
    compute i = function length(test-number) - 1
    perform varying i from i by -2 until i < 1
        add function numval(test-number(i:1)) to check-sum
        add function numval(test-number(i:1)) to check-sum
        *> convert a two-digit double sum number to a single digit
        if test-number(i:1) >= '5'
            subtract 9 from check-sum
        end-if
    end-perform
    display space check-sum with no advancing

    if function mod(check-sum,10) = 0
        move 0 to luhntest-result *> success
    else
        move -1 to luhntest-result *> failure
    end-if
    goback
    .
end program luhntest.
Output:
prompt$ cobc -xj ISINTest.cbl
should be valid  US0378331005 30280378331005 027 050 is valid
should not be valid  US0373831005 30280373831005 022 046 luhn test failed
should not be valid  U50378331005 invalid countrycode
should not be valid  US03378331005 invalid length
should be valid  AU0000XVGZA3 1030000033311635103 018 030 is valid
should be valid  AU0000VXGZA3 1030000031331635103 018 030 is valid
should be valid  FR0000988040 15270000988040 020 050 is valid

Common Lisp[edit]

(defun alphap (char)
  (char<= #\A char #\Z))

(defun alpha-digit-char-p (char)
  (or (alphap char) (digit-char-p char)))

(defun valid-isin-format-p (isin)
  (and (= (length isin) 12)
       (alphap (char isin 0))
       (alphap (char isin 1))
       (loop for i from 2 to 10
             always (alpha-digit-char-p (char isin i)))
       (digit-char-p (char isin 11))))

(defun isin->digits (isin)
  (apply #'concatenate 'string
         (loop for c across isin
               collect (princ-to-string (digit-char-p c 36)))))

(defun luhn-test (string)
  (loop for c across (reverse string)
        for oddp = t then (not oddp)
        if oddp
          sum (digit-char-p c) into result
        else
          sum (let ((n (* 2 (digit-char-p c))))
                (if (> n 9) (- n 9) n))
            into result
        finally (return (zerop (mod result 10)))))

(defun valid-isin-p (isin)
  (and (valid-isin-format-p isin)
       (luhn-test (isin->digits isin))))

(defun test ()
  (dolist (isin '("US0378331005" "US0373831005" "U50378331005" "US03378331005"
                  "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040"))
    (format t "~A: ~:[invalid~;valid~]~%" isin (valid-isin-p isin))))
Output:
US0378331005: valid
US0373831005: invalid
U50378331005: invalid
US03378331005: invalid
AU0000XVGZA3: valid
AU0000VXGZA3: valid
FR0000988040: valid

D[edit]

Translation of: Java

Code for the luhn test was taken from [[1]]

import std.stdio;

void main() {
    auto isins = [
        "US0378331005",
        "US0373831005",
        "U50378331005",
        "US03378331005",
        "AU0000XVGZA3",
        "AU0000VXGZA3",
        "FR0000988040",
    ];
    foreach (isin; isins) {
        writeln(isin, " is ", ISINvalidate(isin) ? "valid" : "not valid");
    }
}

bool ISINvalidate(string isin) {
    import std.array : appender;
    import std.conv : to;
    import std.regex : matchFirst;
    import std.string : strip, toUpper;

    isin = isin.strip.toUpper;

    if (isin.matchFirst(`^[A-Z]{2}[A-Z0-9]{9}\d$`).empty) {
        return false;
    }

    auto sb = appender!string;
    foreach (c; isin[0..12]) {
        sb.put(
            [c].to!int(36)
               .to!string
        );
    }

    import luhn;
    return luhnTest(sb.data);
}
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

Elixir[edit]

used Luhn module from here

isin? = fn str ->
          if str =~ ~r/\A[A-Z]{2}[A-Z0-9]{9}\d\z/ do
            String.codepoints(str)
            |> Enum.map_join(&String.to_integer(&1, 36))
            |> Luhn.valid?
          else
            false
          end
        end

IO.puts "    ISIN        Valid?"
~w(US0378331005
   US0373831005
   U50378331005
   US03378331005
   AU0000XVGZA3
   AU0000VXGZA3
   FR0000988040)
|> Enum.each(&IO.puts "#{&1}\t#{isin?.(&1)}")
Output:
    ISIN        Valid?
US0378331005    true
US0373831005    false
U50378331005    false
US03378331005   false
AU0000XVGZA3    true
AU0000VXGZA3    true
FR0000988040    true

Factor[edit]

We re-use the luhn? word from Luhn test of credit card numbers#Factor.

USING: combinators.short-circuit.smart formatting kernel luhn
math math.parser qw sequences strings unicode ;
IN: rosetta-code.isin

CONSTANT: test-cases qw{
    US0378331005 US0373831005 U50378331005 US03378331005
    AU0000XVGZA3 AU0000VXGZA3 FR0000988040
}

: valid-length? ( str -- ? ) length 12 = ;

: valid-country-code? ( str -- ? ) first2 [ Letter? ] both? ;

: valid-security-code? ( str -- ? )
    [ 2 11 ] dip subseq [ alpha? ] all? ;
    
: valid-checksum-digit? ( str -- ? ) last digit? ;
    
: valid-format? ( str -- ? ) {
        [ valid-length?         ]
        [ valid-country-code?   ]
        [ valid-security-code?  ]
        [ valid-checksum-digit? ]
    } && ;
    
: base36>base10 ( str -- n )
    >upper [ dup LETTER? [ 55 - number>string ] [ 1string ] if ]
    { } map-as concat string>number ;
    
: isin? ( str -- ? )
    { [ valid-format? ] [ base36>base10 luhn? ] } && ;
    
: main ( -- )
    test-cases [
        dup isin? "" " not" ? "%s is%s valid\n" printf
    ] each ;
    
MAIN: main
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

Fortran[edit]

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
                if (i == 12) return
                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

FreeBASIC[edit]

' version 27-10-2016
' compile with: fbc -s console

#Ifndef TRUE        ' define true and false for older freebasic versions
    #Define FALSE 0
    #Define TRUE Not FALSE
#EndIf

Function luhntest(cardnr As String) As Long

    cardnr = Trim(cardnr) ' remove spaces  

    Dim As String reverse_nr = cardnr
    Dim As Long i, j, s1, s2, l = Len(cardnr) -1

    ' reverse string
    For i = 0 To l
        reverse_nr[i] = cardnr[l - i]
    Next
    ' sum odd numbers
    For i = 0 To l Step 2
        s1 = s1 + (reverse_nr[i] - Asc("0"))
    Next
    ' sum even numbers
    For i = 1 To l Step 2
        j = reverse_nr[i] - Asc("0")
        j = j * 2
        If j > 9 Then j = j Mod 10 +1
        s2 = s2 + j
    Next

    If (s1 + s2) Mod 10 = 0 Then
        Return TRUE
    Else
        Return FALSE
    End If

End Function

' ------=< MAIN >=-----

Dim As String test_str
Dim As String test_set(1 To ...) = { "US0378331005", "US0373831005", _
                    "U50378331005", "US03378331005", "AU0000XVGZA3", _
                                     "AU0000VXGZA3", "FR0000988040" }

Dim As Long i, l, n, x

For i = 1 To UBound(test_set)
    test_str = ""
    l = Len(test_set(i))
    If l <> 12 Then
        Print test_set(i), "Invalid, length <> 12 char."
        Continue For
    End If
    If test_set(i)[0] < Asc("A") Or test_set(i)[1] < Asc("A") Then
        Print test_set(i), "Invalid, number needs to start with 2 characters"
        Continue For
    End If
    For n = 0 To l -1
        x = test_set(i)[n] - Asc("0")
        ' if test_set(i)[i] is a letter we to correct for that
        If x > 9 Then x = x -7
        If x < 10 Then
            test_str = test_str + Str(x)
        Else ' two digest number
            test_str = test_str + Str(x \ 10) + Str(x Mod 10)
        End If
    Next
    Print test_set(i), IIf(luhntest(test_str) = TRUE, "Valid","Invalid, checksum error")
Next


' empty keyboard buffer
While InKey <> "" : Wend
Print : Print "hit any key to end program"
Sleep
End
Output:
US0378331005  Valid
US0373831005  Invalid, checksum error
U50378331005  Invalid, number needs to start with 2 characters
US03378331005 Invalid, length <> 12 char.
AU0000XVGZA3  Valid
AU0000VXGZA3  Valid
FR0000988040  Valid

Go[edit]

package main

import "regexp"

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 ValidISIN(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]
		}
	}
	sum += int(n[11] - '0')
	return sum%10 == 0
}
package main

import "testing"

func TestValidISIN(t *testing.T) {
	testcases := []struct {
		isin  string
		valid bool
	}{
		{"US0378331005", true},
		{"US0373831005", false},
		{"U50378331005", false},
		{"US03378331005", false},
		{"AU0000XVGZA3", true},
		{"AU0000VXGZA3", true},
		{"FR0000988040", true},
	}

	for _, testcase := range testcases {
		actual := ValidISIN(testcase.isin)
		if actual != testcase.valid {
			t.Errorf("expected %v for %q, got %v",
				testcase.valid, testcase.isin, actual)
		}
	}
}

Groovy[edit]

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

Haskell[edit]

module ISINVerification2 where

import Data.Char (isUpper, isDigit, digitToInt)

verifyISIN :: String -> Bool
verifyISIN isin =
  correctFormat isin && mod (oddsum + multiplied_even_sum) 10 == 0
  where
    reverted = reverse $ convertToNumber isin
    theOdds = fst $ collectOddandEven reverted
    theEvens = snd $ collectOddandEven reverted
    oddsum = sum $ map digitToInt theOdds
    multiplied_even_sum = addUpDigits $ map ((* 2) . digitToInt) theEvens

capitalLetters :: String
capitalLetters = ['A','B' .. 'Z']

numbers :: String
numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

correctFormat :: String -> Bool
correctFormat isin =
  (length isin == 12) &&
  all (`elem` capitalLetters) (take 2 isin) &&
  all (\c -> elem c capitalLetters || elem c numbers) (drop 2 $ take 11 isin) &&
  elem (last isin) numbers

convertToNumber :: String -> String
convertToNumber = concatMap convert
  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] ])

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
    then putStrLn " valid"
    else putStrLn " not valid"

main :: IO ()
main = do
  let isinnumbers =
        [ "US0378331005"
        , "US0373831005"
        , "U50378331005"
        , "US03378331005"
        , "AU0000XVGZA3"
        , "AU0000VXGZA3"
        , "FR0000988040"
        ]
  mapM_ printSolution isinnumbers
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

Or, making alternative choices from the standard libraries:

import Control.Monad ((<=<))
import Data.Bifunctor (first)
import Data.List (foldl') -- '
import qualified Data.Map as M
import Data.Maybe (fromMaybe)

-------------------- VALID ISIN STRING -------------------

validISIN :: String -> Bool
validISIN =
  (&&) . isinPattern
    <*> luhn . (show <=< stringInts)

isinPattern :: String -> Bool
isinPattern s =
  12 == length s
    && all (`elem` capitals) l
    && all (`elem` (capitals <> digits)) m
    && head r `elem` digits
  where
    [l, m, r] = bites s [2, 9, 1]

luhn :: String -> Bool
luhn x = 0 == rem (s1 + s2) 10
  where
    odds = [(: []), const []]
    evens = reverse odds
    stream f =
      concat $
        zipWith ($) (cycle f) (stringInts $ reverse x)
    s1 = sum (stream odds)
    s2 =
      sum $
        sum . stringInts . show . (2 *) <$> stream evens

charMap :: M.Map Char Int
charMap = M.fromList $ zip (digits <> capitals) [0 ..]

stringInts :: String -> [Int]
stringInts = fromMaybe [] . traverse (`M.lookup` charMap)

bites :: [a] -> [Int] -> [[a]]
bites xs =
  (reverse . fst)
    . foldl' -- '
      (\(a, r) x -> first (: a) (splitAt x r))
      ([], xs)

capitals, digits :: String
capitals = ['A' .. 'Z']
digits = ['0' .. '9']

--------------------------- TEST -------------------------
main :: IO ()
main =
  mapM_
    (print . ((,) <*> validISIN))
    [ "US0378331005",
      "US0373831005",
      "U50378331005",
      "US03378331005",
      "AU0000XVGZA3",
      "AU0000VXGZA3",
      "FR0000988040"
    ]
Output:
("US0378331005",True)
("US0373831005",False)
("U50378331005",False)
("US03378331005",False)
("AU0000XVGZA3",True)
("AU0000VXGZA3",True)
("FR0000988040",True)

J[edit]

Solution:

require'regex'
validFmt=: 0 -: '^[A-Z]{2}[A-Z0-9]{9}[0-9]{1}$'&rxindex

df36=: ;@([: <@":"0 '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'&i.)  NB. decimal from base 36
luhn=: 0 = 10 (| +/@,) 10 #.inv 1 2 *&|: _2 "."0\ |.            NB. as per task Luhn_test_of_credit_card_numbers#J

validISIN=: validFmt *. luhn@df36

Required Examples:

   Tests=: 'US0378331005';'US0373831005';'U50378331005';'US03378331005';'AU0000XVGZA3';'AU0000VXGZA3';'FR0000988040'
   validISIN&> Tests
1 0 0 0 1 1 1

Java[edit]

As the Luhn test method from the Luhn test of credit card numbers task is only a few lines, it has been embedded in the ISIN class for convenience.

public class ISIN {
 
    public static void main(String[] args) {
        String[] isins = {
            "US0378331005", 
            "US0373831005", 
            "U50378331005", 
            "US03378331005",
            "AU0000XVGZA3", 
            "AU0000VXGZA3", 
            "FR0000988040"
        };
        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;
 
        StringBuilder sb = new StringBuilder();
        for (char c : isin.substring(0, 12).toCharArray())
            sb.append(Character.digit(c, 36));
 
        return luhnTest(sb.toString());
    }

    static boolean luhnTest(String number) {
        int s1 = 0, s2 = 0;
        String reverse = new StringBuffer(number).reverse().toString();
        for (int i = 0; i < reverse.length(); i++){
            int digit = Character.digit(reverse.charAt(i), 10);
            //This is for odd digits, they are 1-indexed in the algorithm.
            if (i % 2 == 0){
                s1 += digit;
            } else { // Add 2 * digit for 0-4, add 2 * digit - 9 for 5-9.
                s2 += 2 * digit;
                if(digit >= 5){
                    s2 -= 9;
                }
            }
        }
        return (s1 + s2) % 10 == 0;
    }
}
US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid

jq[edit]

Works with: jq

Works with gojq, the Go implementation of jq

# This filter may be applied to integers or integer-valued strings
def luhntest:
  def digits: tostring | explode | map([.]|implode|tonumber);
  (digits | reverse) 
  | ( [.[range(0;length;2)]] | add ) as $sum1
  | [.[range(1;length;2)]]
  | (map( (2 * .) | if . > 9 then (digits|add) else . end) | add) as $sum2
  | ($sum1 + $sum2) % 10 == 0;

def decodeBase36:
  # decode a single character
  def d1:
    explode[0]
    # "0" is 48; "A" is 65
    | if . < 65 then . - 48
      else . - 55
      end;
  def chars: explode | map([.]|implode);
  chars | map(d1) | join("");

def is_ISIN:
  type == "string"
  and test("^(?<cc>[A-Z][A-Z])(?<sc>[0-9A-Z]{9})(?<cs>[0-9])$")
  and (decodeBase36 | luhntest);

The Task

def task:
  "US0378331005",
  "US0373831005",
  "U50378331005",
  "US03378331005",
  "AU0000XVGZA3",
  "AU0000VXGZA3",
  "FR0000988040"
  |  . + " => " + (if is_ISIN then "valid" else "invalid" end);

task
Output:
US0378331005 => valid
US0373831005 => invalid
U50378331005 => invalid
US03378331005 => invalid
AU0000XVGZA3 => valid
AU0000VXGZA3 => valid
FR0000988040 => valid


Julia[edit]

using Printf

luhntest(x) = luhntest(parse(Int, x))

function checkISIN(inum::AbstractString)
    if length(inum) != 12 || !all(isalpha, inum[1:2]) return false end
    return parse.(Int, collect(inum), 36) |> join |> luhntest
end

for inum in ["US0378331005", "US0373831005", "U50378331005",
    "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"]
    @printf("%-15s %5s\n", inum, ifelse(checkISIN(inum), "pass", "fail"))
end
Output:
US0378331005     pass
US0373831005     fail
U50378331005     fail
US03378331005    fail
AU0000XVGZA3     pass
AU0000VXGZA3     pass
FR0000988040     pass

Kotlin[edit]

As the Luhn test method is only a few lines, it's reproduced here for convenience:

// version 1.1

object Isin {
    val r = Regex("^[A-Z]{2}[A-Z0-9]{9}[0-9]$")

    fun isValid(s: String): Boolean {
        // check format
        if (!s.matches(r)) return false
        // validate checksum
        val sb = StringBuilder()
        for (c in s) {
            when (c) {
                in '0'..'9' -> sb.append(c)
                in 'A'..'Z' -> sb.append((c.toInt() - 55).toString().padStart(2, '0'))
            }
        }
        return luhn(sb.toString())
    }

    private fun luhn(s: String): Boolean {
        fun sumDigits(n: Int) = n / 10 + n % 10
        val t = s.reversed()
        val s1 = t.filterIndexed { i, _ -> i % 2 == 0 }.sumBy { it - '0' }
        val s2 = t.filterIndexed { i, _ -> i % 2 == 1 }.map { sumDigits((it - '0') * 2) }.sum()
        return (s1 + s2) % 10 == 0
    }
}

fun main(args: Array<String>) {
    val isins = arrayOf(
        "US0378331005", "US0373831005", "U50378331005", "US03378331005",
        "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"
    )
    for (isin in isins) {
        println("$isin\t -> ${if (Isin.isValid(isin)) "valid" else "not valid"}")
    }
}
Output:
US0378331005     -> valid
US0373831005     -> not valid
U50378331005     -> not valid
US03378331005    -> not valid
AU0000XVGZA3     -> valid
AU0000VXGZA3     -> valid
FR0000988040     -> valid

langur[edit]

The luhn test is repeated here for simplicity (from Luhn_test_of_credit_card_numbers#langur).

Works with: langur version 0.8.10
val .luhntest = f(.s) {
    val .t = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
    val .numbers = s2n .s
    val .oddeven = len(.numbers) rem 2

    for[=0] .i of .numbers {
        _for += if(.i rem 2 == .oddeven: .numbers[.i]; .t[.numbers[.i]+1])
    } div 10
}

val .isintest = f(.s) {
    matching(re/^[A-Z][A-Z][0-9A-Z]{9}[0-9]$/, .s) and
        .luhntest(join s2n .s)
}

val .tests = h{
    "US0378331005":  true,
    "US0373831005": false,
    "U50378331005": false,
    "AU0000XVGZA3": true,
    "AU0000VXGZA3": true,
    "FR0000988040": true,
    "US03378331005": false,
}

for .key in sort(keys .tests) {
    val .pass = .isintest(.key)
    write .key, ": ", .pass
    writeln if(.pass == .tests[.key]: ""; " (ISIN TEST FAILED)")
}
Output:
AU0000VXGZA3: true
AU0000XVGZA3: true
FR0000988040: true
U50378331005: false
US03378331005: false
US0373831005: false
US0378331005: true

Lua[edit]

function luhn (n)
    local revStr, s1, s2, digit, mod = n:reverse(), 0, 0
    for pos = 1, #revStr do
        digit = tonumber(revStr:sub(pos, pos))
        if pos % 2 == 1 then
            s1 = s1 + digit
        else
            digit = digit * 2
            if digit > 9 then
                mod = digit % 10
                digit = mod + ((digit - mod) / 10)
            end
            s2 = s2 + digit
        end
    end
    return (s1 + s2) % 10 == 0
end

function checkISIN (inStr)
    if #inStr ~= 12 then return false end
    local numStr = ""
    for pos = 1, #inStr do
        numStr = numStr .. tonumber(inStr:sub(pos, pos), 36)
    end
    return luhn(numStr)
end

local testCases = {
    "US0378331005",
    "US0373831005",
    "US0373831005",
    "US03378331005",
    "AU0000XVGZA3",
    "AU0000VXGZA3",
    "FR0000988040"
}
for _, ISIN in pairs(testCases) do print(ISIN, checkISIN(ISIN)) end
Output:
US0378331005    true
US0373831005    false
US0373831005    false
US03378331005   false
AU0000XVGZA3    true
AU0000VXGZA3    true
FR0000988040    true

Mathematica / Wolfram Language[edit]

ClearAll[LuhnQ, VakudISINQ]
LuhnQ[n_Integer] := Block[{digits = Reverse@IntegerDigits@n}, Mod[Total[{digits[[;; ;; 2]], IntegerDigits[2 #] & /@ digits[[2 ;; ;; 2]]}, -1], 10] == 0]
VakudISINQ[sin_String] := Module[{s = ToUpperCase[sin]},
  If[StringMatchQ[s, 
    LetterCharacter ~~ LetterCharacter ~~ 
     Repeated[DigitCharacter | LetterCharacter, {9}] ~~ 
     DigitCharacter],
   s = StringJoin[
     Characters[s] /. 
      Thread[CharacterRange["A", "Z"] -> ToString /@ Range[10, 35]]];
   LuhnQ[ToExpression[s]]
   ,
   False
   ]
  ]
VakudISINQ /@ {"US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"}
Output:
{True, False, False, False, True, True, True}

Nim[edit]

import strformat

const
  DigitRange = '0'..'9'
  UpperCaseRange = 'A'..'Z'

type ISINError = object of ValueError


proc luhn(s: string): bool =
  const m = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
  var sum = 0
  var odd = true
  for i in countdown(s.high, 0):
    let digit = ord(s[i]) - ord('0')
    sum += (if odd: digit else: m[digit])
    odd = not odd
  result = sum mod 10 == 0


proc validateISIN(s: string) =
  if s.len != 12:
    raise newException(ISINError, "wrong length")
  if s[0] notin UpperCaseRange or s[1] notin UpperCaseRange:
    raise newException(ISINError, "wrong country code")
  if s[11] notin DigitRange:
    raise newException(ISINError, "wrong checksum character")
  var t: string
  for ch in s:
    case ch
    of '0'..'9': t.add ch
    of 'A'..'Z': t.addInt ord(ch) - ord('A') + 10
    else: raise newException(ISINError, "invalid characters in code")
  if not t.luhn():
    raise newException(ISINError, "checksum error")


when isMainModule:

  for isin in ["US0378331005", "US0373831005", "U50378331005",
               "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"]:
    try:
      isin.validateISIN()
      echo &"{isin} is valid."
    except ISINError:
      echo &"{isin} is not valid: {getCurrentExceptionMsg()}."
Output:
US0378331005 is valid.
US0373831005 is not valid: checksum error.
U50378331005 is not valid: wrong country code.
US03378331005 is not valid: wrong length.
AU0000XVGZA3 is valid.
AU0000VXGZA3 is valid.
FR0000988040 is valid.

Perl[edit]

We reuse the luhn_test() function from Luhn test of credit card numbers#Perl.

use strict;
use English;
use POSIX;
use Test::Simple tests => 7;

ok(   validate_isin('US0378331005'),  'Test 1');	
ok( ! validate_isin('US0373831005'),  'Test 2');
ok( ! validate_isin('U50378331005'),  'Test 3');
ok( ! validate_isin('US03378331005'), 'Test 4');
ok(   validate_isin('AU0000XVGZA3'),  'Test 5');	
ok(   validate_isin('AU0000VXGZA3'),  'Test 6');
ok(   validate_isin('FR0000988040'),  'Test 7');	
exit 0;

sub validate_isin {
    my $isin = shift;
    $isin =~ /\A[A-Z]{2}[A-Z\d]{9}\d\z/s or return 0;
    my $base10 = join(q{}, map {scalar(POSIX::strtol($ARG, 36))}
                               split(//s, $isin));
    return luhn_test($base10);
}
Output:
1..7
ok 1 - Test 1
ok 2 - Test 2
ok 3 - Test 3
ok 4 - Test 4
ok 5 - Test 5
ok 6 - Test 6
ok 7 - Test 7

Phix[edit]

Note this (slightly better) version of Luhn() has the reverse() inside it, whereas the original did not.

with javascript_semantics
function Luhn(string st)
    integer s = 0, d
    st = reverse(st)
    for i=1 to length(st) do
        d = st[i]-'0'
        s += iff(mod(i,2)?d,d*2-(d>4)*9)
    end for
    return remainder(s,10)=0
end function
 
function valid_ISIN(string st)
-- returns 1 if valid, else 0/2/3/4.
-- (feel free to return 0 instead of 2/3/4)
    if length(st)!=12 then return 2 end if
    for i=length(st) to 1 by -1 do
        integer ch = st[i]
        if ch>='A' then
            if ch>'Z' then return 3 end if
            st[i..i] = sprintf("%d",ch-55)
        elsif i<=2 then
            return 4
        elsif ch<'0' or ch>'9' then
            return 3
        end if
    end for
    return Luhn(st)
end function
 
constant tests = {"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
 
        reasons = {"wrong checksum","valid","wrong length","bad char","wrong country"}
 
for i=1 to length(tests) do
    printf(1,"%s : %s\n",{tests[i],reasons[valid_ISIN(tests[i])+1]})
end for
Output:
US0378331005 : valid
US0373831005 : wrong checksum
U50378331005 : wrong country
US03378331005 : wrong length
AU0000XVGZA3 : valid
AU0000VXGZA3 : valid
FR0000988040 : valid

PicoLisp[edit]

Using the luhn function defined at Luhn test of credit card numbers#PicoLisp:

(de isin (Str)
   (let Str (mapcar char (chop Str))
      (and
         (= 12 (length Str))
         (<= 65 (car Str) 90)
         (<= 65 (cadr Str) 90)
         (luhn
            (pack
               (mapcar
                  '((N)
                     (- N (if (<= 48 N 57) 48 55)) )
                  Str ) ) ) ) ) )
(println
   (mapcar
      isin
      (quote
         "US0378331005"
         "US0373831005"
         "U50378331005"
         "US03783310005"
         "AU0000XVGZA3"
         "AU0000VXGZA3"
         "FR0000988040" ) ) )
Output:
(0 NIL NIL NIL 0 0 0)

PowerShell[edit]

function Test-ISIN
{
    [CmdletBinding()]
    [OutputType([bool])]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [ValidatePattern("[A-Z]{2}\w{9}\d")]
        [ValidateScript({$_.Length -eq 12})]
        [string]
        $Number
    )

    function Split-Array
    {
        $array = @(), @()
        $input | ForEach-Object {$array[($index = -not $index)] += $_}
        $array[1], $array[0]
    }

    filter ConvertTo-Digit
    {
        if ($_ -gt 9)
        {
            $_.ToString().ToCharArray() | ForEach-Object -Begin {$n = 0} -Process {$n += [Char]::GetNumericValue($_)} -End {$n}
        }
        else
        {
            $_
        }
    }


    $checkDigit = $Number[-1]

    $digits = ($Number -replace ".$").ToCharArray() | ForEach-Object {
        if ([Char]::IsDigit($_))
        {
            [Char]::GetNumericValue($_)
        }
        else
        {
            [int][char]$_ - 55
        }
    }

    $odds, $evens = ($digits -join "").ToCharArray() | Split-Array

    if ($odds.Count -gt $evens.Count)
    {
        $odds  = $odds  | ForEach-Object {[Char]::GetNumericValue($_) * 2} | ConvertTo-Digit
        $evens = $evens | ForEach-Object {[Char]::GetNumericValue($_)}
    }
    else
    {
        $odds  = $odds  | ForEach-Object {[Char]::GetNumericValue($_)}
        $evens = $evens | ForEach-Object {[Char]::GetNumericValue($_) * 2} | ConvertTo-Digit
    }

    $sum = ($odds  | Measure-Object -Sum).Sum + ($evens | Measure-Object -Sum).Sum

    (10 - ($sum % 10)) % 10 -match $checkDigit
}
"US0378331005","US0373831005","US0337833103","AU0000XVGZA3","AU0000VXGZA3","FR0000988040" | ForEach-Object {
    [PSCustomObject]@{
        ISIN    = $_
        IsValid = Test-ISIN -Number $_
    }
}
Output:
ISIN         IsValid
----         -------
US0378331005    True
US0373831005   False
US0337833103   False
AU0000XVGZA3    True
AU0000VXGZA3    True
FR0000988040    True

PureBasic[edit]

EnableExplicit

Procedure.b Check_ISIN(*c.Character)
  Define count.i=0, Idx.i=1, v.i=0, i.i
  Dim s.i(24)
  
  If MemoryStringLength(*c) > 12 : ProcedureReturn #False : EndIf
  
  While *c\c
    count+1
    If *c\c>='0' And *c\c<='9'      
      If count<=2 : ProcedureReturn #False : EndIf
      s(Idx)= *c\c - '0'
      Idx+1
    ElseIf *c\c>='A' And *c\c<='Z'      
      s(Idx)= (*c\c - ('A'-10)) / 10
      Idx+1
      s(Idx)= (*c\c - ('A'-10)) % 10
      Idx+1
    Else
      ProcedureReturn #False      
    EndIf    
    *c + SizeOf(Character)
  Wend
  
  For i=Idx-2 To 0 Step -2
    If s(i)*2 > 9
      v+ s(i)*2 -9
    Else
      v+ s(i)*2
    EndIf
    v+s(i+1)
  Next

  ProcedureReturn Bool(v%10=0)      
EndProcedure

Define.s s
OpenConsole("Validate_International_Securities_Identification_Number (ISIN)")

If ReadFile(0,"c:\code_pb\rosettacode\data\isin.txt")
  While Not Eof(0)
    s=ReadString(0)
    Print(s+~"\t")
    If Check_ISIN(@s) : PrintN("TRUE") : Else : PrintN("FALSE") : EndIf    
  Wend
  CloseFile(0)
EndIf
Input()
Output:
US0378331005    TRUE
US0373831005    FALSE
U50378331005    FALSE
US03378331005   FALSE
AU0000XVGZA3    TRUE
AU0000VXGZA3    TRUE
FR0000988040    TRUE

Python[edit]

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[::-2])) % 10

# A more readable version 
def check_isin_alt(a):
    if len(a) != 12:
        return False
    s = []
    for i, c in enumerate(a):
        if c.isdigit():
            if i < 2:
                return False
            s.append(ord(c) - 48)
        elif c.isupper():
            if i == 11:
                return False
            s += divmod(ord(c) - 55, 10)
        else:
            return False
    v = sum(s[::-2])
    for k in s[-2::-2]:
        k = 2 * k
        v += k - 9 if k > 9 else k
    return v % 10 == 0

[check_isin(s) for s in ["US0378331005", "US0373831005", "U50378331005", "US03378331005",
                         "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"]]

# [True, False, False, False, True, True, True]

Quackery[edit]

luhn is defined at Luhn test of credit card numbers#Quackery.

  [ 2 split drop do 
    char A char z 1+ within
    swap 
    char A char z 1+ within
    and ]                   is 2chars ( $ --> b )
 
  [ dup size 12 != iff
      [ drop false ] done
    dup 2chars not iff
      [ drop false ] done
    [] swap 
    witheach
      [ 36 base put
        char->n 
        base release
        number$ join ]
    $->n drop luhn ]        is isin   ( n --> b )

  [ dup echo$ 
    say " is "
    isin not if 
      [ say "not " ]
    say "valid." cr ]       is task   ( n -->   )

  $ "US0378331005"  task
  $ "US0373831005"  task
  $ "U50378331005"  task
  $ "US03378331005" task
  $ "AU0000XVGZA3"  task
  $ "AU0000VXGZA3"  task
  $ "FR0000988040"  task
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.

Racket[edit]

#lang racket

;; convert a base36 character (#\0 - #\Z) to its equivalent
;; in base 10 as a string ("0" - "35")
(define (base36-char->base10-string c)
  (let ([char-int (char->integer (char-upcase c))]
        [zero-int (char->integer #\0)]
        [nine-int (char->integer #\9)]
        [A-int (char->integer #\A)]
        [Z-int (char->integer #\Z)])
    (cond [(and (>= char-int zero-int) (<= char-int nine-int)) (~a c)]
          [(and (>= char-int A-int) (<= char-int Z-int)) (~a (+ (- char-int A-int) 10))]
          [else null])))

;; substitute equivalent base 10 numbers for base 36 characters in string
;; this is a character-by-character substitution not a conversion
;; of a base36 number to a base10 number
(define (base36-string-characters->base10-string-characters s)
  (for/fold ([joined ""])
            ([tenstr (map base36-char->base10-string (string->list (string-upcase s)))])
    (values (string-append joined tenstr))))

;; This uses the Racket Luhn solution
(define [isin-test? s]
  (let ([RE (pregexp "^[A-Z]{2}[A-Z0-9]{9}[0-9]{1}$")])
    (and
     (regexp-match? RE s)
     (luhn-test (string->number (base36-string-characters->base10-string-characters s))))))

(define test-cases '("US0378331005" "US0373831005" "U50378331005" "US03378331005" "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040"))

(map isin-test? test-cases)
;; -> '(#t #f #f #f #t #t #t)
Output:

'(#t #f #f #f #t #t #t)

Raku[edit]

(formerly Perl 6)

Works with: Rakudo version 2018.12

Using the luhn-test function from the Luhn test of credit card numbers task.

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

sub luhn-test ($number --> Bool) {
    my @digits = $number.comb.reverse;
    my $sum = @digits[0,2...*].sum
            + @digits[1,3...*].map({ |($_ * 2).comb }).sum;
    return $sum %% 10;
}

# Testing:

say "$_ is { m/$ISIN/ ?? "valid" !! "not valid"}" for <
US0378331005
US0373831005
U50378331005
US03378331005
AU0000XVGZA3
AU0000VXGZA3
FR0000988040
>;
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

REXX[edit]

/*REXX program validates the  checksum digit for an  International Securities ID number.*/
parse arg z                                      /*obtain optional  ISINs  from the C.L.*/
if z=''  then z= "US0378331005 US0373831005 U50378331005 US03378331005 AU0000XVGZA3" ,
                 'AU0000VXGZA3 FR0000988040'     /* [↑]  use the default list of  ISINs.*/
                                                 /* [↓]  process  all  specified  ISINs.*/
     do n=1  for words(z);  x=word(z, n);  y= x  /*obtain an  ISIN  from the  Z  list.  */
     $=                                          /* [↓]  construct list of ISIN digits. */
        do k=1  for length(x);  _= substr(x,k,1) /*the ISIN may contain alphabetic chars*/
        p= pos(_, 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ)   /*X must contain A──►Z, 0──►9.*/
        if p==0  then y=                                  /*trigger  "not"  valid below.*/
                 else $= $ || p-1                /*convert  X  string (base 36 ──► dec).*/
        end   /*k*/                              /* [↑]  convert  alphabetic ──► digits.*/
     @=                                          /*placeholder for the "not" in message.*/
     if length(y)\==12             then @= "not" /*see if the ISIN is exactly 12 chars. */
     if \datatype( left(x,2),'U')  then @= "not" /* "   "  "    "  1st 2 chars cap. let.*/
     if \datatype(right(x,1),'W')  then @= "not" /* "   "  "    "  last char not a digit*/
     if @==''  then  if \luhn($)   then @= "not" /* "   "  "    "  passed the Luhn test.*/
     say right(x, 30)   right(@, 5)   "valid"    /*display the   yea  or  nay   message.*/
     end   /*n*/                                 /* [↑] 1st 3 IFs could've been combined*/
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
Luhn: procedure;  parse arg x;           $= 0    /*get credit card number;  zero $ sum. */
      y= reverse( left(0, length(x) // 2)x)      /*add leading zero if needed, & reverse*/
                            do j=1  to length(y)-1  by 2;    _= 2  *  substr(y, j+1, 1)
                            $= $ + substr(y, j, 1)  +  left(_, 1)  +  substr(_, 2  , 1, 0)
                            end   /*j*/          /* [↑]   sum the  odd and even  digits.*/
      return right($, 1)==0                      /*return "1" if number passed Luhn test*/
output   when using the default inputs:
                  US0378331005       valid
                  US0373831005   not valid
                  U50378331005   not valid
                 US03378331005   not valid
                  AU0000XVGZA3       valid
                  AU0000VXGZA3       valid
                  FR0000988040       valid 

Ring[edit]

# Project : Validate International Securities Identification Number

decimals(0)

test = ["US0378331005",
           "US0373831005",
           "U50378331005",
           "US03378331005",
           "AU0000XVGZA3",
           "AU0000VXGZA3",
           "FR0000988040"]

for n = 1 to len(test)
      testold = test[n] 
      ascii1 = ascii(left(test[n],1))
      ascii2 = ascii(substr(test[n],2,1))
      if len(test[n]) != 12 or (ascii1 < 65 or ascii1 > 90) or (ascii2 < 65 or ascii2 > 90)
         see test[n] + " -> Invalid" + nl
         loop
      ok
      for m = 1 to len(test[n])
           if ascii(test[n][m]) > 64 and ascii(test[n][m]) < 91
              asc = ascii(test[n][m]) - 55
              test[n] = left(test[n],m-1) + string(asc) + right(test[n],len(test[n])-m)
           ok
      next
      see testold + " -> " + cardtest(test[n]) + nl
next
 
func cardtest(numstr)
        revstring = revstr(numstr)
        s1 = revodd(revstring)
        s2 = reveven(revstring)
        s3 =right(string(s1+s2), 1)
        if s3 = "0"
           return "Valid"
        else
           return "Invalid"
        ok
 
func revstr(str)
      strnew = ""
      for nr = len(str) to 1 step -1
           strnew = strnew + str[nr]
      next
      return strnew
 
func revodd(str)
        strnew = ""
        for nr = 1 to len(str) step 2
             strnew = strnew + str[nr]
        next
        sumodd = 0
        for p = 1 to len(strnew)
              sumodd = sumodd + number(strnew[p])
        next     
        return sumodd
 
func reveven(str)
        strnew = ""
        for nr = 2 to len(str) step 2
             strnew = strnew + str[nr]
        next
        lsteven = []
        for p = 1 to len(strnew)
             add(lsteven, string(2*number(strnew[p])))
        next  
        arreven = list(len(lsteven))
        for q = 1 to len(lsteven)
              sum = 0
              for w = 1 to len(lsteven[q])
                    sum = sum + lsteven[q][w]
              next
              arreven[q] = sum
        next
        sumarr = 0
        for x = 1 to len(arreven)
             sumarr = sumarr + arreven[x]
        next
        return sumarr

Output:

US0378331005 -> Valid
US0373831005 -> Invalid
U50378331005 -> Invalid
US03378331005 -> Invalid
AU0000XVGZA3 -> Valid
AU0000VXGZA3 -> Valid
FR0000988040 -> Valid

Ruby[edit]

Using a pre-existing luhn method:

RE = /\A[A-Z]{2}[A-Z0-9]{9}[0-9]{1}\z/

def valid_isin?(str)
  return false unless str =~ RE
  luhn(str.chars.map{|c| c.to_i(36)}.join)
end

p %w(US0378331005
US0373831005
U50378331005
US03378331005
AU0000XVGZA3
AU0000VXGZA3
FR0000988040).map{|tc| valid_isin?(tc) }	 
 
# => [true, false, false, false, true, true, true]

Rust[edit]

extern crate luhn_cc;

use luhn_cc::compute_luhn;

fn main() {
    assert_eq!(validate_isin("US0378331005"), true);
    assert_eq!(validate_isin("US0373831005"), false);
    assert_eq!(validate_isin("U50378331005"), false);
    assert_eq!(validate_isin("US03378331005"), false);
    assert_eq!(validate_isin("AU0000XVGZA3"), true);
    assert_eq!(validate_isin("AU0000VXGZA3"), true);
    assert_eq!(validate_isin("FR0000988040"), true);
}

fn validate_isin(isin: &str) -> bool {
    // Preliminary checks to avoid working on non-ASCII stuff
    if !isin.chars().all(|x| x.is_alphanumeric()) || isin.len() != 12 {
        return false;
    }
    if !isin[..2].chars().all(|x| x.is_alphabetic())
        || !isin[2..12].chars().all(|x| x.is_alphanumeric())
        || !isin.chars().last().unwrap().is_numeric()
    {
        return false;
    }

    // Converts the alphanumeric string in a numeric-only string
    let bytes = isin.as_bytes();

    let s2 = bytes.iter()
        .flat_map(|&c| {
            if c.is_ascii_digit() {
                vec![c]
            }
            else {
                (c + 10 - ('A' as u8)).to_string().into_bytes()
            }
        }).collect::<Vec<u8>>();

    let string = std::str::from_utf8(&s2).unwrap();
    let number = string.parse::<usize>().unwrap();

    return compute_luhn(number);
}

SAS[edit]

data test;
length isin $20 ok $1;
input isin;
keep isin ok;
array s{24};
link isin;
return;
isin:
ok="N";
n=length(isin);
if n=12 then do;
    j=0;
    do i=1 to n;
        k=rank(substr(isin,i,1));
        if k>=48 & k<=57 then do;
            if i<3 then return;
            j+1;
            s{j}=k-48;
        end;
        else if k>=65 & k<=90 then do;
            if i=12 then return;
            k+-55;
            j+1;
            s{j}=int(k/10);
            j+1;
            s{j}=mod(k,10);
        end;
        else return;
    end;

    v=sum(of s{*});
    do i=j-1 to 1 by -2;
        v+s{i}-9*(s{i}>4);
    end;

if mod(v,10)=0 then ok="Y";
end;
return;
cards;
US0378331005
US0373831005
U50378331005
US03378331005
AU0000XVGZA3
AU0000VXGZA3
FR0000988040
;
run;

Scala[edit]

Output:
Best seen running in your browser either by ScalaFiddle (ES aka JavaScript, non JVM) or Scastie (remote JVM).
object Isin extends App {
  val isins = Seq("US0378331005", "US0373831005", "U50378331005",
    "US03378331005", "AU0000XVGZA3","AU0000VXGZA3", "FR0000988040")

  private def ISINtest(isin: String): Boolean = {
    val isin0 = isin.trim.toUpperCase

    def luhnTestS(number: String): Boolean = {

      def luhnTestN(digits: Seq[Int]): Boolean = {

        def checksum(digits: Seq[Int]): Int = {
          digits.reverse.zipWithIndex
            .foldLeft(0) {
              case (sum, (digit, i)) =>
                if (i % 2 == 0) sum + digit
                else sum + (digit * 2) / 10 + (digit * 2) % 10
            } % 10
        }

        checksum(digits) == 0
      }

      luhnTestN(number.map { c =>
        assert(c.isDigit, s"$number has a non-digit error")
        c.asDigit
      })
    }

    if (!isin0.matches("^[A-Z]{2}[A-Z0-9]{9}\\d$")) false
    else {
      val sb = new StringBuilder
      for (c <- isin0.substring(0, 12)) sb.append(Character.digit(c, 36))
      luhnTestS(sb.toString)
    }
  }

  isins.foreach(isin => println(f"$isin is ${if (ISINtest(isin)) "" else "not"}%s valid"))

}

SQL PL[edit]

Works with: Db2 LUW
version 9.7 or higher.

With SQL PL:

--#SET TERMINATOR @

SET SERVEROUTPUT ON @

CREATE OR REPLACE FUNCTION VALIDATE_ISIN (
  IN IDENTIFIER VARCHAR(12)
 ) RETURNS SMALLINT
 -- ) RETURNS BOOLEAN
 BEGIN
  DECLARE CHECKSUM_FUNC CHAR(1);
  DECLARE CONVERTED VARCHAR(24);
  DECLARE I SMALLINT;
  DECLARE LENGTH SMALLINT;
  DECLARE RET SMALLINT DEFAULT 1;
  --DECLARE RET BOOLEAN DEFAULT FALSE;
  DECLARE CHAR_AT CHAR(1);
  DECLARE INVALID_CHAR CONDITION FOR SQLSTATE 'ISIN1';

  SET CHAR_AT = SUBSTR(IDENTIFIER, 1, 1);
  IF (ASCII(CHAR_AT) < 65 OR 90 < ASCII(CHAR_AT)) THEN
   SIGNAL INVALID_CHAR SET MESSAGE_TEXT = 'Country code with invalid characters';
  END IF;
  SET CHAR_AT = SUBSTR(IDENTIFIER, 2, 1);
  IF (ASCII(CHAR_AT) < 65 OR 90 < ASCII(CHAR_AT)) THEN
   SIGNAL INVALID_CHAR SET MESSAGE_TEXT = 'Country code with invalid characters';
  END IF;

  -- Convert letters to numbers.
  SET I = 1;
  SET CONVERTED = '';
  SET LENGTH = LENGTH(IDENTIFIER);
  WHILE (I <= LENGTH) DO
   SET CHAR_AT = SUBSTR(IDENTIFIER, I, 1);
   IF (48 <= ASCII(CHAR_AT) AND ASCII(CHAR_AT) <= 57) THEN
    SET CONVERTED = CONVERTED || CHAR_AT;
   ELSE
    SET CONVERTED = CONVERTED || (ASCII(CHAR_AT) - 55);
   END IF;
   SET I = I + 1;
  END WHILE;

  CALL DBMS_OUTPUT.PUT_LINE(CONVERTED);
  
  -- This function is implemented in Rosetta code.
  SET CHECKSUM_FUNC = LUHN_TEST(CONVERTED);
  IF (CHECKSUM_FUNC = 0) THEN
   SET RET = 0;
   --SET RET = TRUE;
  END IF;

  RETURN RET;
 END @

Output:

db2 -td@
db2 => BEGIN
...
db2 (cont.) => END @
DB20000I  The SQL command completed successfully.
db2 => VALUES VALIDATE_ISIN('US0378331005')@
1     
------
     0

  1 record(s) selected.

30280378331005
It is a valid number 27+23=50
db2 => VALUES VALIDATE_ISIN('US0373831005')@
1     
------
     1

  1 record(s) selected.

30280373831005
It is NOT a valid number 22+24=46
db2 => VALUES VALIDATE_ISIN('U50378331005')@
1     
------
SQL0438N  Application raised error or warning with diagnostic text: "Country 
code with invalid characters".  SQLSTATE=ISIN1
db2 => VALUES VALIDATE_ISIN('U503378331005')@
1     
------
SQL0433N  Value "U503378331005" is too long.  SQLSTATE=22001
db2 => VALUES VALIDATE_ISIN('AU0000XVGZA3')@
1     
------
     0

  1 record(s) selected.

1030000033311635103
It is a valid number 18+12=30
db2 => VALUES VALIDATE_ISIN('AU0000VXGZA3')@
1     
------
     0

  1 record(s) selected.

1030000031331635103
It is a valid number 18+12=30
db2 => VALUES VALIDATE_ISIN('FR0000988040')@

1     
------
     0

  1 record(s) selected.

15270000988040
It is a valid number 20+30=50

Tcl[edit]

package require Tcl 8.6   ;# mostly needed for [assert].  Substitute a simpler one or a NOP if required.

A proc like assert is always good to have around. This one tries to report values used in its expression using subst:

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

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:

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]]
    }

    # copied from "Luhn test of credit card numbers"
    # included here for ease of testing, and because it is short
    proc luhn digitString {
        if {[regexp {[^0-9]} $digitString]} {error "not a number"}
        set sum 0
        set flip 1
        foreach ch [lreverse [split $digitString {}]] {
            incr sum [lindex {
                {0 1 2 3 4 5 6 7 8 9}
                {0 2 4 6 8 1 3 5 7 9}
            } [expr {[incr flip] & 1}] $ch]
        }
        return [expr {($sum % 10) == 0}]
    }

    proc validate {isin} {
        if {![regexp {^[A-Z]{2}[A-Z0-9]{9}[0-9]$} $isin]} {return false}
        luhn [normalize $isin]
    }

}

To run the test suite, we use the tcltest framework included with Tcl:

package require tcltest

tcltest::test isin-1 "Test isin validation" -body {
    foreach {isin ok} {
        US0378331005    yes
        US0373831005    no
        U50378331005    no
        US03378331005   no
        AU0000XVGZA3    yes
        AU0000VXGZA3    yes
        FR0000988040    yes
    } {
        if {$ok} {
            assert {[isin::validate $isin]}
        } else {
            assert {![isin::validate $isin]}
        }
    }
    return ok
} -result ok

Transact-SQL[edit]

CREATE FUNCTION dbo._ISINCheck( @strISIN VarChar(40) )
RETURNS bit
AS
BEGIN
--*** Test an ISIN code and return 1 if it is valid, 0 if invalid.
DECLARE @bValid	 bit;

SET @bValid = CASE WHEN @strISIN LIKE '[A-Z][A-Z][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][0-9]' THEN 1 ELSE 0 END
IF @bValid = 1
	BEGIN
		DECLARE @strTest VarChar(40) = '';
		DECLARE @strAdd  VarChar(2);
		DECLARE @p INT = 0;
		WHILE @p < LEN(@strISIN)
			BEGIN
				SET @p = @p+1;
				SET @strAdd = SUBSTRING(@strISIN,@p,1);
				IF @strAdd LIKE '[A-Z]' SET @strAdd = CONVERT(VarChar(2),ASCII(UPPER(@strAdd))-55);
				SET @strTest = @strTest + @strAdd;
			END;

		-- Proceed with Luhn test
		DECLARE @strLuhn VarChar(40) = REVERSE(@strTest); -- usage: set once, never changed
		DECLARE @strS2Values VarChar(10) = '0246813579';  -- constant: maps digits to their S2 summed values
		SET @p = 0; -- reset loop counter
		DECLARE @intValue INT;
		DECLARE @intSum	INT = 0;
		-- loop through the reversed string, get the value (even-positioned digits are mapped) and add it to @intSum
		WHILE @p < LEN(@strLuhn)
			BEGIN
				SET @p = @p+1;
				SET @intValue = CONVERT(INT, SUBSTRING(@strLuhn,@p,1) ) -- value of the digit at position @p in the string
				IF @p % 2 = 0	SET @intValue = CONVERT(INT,SUBSTRING(@strS2Values,@intValue+1,1))
				SET @intSum = @intSum + @intValue
			END
		-- If the of the digits' mapped values ends in 0 (modulo 10 = 0) then the Luhn test succeeds
		SET @bValid = CASE WHEN @intSum % 10 = 0 THEN 1 ELSE 0 END 
	END;

RETURN @bValid
END

Testing

-- Testing. The following tests all pass.
;WITH ISIN_Tests AS
( SELECT 'US0378331005' AS ISIN, 1 Expected
  UNION SELECT 'US0373831005',0
  UNION SELECT 'U50378331005',0
  UNION SELECT 'US03378331005',0
  UNION SELECT 'AU0000XVGZA3',1
  UNION SELECT 'AU0000VXGZA3',1
  UNION SELECT 'FR0000988040',1
  UNION SELECT '0___garbage',0
  UNION SELECT '',0
)
SELECT ISIN, Expected, dbo._ISINCheck(ISIN) AS TestResult FROM ISIN_Tests ORDER BY ISIN

VBScript[edit]

' Validate International Securities Identification Number - 03/03/2019

buf=buf&test("US0378331005")&vbCrLf
buf=buf&test("US0373831005")&vbCrLf
buf=buf&test("U50378331005")&vbCrLf
buf=buf&test("US03378331005")&vbCrLf
buf=buf&test("AU0000XVGZA3")&vbCrLf
buf=buf&test("AU0000VXGZA3")&vbCrLf
buf=buf&test("FR0000988040")&vbCrLf
msgbox buf,,"Validate International Securities Identification Number"

function test(cc)
	dim err,c,r,s,i1,i2
	if len(cc)=12 then
		for i=1 to len(cc)
			p=instr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",mid(cc,i,1))
			if p<>0 then c=c&(p-1) else err=1
		next 'i
		for i=1 to 2
			if instr("ABCDEFGHIJKLMNOPQRSTUVWXYZ",mid(cc,i,1))=0 then err=1 
		next 'i
		if err=0 then
			for i=len(c) to 1 step -1
				r=r&mid(c,i,1)
			next 'i
			for i=1 to len(r) step 2
				i1=i1+cint(mid(r,i,1))
			next 'i
			for i=2 to len(r) step 2
				ii=cint(mid(r,i,1))*2
				if ii>=10 then ii=ii-9
				i2=i2+ii
			next 'i
			s=cstr(i1+i2)
			if mid(s,len(s),1)="0" then
				msg="valid"
			else
				msg="invalid ??1"
			end if
		else
			msg="invalid ??2"
		end if
	else
		msg="invalid ??3"
	end if
	test=cc&" "&msg
end function 'test
Output:
US0378331005 valid
US0373831005 invalid ??1
U50378331005 invalid ??2
US03378331005 invalid ??3
AU0000XVGZA3 valid
AU0000VXGZA3 valid
FR0000988040 valid

Visual Basic[edit]

Works with: Visual Basic version VB6 Standard

Calls LuhnCheckPassed() function described at Luhn_test_of_credit_card_numbers#Visual_Basic

Function IsValidISIN(ByVal ISIN As String) As Boolean
Dim s As String, c As String
Dim i As Long
  If Len(ISIN) = 12 Then
    For i = 1 To Len(ISIN)
      c = UCase$(Mid(ISIN, i, 1))
        Select Case c
        Case "A" To "Z"
          If i = 12 Then Exit Function
          s = s & CStr(Asc(c) - 55)
        Case "0" To "9"
          If i < 3 Then Exit Function
          s = s & c
        Case Else
          Exit Function
        End Select
   Next i
  IsValidISIN = LuhnCheckPassed(s)
  End If
End Function

Test:

Sub Main()
  Debug.Assert IsValidISIN("US0378331005")
  Debug.Assert Not IsValidISIN("US0373831005")
  Debug.Assert Not IsValidISIN("U50378331005")
  Debug.Assert Not IsValidISIN("US03378331005")
  Debug.Assert IsValidISIN("AU0000XVGZA3")
  Debug.Assert IsValidISIN("AU0000VXGZA3")
  Debug.Assert IsValidISIN("FR0000988040")
  Debug.Assert Not IsValidISIN("FR000098804O")
End Sub

Visual Basic .NET[edit]

Translation of: C#
Option Strict On
Imports System.Text.RegularExpressions

Module Module1
    ReadOnly IsinRegex As New Regex("^[A-Z]{2}[A-Z0-9]{9}\d$", RegexOptions.Compiled)

    Function DigitValue(c As Char) As Integer
        Dim temp As Integer
        If Asc(c) >= Asc("0"c) AndAlso Asc(c) <= Asc("9"c) Then
            temp = Asc(c) - Asc("0"c)
        Else
            temp = Asc(c) - Asc("A"c) + 10
        End If
        Return temp
    End Function

    Function LuhnTest(number As String) As Boolean
        Return number.Select(Function(c, i) (AscW(c) - 48) << ((number.Length - i - 1) And 1)).Sum(Function(n) If(n > 9, n - 9, n)) Mod 10 = 0
    End Function

    Function Digitize(isin As String) As String
        Return String.Join("", isin.Select(Function(c) $"{DigitValue(c)}"))
    End Function

    Function IsValidIsin(isin As String) As Boolean
        Return IsinRegex.IsMatch(isin) AndAlso LuhnTest(Digitize(isin))
    End Function

    Sub Main()
        Dim isins() = {
            "US0378331005",
            "US0373831005",
            "U50378331005",
            "US03378331005",
            "AU0000XVGZA3",
            "AU0000VXGZA3",
            "FR0000988040"
        }

        For Each isin In isins
            If IsValidIsin(isin) Then
                Console.WriteLine("{0} is valid", isin)
            Else
                Console.WriteLine("{0} is not valid", isin)
            End If
        Next
    End Sub

End Module
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

V (Vlang)[edit]

Translation of: go
import regex

const (
    inc = [
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 2, 4, 6, 8, 1, 3, 5, 7, 9],
    ]
)
 
fn valid_isin(n string) bool {
    mut r,_,_ := regex.regex_base('^[A-Z]{2}[A-Z0-9]{9}\d$')
    if !r.matches_string(n) {
        return false
    }
    mut sum := 0
    mut p := 0
    for i := 10; i >= 0; i-- {
        p = 1 - p
        mut d := n[i..i+1].int()
        if d < 'A'.int() {
            sum += inc[p][d-'0'.int()]
        } else {
            d -= 'A'.int()
            sum += inc[p][d%10]
            p = 1 - p
            sum += inc[p][d/10+1]
        }
    }
    sum += n[11..12].int() - '0'.int()
    return sum%10 == 0
}

struct Testcases {
    isin string
    valid bool
}

fn main(){
    testcases := [
        Testcases{"US0378331005", true},
        Testcases{"US0373831005", false},
        Testcases{"U50378331005", false},
        Testcases{"US03378331005", false},
        Testcases{"AU0000XVGZA3", true},
        Testcases{"AU0000VXGZA3", true},
        Testcases{"FR0000988040", true},
    ]
 
    for testcase in testcases {
        actual := valid_isin(testcase.isin)
        if actual != testcase.valid {
            println("expected ${testcase.valid} for ${testcase.isin}, got $actual")
        }
    }
}
Output:
expected true for US0378331005, got false
expected true for AU0000XVGZA3, got false
expected true for AU0000VXGZA3, got false
expected true for FR0000988040, got false

Wren[edit]

Library: Wren-str
Library: Wren-trait
Library: Wren-fmt

The Luhn test method is reproduced here for convenience.

import "/str" for Char
import "/trait" for Stepped
import "/fmt" for Conv, Fmt

var luhn = Fn.new { |s|
    s = s[-1..0]
    var s1 = Stepped.new(s, 2).reduce(0) { |sum, d| sum + d.bytes[0] - 48 }
    var s2 = Stepped.new(s[1..-1], 2).reduce(0) { |sum, d|
        var d2 = (d.bytes[0] - 48) * 2
        return sum  + ((d2 > 9) ?  d2%10 + 1 : d2)
    }
    return (s1 + s2)%10 == 0
}

var isin = Fn.new { |s|
    if (!(s is String && s.count == 12)) return false
    for (i in 0..11) {
        var c = s[i]
        if (i <= 1) {
            if (!Char.isUpper(c)) return false
        } else if (i >= 2 && i <= 10) {
            if (!Char.isUpper(c) && !Char.isDigit(c)) return false
        } else {
            if (!Char.isDigit(c)) return false
        }
    }
    var dec = ""
    for (i in 0...s.count) dec = dec + "%(Conv.atoi(s[i], 36))"
    return luhn.call(dec)
}

var tests = [
    "US0378331005", "US0373831005", "U50378331005", "US03378331005",
    "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"
]

for (test in tests) {
    var ans = (isin.call(test)) ? "valid" : "not valid"
    System.print("%(Fmt.s(-13, test)) -> %(ans)")
}
Output:
US0378331005  -> valid
US0373831005  -> not valid
U50378331005  -> not valid
US03378331005 -> not valid
AU0000XVGZA3  -> valid
AU0000VXGZA3  -> valid
FR0000988040  -> valid

XPL0[edit]

string 0;               \use zero-terminated strings

func    Luhn(Str);      \Return 'true' if digits in Str pass Luhn test
char    Str;
int     Len, Sum, I, Dig;
[Len:= 0;               \find length of Str
while Str(Len) do Len:= Len+1;
Sum:= 0;                \sum even and odd digits
for I:= 0 to Len-1 do   \(no need to reverse)
        [if (I xor Len) & 1 then
                Sum:= Sum + Str(I) - ^0
        else    [Dig:= Str(I) - ^0;
                Dig:= Dig*2;
                Sum:= Sum + Dig/10 + rem(0);
                ];
        ];
return rem(Sum/10) = 0; 
];      \Luhn

func    Valid(Str);     \Return 'true' if valid ISIN code
char    Str, Str2(100);
int     Sum, I, J, C, V;
[J:= 0;
for I:= 0 to 12-1 do    \convert letters in Str to digits in Str2
        [C:= Str(I);
        case of
          C>=^0 & C<=^9: [Str2(J):= C;  J:= J+1];
          C>=^A & C<=^Z: [Str2(J):= (C-^A+10)/10 + ^0;  J:= J+1;
                          Str2(J):= rem(0) + ^0;  J:= J+1]
        other return false;
        if I=1 & J#4 then return false; \first two chars not letters
        ];
if Str(I) # 0 then return false;        \too long
Str2(J):= 0;            \terminate string
return Luhn(Str2);
];      \Valid

int     ISIN, N;
[ISIN:= ["US0378331005",
         "US0373831005",
         "U50378331005",
         "US03378331005",
         "AU0000XVGZA3",
         "AU0000VXGZA3",
         "FR0000988040"];
for N:= 0 to 7-1 do
        [Text(0, ISIN(N));
         Text(0, if Valid(ISIN(N))
                then " is valid"
                else " is not valid");
        CrLf(0);
        ];
]
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

Yabasic[edit]

Translation of: FreeBASIC
sub luhntest(cardnr$)
    local i, j, s1, s2, l
 
    cardnr$ = Trim$(cardnr$) // remove spaces
    
    l = Len(cardnr$)
 
    // sum odd numbers
    For i = l To 1 Step -2
        s1 = s1 + (asc(mid$(cardnr$, i, 1)) - Asc("0"))
    Next
    // sum even numbers
    For i = l-1 To 1 Step -2
        j = asc(mid$(cardnr$, i, 1)) - Asc("0")
        j = j * 2
        If j > 9 j = mod(j, 10) + 1
        s2 = s2 + j
    Next
 
    return mod(s1 + s2, 10) = 0
End sub
 
// ------=< MAIN >=-----
 
data "US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040", ""
 
do
    read test_item$
    if test_item$ = "" break
    
    l = Len(test_item$)
    If l <> 12 Then
        Print test_item$, " Invalid, length <> 12 char."
        Continue
    End If
    c1$ = mid$(test_item$, 1, 1) : c2$ = mid$(test_item$, 2, 1)
    If c1$ < "A" Or c1$ > "Z" or c2$ < "A" or c2$ > "Z" Then
        Print test_item$, " Invalid, number needs to start with 2 characters"
        Continue
    End If
    test_str$ = ""
    For n = 1 To l
        x = asc(mid$(test_item$, n, 1)) - Asc("0")
        // if is a letter we to correct for that
        If x > 9 x = x - 7
        If x < 10 Then
            test_str$ = test_str$ + Str$(x)
        Else // two digest number
            test_str$ = test_str$ + Str$(int(x / 10)) + Str$(mod(x, 10))
        End If
    Next
    Print test_item$;
    if luhntest(test_str$) then print " Valid" else print " Invalid, checksum error" end if
loop

zkl[edit]

Uses the luhn test from Luhn_test_of_credit_card_numbers#zkl (copied here as it is short).

fcn validateISIN(isin){
   RegExp(String("^","[A-Z]"*2,"[A-Z0-9]"*9,"[0-9]$")).matches(isin) and 
      luhnTest(isin.split("").apply("toInt",36).concat().toInt())
}
fcn luhnTest(n){
   0 == (n.split().reverse().reduce(fcn(s,n,clk){
      s + if(clk.inc()%2) n else 2*n%10 + n/5 },0,Ref(1)) %10)
}
println("     ISIN       Valid?");
foreach isin in (T("US0378331005","US0373831005","U50378331005",
	       "US03378331005","AU0000XVGZA3","AU0000VXGZA3","FR0000988040")){
   println(isin," --> ",validateISIN(isin));
}
Output:
     ISIN       Valid?
US0378331005 --> True
US0373831005 --> False
U50378331005 --> False
US03378331005 --> False
AU0000XVGZA3 --> True
AU0000VXGZA3 --> True
FR0000988040 --> True