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:
For this task, you may assume that any 2-character alphabetic sequence is a valid country code.
The checksum can be validated as follows:
- Replace letters with digits, by converting each character from base 36 to base 10, e.g.
AU0000XVGZA3
→1030000033311635103
. - Perform the Luhn test on this base-10 number.
There is a separate task for this test: Luhn test of credit card numbers.
You don't have to replicate 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
- Interactive online ISIN validator
- Wikipedia article: International Securities Identification Number
11l
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
* 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
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
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
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
# 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
BASIC256
array base 1
dim test_set$(7)
test_set$ = {"US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"}
for i = 1 to test_set$[?]
test_str$ = ""
l = length(test_set$[i])
if l <> 12 then
print test_set$[i]; " Invalid, length <> 12 char."
continue for
end if
if asc(mid(test_set$[i], 1, 1)) < asc("A") or asc(mid(test_set$[i], 2, 1)) < asc("A") then
print test_set$[i]; " Invalid, number needs to start with 2 characters"
continue for
end if
for n = 1 to l
x = asc(mid(test_set$[i], n, 1)) - asc("0")
if x > 9 then x -= 7
if x < 10 then
test_str$ += string(x)
else # two digest number
test_str$ += string(x \ 10) + string(x mod 10)
end if
next
print test_set$[i]; " ";
if luhntest(test_str$) = 1 then
print "Invalid, checksum error"
else
print "Valid"
end if
next
end
function luhntest(cardnr$)
cardnr$ = trim(cardnr$) # remove spaces
l = length(cardnr$)
s1 = 0
s2 = 0
# sum odd numbers
for i = 1 to l step 2
s1 += fromradix(asc(mid(cardnr$, i, 1)), 10)
next
# sum even numbers
for i = 2 to l step 2
j = fromradix(asc(mid(cardnr$, i, 1)), 10)
j *= 2
if j > 9 then j = (j mod 10) + 1
s2 += j
next
return (s1 + s2) mod 10 = 0
end function
- Output:
Similar to FreeBASIC entry.
Bruijn
Using bruijn's luhn
solution from Luhn test of credit card numbers:
:import luhn_test_of_credit_card_numbers .
:import std/Number/Conversion .
:import std/Combinator .
:import std/String .
:import std/Char .
:import std/Logic .
:import std/Number .
# verifies ISIN format
format? [len ⋀? country ⋀? security ⋀? checksum]
len (length 0) =? (+12)
country all? uppercase? (take (+2) 0)
security all? (φ or? uppercase? numeric?) (take (+9) (drop (+2) 0))
checksum numeric? _0
# performs luhn test
checksum? (map (from-base36 → number→string)) → concat → string→number → luhn
from-base36 binary→ternary → [(0 - (0 ≥? (+65) ((+65) - (+10)) (+48)))]
# performs format and checksum test
validate φ and? format? checksum?
:test (validate "US0378331005") (true)
:test (validate "US0373831005") (false)
:test (validate "U50378331005") (false)
:test (validate "US03378331005") (false)
:test (validate "AU0000XVGZA3") (true)
:test (validate "AU0000VXGZA3") (true)
:test (validate "FR0000988040") (true)
C
#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#
{
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++
#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
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
(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
>>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
(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
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
Dart
bool checkISIN(String isin) {
int j = 0, v = 0;
List<int> s = List.filled(24, 0);
for (int i = 0; i < 12; i++) {
int k = isin.codeUnitAt(i);
if (k >= '0'.codeUnitAt(0) && k <= '9'.codeUnitAt(0)) {
if (i < 2) return false;
s[j++] = k - '0'.codeUnitAt(0);
} else if (k >= 'A'.codeUnitAt(0) && k <= 'Z'.codeUnitAt(0)) {
if (i == 11) return false;
k -= 'A'.codeUnitAt(0) - 10;
s[j++] = k ~/ 10;
s[j++] = k % 10;
} else {
return false;
}
}
if (isin.length > 12) return false;
for (int i = j - 2; i >= 0; i -= 2) {
int k = 2 * s[i];
v += k > 9 ? k - 9 : k;
}
for (int i = j - 1; i >= 0; i -= 2) {
v += s[i];
}
return v % 10 == 0;
}
void main() {
List<String> test = [
"US0378331005",
"US0373831005",
"U50378331005",
"US03378331005",
"AU0000XVGZA3",
"AU0000VXGZA3",
"FR0000988040"
];
for (String isin in test) {
print('$isin - ${checkISIN(isin)}');
}
}
- Output:
US0378331005 - true US0373831005 - false U50378331005 - false US03378331005 - false AU0000XVGZA3 - true AU0000VXGZA3 - true FR0000988040 - true
Delphi
function StrToBase10(S: string): TByteDynArray;
{Convert ASCII string to Base-10}
{ASCII Digits converted to integer 0..9 }
{ASCII Chars convert to bytes "A"=10, "B"=11, etc }
var I: Integer;
var B: byte;
procedure StoreByte(B: byte);
begin
SetLength(Result,Length(Result)+1);
Result[High(Result)]:=B;
end;
begin
SetLength(Result,0);
for I:=1 to Length(S) do
begin
if S[I] in ['0'..'9'] then StoreByte(Byte(S[I])-$30)
else
begin
B:=(Byte(S[I])-$41)+10;
StoreByte(B div 10);
StoreByte(B mod 10);
end;
end;
end;
{Simplifies cases where we have to sum a two digit number}
const DigitSum: array [0..18] of byte = (0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9);
function LuhnTest(Nums: array of byte): boolean;
{Perform Luhn Test of byte array}
var I,J,Len,Sum,Sum1,Sum2: integer;
var Rev: array of byte;
begin
Sum1:=0; Sum2:=0;
Len:=High(Nums);
for I:=Len downto 0 do
if ((I-Len) and 1)=0 then Sum1:=Sum1 + Nums[I]
else Sum2:=Sum2 + DigitSum[Nums[I]*2];
Sum:=Sum1+Sum2;
Result:=(Sum mod 10)=0;
end;
{String error types}
type TStringErrors = (seNone,seLength,seCountry);
function ValidateStr(IDStr: string): TStringErrors;
{Validate string checking for incorrectly length}
{And invalid country code}
begin
if Length(IDStr)<>12 then Result:=seLength
else if not (IDStr[1] in ['a'..'z','A'..'Z']) or
not (IDStr[2] in ['a'..'z','A'..'Z']) then Result:=seCountry
else Result:=seNone;
end;
procedure ValidateID(Memo: TMemo; IDStr: string);
{Validate and display status of string}
var BA: TByteDynArray;
var LT: boolean;
var SE: TStringErrors;
var S: string;
begin
SE:=ValidateStr(IDStr);
BA:=StrToBase10(IDStr);
LT:=LuhnTest(BA);
if LT and (SE=seNone) then Memo.Lines.Add(IDStr+': Valid')
else
begin
S:=IDStr+': Invalid';
if not LT then S:=S+', Luhn Error';
case SE of
seLength: S:=S+', Length Error';
seCountry: S:=S+', Country Code Error';
end;
Memo.Lines.Add(S);
end;
end;
procedure ValidateSecuritiesID(Memo: TMemo);
var BA: TByteDynArray;
var I: integer;
var S: string;
begin
ValidateID(Memo,'US0378331005');
ValidateID(Memo,'US0373831005');
ValidateID(Memo,'U50378331005');
ValidateID(Memo,'US03378331005');
ValidateID(Memo,'AU0000XVGZA3');
ValidateID(Memo,'AU0000VXGZA3');
ValidateID(Memo,'FR0000988040');
end;
- Output:
US0378331005: Valid US0373831005: Invalid, Luhn Error U50378331005: Invalid, Country Code Error US03378331005: Invalid, Length Error AU0000XVGZA3: Valid AU0000VXGZA3: Valid FR0000988040: Valid Elapsed Time: 6.863 ms.
EasyLang
func isin t$ .
if len t$ <> 12
return 0
.
for i to 12
k = strcode substr t$ i 1
if k >= 48 and k <= 57
if i <= 2
return 0
.
s[] &= k - 48
elif k >= 65 and k <= 91
if (i = 12)
return 0
.
k -= 55
s[] &= k div 10
s[] &= k mod 10
else
return 0
.
.
i = len s[] - 1
while i >= 1
k = 2 * s[i]
if k > 9
k -= 9
.
v += k
i -= 2
.
i = len s[]
while i >= 1
v += s[i]
i -= 2
.
if v mod 10 = 0
return 1
.
.
test$[] = [ "US0378331005" "US0373831005" "U50378331005" "US03378331005" "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040" ]
for t$ in test$[]
if isin t$ = 1
print t$ & " is valid"
else
print t$ & " is invalid"
.
.
- Output:
US0378331005 is valid US0373831005 is invalid U50378331005 is invalid US03378331005 is invalid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Elixir
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
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
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
' 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
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
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
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
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
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
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
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
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
The luhn test is repeated here for simplicity.
val luhntest = fn(s) {
val t = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
val numbers = s -> s2n
val oddeven = len(numbers) rem 2
for[=0] i of numbers {
_for += if i rem 2 == oddeven {
numbers[i]
} else {
t[numbers[i]+1]
}
} div 10
}
val isintest = fn(s) {
s -> re/^[A-Z][A-Z][0-9A-Z]{9}[0-9]$/ and
s -> s2n -> join -> luhntest
}
val tests = {
"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
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
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
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
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
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
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
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
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
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
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
#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
(formerly Perl 6)
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
/*REXX program validates International Securities ID numbers. */
Parse Arg z /*obtain ISINs from the C.L. */
If z='' Then /* [?] use the default list of ISINs */
z='US0378331005 US0373831005 U50378331005 US03378331005',
'US037*331005',
'XY037833100Z AU0000XVGZA3 AU0000VXGZA3 FR0000988040'
valid='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' /*X must contain 0-->9 A-->Z */
Do n=1 To words(z) /* [?] process all specified ISINs.*/
x=word(z,n) /*obtain an ISIN from the Z list. */
p=verify(x,valid,'N')
If p>0 Then msg='invalid character in position' p':' substr(x,p,1)
Else Do
dd='' /* [?] construct list of ISIN digits. */
Do k=1 To length(x)
_=substr(x,k,1) /*the ISIN may contain alphabetic chars*/
p=pos(_,valid) /*X contains 0-->9 A-->Z */
dd=dd||p-1 /*convert X string (base 36 --? dec).*/
End
msg=''
Select
When length(x)\==12 Then msg='not exactly 12 chars'
When \datatype( left(x,2),'U') Then msg='not starting with 2 capital chars'
When \datatype(right(x,1),'W') Then msg='last character is not a digit'
Otherwise
If \luhn(dd) Then msg='does not pass the Luhn test'
End
End
If msg='' Then
Say right(x,15) ' valid' /* display the positive message. */
Else
Say right(x,15) 'not valid:' msg /* display the problem */
End /*n*/
Exit /*stick a fork in it, we're all done */
/*-----------------------------------------------------------------------------------*/
luhn: Procedure
Parse Arg x /* get credit card number; */
dsum=0 /* zero digit 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)
dsum=dsum+substr(y,j,1)+left(_,1)+substr(_,2,1,0)
End
Return right(dsum,1)==0 /* Return 1 if number passed Luhn test */
- output when using the default inputs:
US0378331005 valid US0373831005 not valid: does not pass the Luhn test U50378331005 not valid: not starting with 2 capital chars US03378331005 not valid: not exactly 12 chars US037*331005 not valid: invalid character in position 6: * XY037833100Z not valid: last character is not a digit AU0000XVGZA3 valid AU0000VXGZA3 valid FR0000988040 valid
Ring
# 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
RPL
LUHN?
is defined at Luhn test of credit card numbers
« IF DUP SIZE 12 ≠ THEN DROP 0 ELSE "" 1 3 PICK SIZE FOR j OVER j DUP SUB IF "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" SWAP POS THEN LASTARG 1 - + END NEXT LUHN? { "AD" "AT" "AU" "BE" "CA" "DE" "ES" "FR" "GB" "HK" "IT" "US" "ZW" } @ country codes sample ROT 1 2 SUB POS AND END » 'ISIN?' STO
{"US0378331005" "US0373831005" "U50378331005" "US03378331005" "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040"}
1 « ISIN? » DOLIST
- Output:
1: { 1 0 0 0 1 1 1 }
Ruby
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
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
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
- 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
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
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
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
Uiua
Base ← "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Luhn ← =0◿10+⊃(/+⊢|/+∵(⍥(-9)>9.×2)⊡1)⍉⬚0↯∞_2⇌
Checksum ← Luhn≡⋕/◇⊂≡(□°⋕⊗)⊙(¤Base)
Format ← ××⊃(/×≡(≥@A)↙2|=12⧻|/↧∊:Base)
Valid ← ×⊃(Format|Checksum)
Tests ← {
"AU0000XVGZA3"
"US0378331005"
"US0373831005" # twiddled
"U50378331005" # 5 rather than S
"US03378331005" # Extra char
"AU0000XVGZA3"
"AU0000VXGZA3"
"FR0000988040"
"F00Ö00988040" # Illegal char
}
≡◇Valid Tests
- Output:
[1 1 0 0 0 1 1 1 0]
VBScript
' 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
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
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)
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
The Luhn test method is reproduced here for convenience.
import "./str" for Char
import "./iterate" 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
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
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
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
- Programming Tasks
- Solutions by Programming Task
- 11l
- 360 Assembly
- Ada
- ALGOL W
- AppleScript
- AWK
- BASIC256
- Bruijn
- C
- C sharp
- C++
- Caché ObjectScript
- Clojure
- COBOL
- Common Lisp
- D
- Dart
- Delphi
- SysUtils,StdCtrls
- EasyLang
- Elixir
- Factor
- Fortran
- FreeBASIC
- Go
- Groovy
- Groovy examples needing attention
- Examples needing attention
- Haskell
- J
- Java
- Jq
- Julia
- Kotlin
- Langur
- Lua
- Mathematica
- Wolfram Language
- Nim
- Perl
- Phix
- PicoLisp
- PowerShell
- PureBasic
- Python
- Quackery
- Racket
- Raku
- REXX
- Ring
- RPL
- Ruby
- Rust
- SAS
- Scala
- SQL PL
- Tcl
- Transact-SQL
- Uiua
- VBScript
- Visual Basic
- Visual Basic .NET
- V (Vlang)
- Wren
- Wren-str
- Wren-iterate
- Wren-fmt
- XPL0
- Yabasic
- Zkl