CloudFlare suffered a massive security issue affecting all of its customers, including Rosetta Code. All passwords not changed since February 19th 2017 have been expired, and session cookie longevity will be reduced until late March.--Michael Mol (talk) 05:15, 25 February 2017 (UTC)

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[edit]

Write a function or program that takes a string as input, and checks whether it is a valid ISIN.
It is only valid if it has the correct format, and the embedded checksum is correct.

Demonstrate that your code passes the test-cases listed below.

Details

The format of an ISIN is as follows:

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

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

The checksum can be validated as follows:

  1. Replace letters with digits, by converting each character from base 36 to base 10, e.g. AU0000XVGZA31030000033311635103.
  2. Perform the Luhn test on this base-10 number.
    There is a separate task for this test: Luhn test of credit card numbers.
    You don't have to replicate the implementation of this test here – you can just call the existing function from that task. (Add a comment stating if you did this.)
Test-cases
ISIN Validity Comment
US0378331005 valid
US0373831005 not valid The transposition typo is caught by the checksum constraint.
U50378331005 not valid The substitution typo is caught by the format constraint.
US03378331005 not valid The duplication typo is caught by the format constraint.
AU0000XVGZA3 valid
AU0000VXGZA3 valid Unfortunately, not all transposition typos are caught by the checksum constraint.
FR0000988040 valid

(The comments are just informational. Your function should simply return a Boolean result. See #Perl_6 for a reference solution.)

See also

Useful resources:


Related tasks:



Ada[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

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

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>

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

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

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 :: [Char]
capitalLetters = ['A' , 'B'..'Z']
 
numbers :: [Char]
numbers = ['0' , '1' , '2', '3' , '4' , '5', '6' , '7' , '8' , '9' ]
 
correctFormat :: String -> Bool
correctFormat isin = (length isin == 12 ) && ( all (\b -> elem b 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 str = concat $ map convert str
where
convert :: Char -> String
convert c = if isDigit c then show $ digitToInt c else show ( fromEnum c - 55 )
 
collectOddandEven :: String -> (String , String )
collectOddandEven term
|odd $ length term = (concat [take 1 $ drop n term | n <- [0,2..length term - 1]] ,
concat [take 1 $ drop d term | d <- [1,3..length term - 2]] )
|otherwise = (concat [take 1 $ drop n term | n <- [0,2..length term -2]] ,
concat [take 1 $ drop d term | d <- [1,3..length term - 1]] )
 
 
addUpDigits :: [Int] -> Int
addUpDigits list = sum $ map (\d -> if d > 9 then sum $ map digitToInt $ show d else d ) list
 
printSolution :: String -> IO ( )
printSolution str = do
putStr $ str ++ " is"
if verifyISIN str == True then putStrLn " valid" else putStrLn " not valid"
 
main :: IO ( )
main = do
let isinnumbers = ["US0378331005" , "US0373831005" , "US03378331005" , "AU0000XVGZA3" ,
"AU0000VXGZA3" , "FR0000988040"]
mapM_ printSolution isinnumbers
Output:
US0378331005 is valid
US0373831005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid

J[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.
splt=: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' i. ' ' -.~ ":
checksum=: 3 : '10| - +/ splt (* 2 1 $~ #) |. splt splt y'
 
assert 5 = checksum 'US037833100'
assert 0 = checksum 'US037833107'
assert 3 = checksum 'AU0000VXGZA'
assert 6 = checksum 'GB000263494'

Java[edit]

This now assumes that the existing Luhn algorithm implementation from the Luhn test of credit card numbers task is available in the same (default) package.

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\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 Luhn.luhnTest(sb.toString());
}
}
US0378331005 is valid
US0373831009 is valid
D56000543287 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
GB0002634946 is valid
US0373831005 is not valid

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

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

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

Perl 6[edit]

Using the luhn-test function defined at Luhn test of credit card numbers#Perl 6:

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

Testing:

say "$_ is {$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

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

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)

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" /*check 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 digit*/
if @=='' then if \luhn($) then @= "not" /* " " " " passed 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 defaults for input:

                  US0378331005       valid
                  US0373831005   not valid
                  U50378331005   not valid
                 US03378331005   not valid
                  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]

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;

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

Visual Basic[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.
Works with: VB6
 
Option Explicit
 
Function MakeIsinCode(Exchange As String, security As String)
Dim numLeadingZeroes As Integer
 
numLeadingZeroes = 9 - Len(security)
 
Dim leader As String
 
leader = Exchange & String(numLeadingZeroes, "0") & security
 
MakeIsinCode = leader & CStr(IsinCheckDigit(leader))
End Function
 
Function IsinCheckDigit(ByVal security As String) As Integer
Dim digits As String
 
Dim i As Integer
 
For i = 1 To Len(security)
Dim ch As String
 
ch = UCase(Mid(security, i, 1))
 
If ch >= "A" And ch <= "Z" Then
' A to Z translated to "10", "11", .. "35"
digits = digits & CStr(Asc(ch) - 55)
ElseIf ch >= "0" And ch <= "9" Then
digits = digits & ch
Else
Err.Raise 50001, , "Security must contain only letters and digits"
End If
Next
 
Dim total As Integer
Dim tmp As Integer
 
total = 0
 
'If rightmost even, "other" digits for doubling are 2,4,6. If rightmost odd, they're 1,3,5.
'rightmost digit is always doubled, so start with it and work backwards
Dim other As Boolean
other = True
 
For i = Len(digits) To 1 Step -1
tmp = CInt(Mid(digits, i, 1))
 
If other Then
If tmp < 5 Then
' 0 to 4 map to 0,2,4,6,8
total = total + (tmp * 2)
Else
' 5 to 9 map to 1,3,5,7,9
total = total + ((tmp * 2) - 9)
End If
Else
total = total + tmp
End If
 
'Toggle doubling flag
other = Not other
Next
 
'Last Mod 10 is to wrap 10 to zero
IsinCheckDigit = (10 - (total Mod 10)) Mod 10
End Function
 

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