Validate International Securities Identification Number: Difference between revisions

m
(clarify checksum digit character class)
(142 intermediate revisions by 61 users not shown)
Line 1:
{{draft task}}
 
An [[wp:International_Securities_Identification_Number|International Securities Identification Number]] (ISIN) is a unique international identifier for a financial security such as a stock or bond.
 
{{introheader|Task}}
 
;Task:
Write a function or program that takes a string as input, and checks whether it is a valid ISIN.<br>
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.
 
It is only valid if it has the correct format, &nbsp; ''and'' &nbsp; the embedded checksum is correct.
 
Demonstrate that your code passes the test-cases listed below.
 
{{introheader|Details}}
 
;Details:
The format of an ISIN is as follows:
 
<!-- BEGIN DIAGRAM -->
<div style="margin:0.5em3em; white-space:nowrap; line-height:20px">
<div><span style="font-size:20px; font-family:'Lucida Console',Monaco,monospace"><span style="color:green; margin:0 0 0 10px">┌───────────── </span></span><span style="color:green">a 2-character ISO country code (A-Z)</span></div>
<div><span style="font-size:20px; font-family:'Lucida Console',Monaco,monospace"><span style="color:green; margin:0 -10px 0 10px">│</span>&nbsp;<span style="color:blue; margin:0 0 0 10px">┌─────────── </span></span><span style="color:blue">a 9-character security code (A-Z, 0-9)</span></div>
Line 22 ⟶ 23:
</div>
<!-- END DIAGRAM -->
 
 
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. <code>AU0000XVGZA3</code> &rarr;<code>1030000033311635103</code>.
# '''Perform the Luhn test on this base-10 number.'''<br>There is a separate task for this test: ''[[Luhn test of credit card numbers]]''.<br>You don't have to replicate the implementation of this test here &ndashnbsp; ─── &nbsp; you can just call the existing function from that task. &nbsp; (Add a comment stating if you did this.)
<br>
 
{{introheader|Test-cases}}
 
;Test cases:
{| class="wikitable"
:::: {| class="wikitable"
! ISIN
! Validity
Line 51 ⟶ 53:
|}
 
(The comments are just informational. &nbsp; Your function should simply return a Boolean result. &nbsp; See [[#Perl_6Raku]] for a reference solution.)
 
{{introheader|See also}}
 
Related task:
Useful resources:
* [http://www.isincodes.net/validate-isin.php Interactive online ISIN validator]
* Wikipedia article: [[wp:International_Securities_Identification_Number|International Securities Identification Number]]
<br>
 
Related tasks:
* [[Luhn test of credit card numbers]]
<br>
<hr>
 
=={{header|Ada}}==
 
;Also see:
{{update|Ada|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.}}
* [https://www.isincodes.net/validate-isin/ Interactive online ISIN validator]
* Wikipedia article: [[wp:International_Securities_Identification_Number|International Securities Identification Number]]
<br><br>
 
=={{header|11l}}==
=== package ISIN ===
{{trans|Python}}
 
<syntaxhighlight lang="11l">F check_isin(a)
We start with specifying an Ada package (a collection of subprograms) to compute the checksum digit for a given ISIN (without checksum), and to check the ISIN (when given with the checksum).
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’,
<lang Ada>package ISIN is
‘AU0000XVGZA3’, ‘AU0000VXGZA3’, ‘FR0000988040’].map(s -> check_isin(s)))</syntaxhighlight>
subtype Decimal is Character range '0' .. '9';
subtype Letter is Character range 'A' .. 'Z';
Invalid_Character: exception;
function Checksum(S: String) return Decimal;
function Valid(S: String) return Boolean is
(Checksum(S(S'First .. S'Last-1)) = S(S'Last));
end ISIN;</lang>
 
{{out}}
The implementation of the package is as follows.
<pre>
[1B, 0B, 0B, 0B, 1B, 1B, 1B]
</pre>
 
=={{header|360 Assembly}}==
<lang Ada>package body ISIN is
<syntaxhighlight lang="360asm">* 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</syntaxhighlight>
{{out}}
<pre>
1 US0378331005 OK
2 US0373831005 ?err1
3 U50378331005 ?err2
4 US03378331005 ?err3
5 AU0000XVGZA3 OK
6 AU0000VXGZA3 OK
7 FR0000988040 OK
</pre>
 
=={{header|Ada}}==
Calling the existing Luhn algorithm implementation from the ''[[Luhn test of credit card numbers]]'' task.
 
<syntaxhighlight lang="ada">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
-- converts a string of decimals and letters into a string of decimals
Offset: constant Integer := Character'Pos('A')-10;
-- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ...
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 ChecksumIs_Valid_ISIN(S: StringISIN_Type) return DecimalBoolean is
TNumber : String := To_Digits(S);
-- first convert letters to numbers by adding their ordinal position
Double: Boolean := True;
Sum: Integer range 0 .. 9 := 0;
Add: Integer range 0 .. 18;
Result: String(1 .. 2);
begin
forreturn S(S'First) I in reverseLetter T'Range loopand
S(S'First+1) in Letter and
Add := Integer'Value(T(I .. I));
S(S'Last) in Decimal and
if Double then
Luhn_Test(Number);
-- starting with the rightmost digit, every other digit is doubled
end Add := Add * 2Is_Valid_ISIN;
if Add > 8 then
-- if Add is 1X (*10, 12, ..., 18*), add X+1
Add := (Add mod 10) + 1;
end if;
end if;
Double := not Double;
Sum := (Sum + Add) mod 10;
end loop;
Result:= Integer'Image((10-Sum) mod 10); -- result is " X", with Decimal X
return Result(2);
end Checksum;
end ISIN;</lang>
 
Test_Cases : constant Array(1..6) of ISIN_Type :=
=== Computing Checksums ===
("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;</syntaxhighlight>
 
Output:
Now the main program is easy.
<pre>US0378331005:TRUE
It reads a couple of ISINs (without checksum) from the command line
US0373831005:FALSE
and outputs the checksum digits.
U50378331005:FALSE
AU0000XVGZA3:TRUE
AU0000VXGZA3:TRUE
FR0000988040:TRUE
US03378331005:Exception occured</pre>
 
=={{header|ALGOL W}}==
<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;
Uses the LuhnTest procedure from the [[Luhn test of credit card numbers]] task.
<syntaxhighlight lang="algolw">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.</syntaxhighlight>
{{out}}
<pre>
US0378331005 is valid
US0373831005 is invalid
U50378331005 is invalid
US03378331005 is invalid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid
</pre>
 
=={{header|AppleScript}}==
 
This script calls a handler posted for the [https://www.rosettacode.org/wiki/Luhn_test_of_credit_card_numbers#Straightforward Luhn test of credit card numbers] task.
 
<syntaxhighlight lang="applescript">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</syntaxhighlight>
 
{{output}}
<syntaxhighlight lang="applescript">{{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}}</syntaxhighlight>
=={{header|AWK}}==
<syntaxhighlight lang="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)
}
</syntaxhighlight>
{{out}}
<pre>
1 US0378331005
0 US0373831005
0 U50378331005
0 US03378331005
1 AU0000XVGZA3
1 AU0000VXGZA3
1 FR0000988040
</pre>
 
=={{header|BASIC256}}==
<syntaxhighlight lang="vbnet">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</syntaxhighlight>
{{out}}
<pre>Similar to FreeBASIC entry.</pre>
 
=={{header|Bruijn}}==
Using bruijn's <code>luhn</code> solution from [[Luhn test of credit card numbers]]:
<syntaxhighlight lang="bruijn">
: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)
</syntaxhighlight>
 
=={{header|C}}==
<syntaxhighlight lang="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 */</syntaxhighlight>
 
=={{header|C sharp|C#}}==
{
<syntaxhighlight lang="csharp">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}");
}
}
}
}</syntaxhighlight>
{{out}}
<pre>US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid</pre>
 
=={{header|C++}}==
<syntaxhighlight lang="cpp">
 
#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;
}
</syntaxhighlight>
 
=={{header|Caché ObjectScript}}==
 
<syntaxhighlight lang="cos">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)
}
 
}</syntaxhighlight>
{{out|Examples}}
<pre>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></pre>
 
=={{header|Clojure}}==
<syntaxhighlight lang="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)))
</syntaxhighlight>
<tt>luhn?</tt> is based on ''[[Luhn test of credit card numbers#Clojure]]''.
{{out}}
<pre>US0378331005: valid
US0373831005: invalid
U50378331005: invalid
US03378331005: invalid
AU0000XVGZA3: valid
AU0000VXGZA3: valid
FR0000988040: valid</pre>
 
=={{header|COBOL}}==
{{works with|GnuCOBOL}}
<syntaxhighlight lang="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.</syntaxhighlight>
 
{{out}}
<pre>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</pre>
 
=={{header|Common Lisp}}==
<syntaxhighlight lang="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))))</syntaxhighlight>
{{out}}
<pre>US0378331005: valid
US0373831005: invalid
U50378331005: invalid
US03378331005: invalid
AU0000XVGZA3: valid
AU0000VXGZA3: valid
FR0000988040: valid</pre>
 
=={{header|D}}==
{{trans|Java}}
Code for the luhn test was taken from [[https://rosettacode.org/wiki/Luhn_test_of_credit_card_numbers#D]]
<syntaxhighlight lang="d">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);
}</syntaxhighlight>
 
{{out}}
<pre>US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid</pre>
 
=={{header|Dart}}==
<syntaxhighlight lang="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)}');
}
}</syntaxhighlight>
{{out}}
<pre>US0378331005 - true
US0373831005 - false
U50378331005 - false
US03378331005 - false
AU0000XVGZA3 - true
AU0000VXGZA3 - true
FR0000988040 - true</pre>
 
=={{header|Delphi}}==
{{works with|Delphi|6.0}}
{{libheader|SysUtils,StdCtrls}}
 
 
<syntaxhighlight lang="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;
 
procedure Compute_ISIN is
begin
SetLength(Result,0);
for I in 1 .. Ada.Command_Line.Argument_Count loop
for I:=1 to Length(S) do
Ada.Text_IO.Put_Line("The Checksum for " &
begin
Ada.Command_Line.Argument(I) & " is " &
if S[I] in ['0'..'9'] then StoreByte(Byte(S[I])-$30)
ISIN.Checksum(Ada.Command_Line.Argument(I)));
else
end loop;
begin
end Compute_ISIN;</lang>
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}
We compute the ISIN-Checksums for Apple, Apple with two digits swapped, the Treasury Corporation of Victoria, and the Treasury Corporation of Victoria with two digits swapped.
Note that the first swap does actually change the checksum, while the second one does not.
I.e., the ISIN checksums don't always discover flaws, such as swapping two adjacent digits.
 
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);
<pre>./compute_isin US037833100 US037383100 AU0000XVGZA AU0000VXGZA
The Checksum for US037833100 is 5
The Checksum for US037383100 is 9
The Checksum for AU0000XVGZA is 3
The Checksum for AU0000VXGZA is 3</pre>
 
function LuhnTest(Nums: array of byte): boolean;
=== Verifying ISINs with given Checksums ===
{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}
Similarily to the above, we check if an ISIN with checksum is valid.
 
type TStringErrors = (seNone,seLength,seCountry);
<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;
 
function ValidateStr(IDStr: string): TStringErrors;
procedure Check_ISIN is
{Validate string checking for incorrectly length}
{And invalid country code}
begin
if Length(IDStr)<>12 then Result:=seLength
for I in 1 .. Ada.Command_Line.Argument_Count loop
else if not (IDStr[1] in ['a'..'z','A'..'Z']) or
if ISIN.Valid(Ada.Command_Line.Argument(I)) then
not (IDStr[2] in ['a'..'z','A'..'Z']) then Result:=seCountry
Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " OK!");
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;
 
 
 
</syntaxhighlight>
{{out}}
<pre>
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.
 
</pre>
 
 
=={{header|EasyLang}}==
{{trans|AWK}}
<syntaxhighlight>
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
Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " ** Fail! **");
end if;.
end loop;.
i = len s[] - 1
end Check_ISIN;</lang>
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"
.
.
</syntaxhighlight>
{{out}}
<pre>
US0378331005 is valid
US0373831005 is invalid
U50378331005 is invalid
US03378331005 is invalid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid
</pre>
 
=={{header|Elixir}}==
We check Apple's ISIN, and two "misspellings" of Apple's ISIN, we got by permuting two digits or letters. The error of permuting "US" to "SU" is not discovered by the algorithm, the error of permuting 83 to 38 is.
used Luhn module from [[Luhn_test_of_credit_card_numbers#Elixir | here]]
<syntaxhighlight lang="elixir">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?"
<pre>./check_isin US0378331005 SU0378331005 US0373831005
~w(US0378331005 OK!
US0373831005
SU0378331005 OK!
U50378331005
US0373831005 ** Fail! **</pre>
US03378331005
AU0000XVGZA3
AU0000VXGZA3
FR0000988040)
|> Enum.each(&IO.puts "#{&1}\t#{isin?.(&1)}")</syntaxhighlight>
 
{{out}}
=={{header|Fortran}}==
<pre>
ISIN Valid?
US0378331005 true
US0373831005 false
U50378331005 false
US03378331005 false
AU0000XVGZA3 true
AU0000VXGZA3 true
FR0000988040 true
</pre>
 
=={{header|Factor}}==
We need some functions equivalent to those in ctype.h in C. Here is a module.
We re-use the <code>luhn?</code> word from ''[[Luhn test of credit card numbers#Factor]]''.
<syntaxhighlight lang="factor">USING: combinators.short-circuit.smart formatting kernel luhn
math math.parser qw sequences strings unicode ;
IN: rosetta-code.isin
 
CONSTANT: test-cases qw{
<lang fortran>module ctype
US0378331005 US0373831005 U50378331005 US03378331005
implicit none
AU0000XVGZA3 AU0000VXGZA3 FR0000988040
contains
}
elemental logical function isupper(c)
character, intent(in) :: c
integer :: n
n = iachar(c)
isupper = n >= 65 .and. n <= 90
end function
elemental logical function islower(c)
character, intent(in) :: c
integer :: n
n = iachar(c)
islower = n >= 97 .and. n <= 122
end function
 
: valid-length? ( str -- ? ) length 12 = ;
elemental logical function isalpha(c)
character, intent(in) :: c
isalpha = isupper(c) .or. islower(c)
end function
 
: valid-country-code? ( str -- ? ) first2 [ Letter? ] both? ;
elemental logical function isdigit(c)
 
character, intent(in) :: c
: valid-security-code? ( str -- ? )
integer :: n
[ 2 11 ] ndip =subseq iachar(c)[ alpha? ] all? ;
isdigit = n >= 48 .and. n <= 57
end function
: valid-checksum-digit? ( str -- ? ) last digit? ;
elemental logical function isalnum(c)
character, intent(in) :: c
: valid-format? ( str -- ? ) {
isalnum = isdigit(c) .or. isalpha(c)
[ valid-length? ]
end function
[ valid-country-code? ]
end module</lang>
[ 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</syntaxhighlight>
{{out}}
<pre>
US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid
</pre>
 
=={{header|Fortran}}==
The main program follows.
 
<langsyntaxhighlight lang="fortran">program isin
use ctype
implicit none
Line 249 ⟶ 1,529:
n = len_trim(a)
if (n /= 12) return
if (.not. isalpha(a(1:1)) .or. .not. isalpha(a(2:2))) return
do i = 3, 12
if (.not. isalnum(a(i:i))) return
end do
! Convert to an array of digits
Line 260 ⟶ 1,535:
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 if (k >= 97 .and. k <= 122) then
k = k - 97 + 10
j = j + 1
s(j) = k / 10
Line 293 ⟶ 1,564:
check_isin = 0 == mod(v, 10)
end function
end program</langsyntaxhighlight>
 
=={{header|GoFreeBASIC}}==
<syntaxhighlight lang="freebasic">' version 27-10-2016
' compile with: fbc -s console
 
#Ifndef TRUE ' define true and false for older freebasic versions
{{update|Go|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.}}
#Define FALSE 0
#Define TRUE Not FALSE
#EndIf
 
Function luhntest(cardnr As String) As Long
<lang go>package main
 
cardnr = Trim(cardnr) ' remove spaces
import (
"fmt"
"regexp"
)
 
Dim As String reverse_nr = cardnr
var numbers = []string{
Dim As Long i, j, s1, s2, l = Len(cardnr) -1
"US0378331005",
 
"US0373831009",
' reverse string
"D56000543287",
"AU0000XVGZA3",For i = 0 To l
reverse_nr[i] = cardnr[l - i]
"AU0000VXGZA3",
"GB0002634946",Next
' sum odd numbers
"US0373831005",
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</syntaxhighlight>
{{out}}
<pre>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</pre>
 
=={{header|Go}}==
 
<syntaxhighlight lang="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 validValidISIN(n string) bool {
if !r.MatchString(n) {
return false
}
}
var sum, p int
for i := 10; i >= 0; i-- {
p = 1 - p
if d := n[i]; d < 'A' {
sum += inc[p][d-'0']
} else {
d -= 'A'
sum += inc[p][d%10]
p = 1 - p
sum += inc[p][d/10+1]
}
}
}
}
return (140- sum)%10 =+= int(n[11]) - '0')
return sum%10 == 0
}
}</syntaxhighlight>
 
<syntaxhighlight lang="go">package main
func main() {
 
for _, n := range numbers {
import "testing"
if valid(n) {
 
fmt.Println(n, "is valid")
func TestValidISIN(t *testing.T) {
} else {
testcases := []struct {
fmt.Println(n, "is not valid")
isin string
}
valid bool
}
}{
}</lang>
{"US0378331005", true},
{{out}}
{"US0373831005", false},
<pre>
{"U50378331005", false},
US0378331005 is valid
{"US03378331005", false},
US0373831009 is valid
{"AU0000XVGZA3", true},
D56000543287 is not valid
{"AU0000VXGZA3", true},
AU0000XVGZA3 is valid
{"FR0000988040", true},
AU0000VXGZA3 is valid
}
GB0002634946 is valid
 
US0373831005 is not valid
for _, testcase := range testcases {
</pre>
actual := ValidISIN(testcase.isin)
if actual != testcase.valid {
t.Errorf("expected %v for %q, got %v",
testcase.valid, testcase.isin, actual)
}
}
}</syntaxhighlight>
 
=={{header|Groovy}}==
Line 366 ⟶ 1,718:
{{update|Groovy|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.}}
 
<langsyntaxhighlight lang="groovy">CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
 
int checksum(String prefix) {
Line 378 ⟶ 1,730:
assert checksum('GB000263494') == 6
assert checksum('US037833100') == 5
assert checksum('US037833107') == 0</langsyntaxhighlight>
 
=={{header|Haskell}}==
<syntaxhighlight lang="haskell">module ISINVerification2 where
 
import Data.Char (isUpper, isDigit, digitToInt)
{{update|Haskell|Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the ''[[Luhn test of credit card numbers]]'' task instead of duplicating it.}}
 
<lang Haskell>import Data.Char ( isUpper , isDigit , digitToInt )
 
verifyISIN :: String -> Bool
verifyISIN isin = (digitToInt $ last isin) == mod (10 - (digitSum `mod` 10)) 10
correctFormat isin && mod (oddsum + multiplied_even_sum) 10 == 0
where
where
firstEleven = take 11 isin
reverted = convertedreverse =$ convertToNumber firstElevenisin
theOdds = fst $ collectOddandEven reverted
multiplied = multiplyDigits converted
theEvens = snd $ collectOddandEven reverted
digitSum = addUpDigits multiplied
oddsum = sum $ map digitToInt theOdds
multiplied_even_sum = addUpDigits $ map ((* 2) . digitToInt) theEvens
 
convertToNumbercapitalLetters :: String -> String
capitalLetters = ['A','B' .. 'Z']
convertToNumber str = concat $ map convert str
where
convert :: Char -> String
convert c = if isDigit c then show $ digitToInt c else show ( fromEnum c - 55 )
 
collectOddandEvennumbers :: String -> (String , String )
numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
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]] )
 
multiplyDigitscorrectFormat :: String -> [Int]Bool
correctFormat isin =
multiplyDigits digits
(length isin == 12) &&
|odd $ length digits = (map ( (* 2) . digitToInt ) $ fst $ collectOddandEven digits) ++
all (`elem` capitalLetters) (take 2 isin) &&
( map digitToInt $ snd $ collectOddandEven digits )
all (\c -> elem c capitalLetters || elem c numbers) (drop 2 $ take 11 isin) &&
|otherwise = (map digitToInt $ fst $ collectOddandEven digits ) ++
elem (last isin) numbers
(map ( (* 2) . digitToInt ) $ snd $ collectOddandEven digits)
 
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 =
addUpDigits list = sum $ map (\d -> if d > 9 then sum $ map digitToInt $ show d else d ) 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"
then putStrLn " valid"
else putStrLn " not valid"
 
main :: IO ( )
main = do
let isinnumbers = ["US0378331005" , "US0373831009" , "D56000543287" , "AU0000XVGZA3" ,
[ "US0378331005"
"AU0000VXGZA3" , "GB0002634946" , "US0373831005"]
, "US0373831005"
mapM_ printSolution isinnumbers</lang>
, "U50378331005"
, "US03378331005"
, "AU0000XVGZA3"
, "AU0000VXGZA3"
, "FR0000988040"
]
mapM_ printSolution isinnumbers</syntaxhighlight>
 
{{out}}
<pre>US0378331005 is valid
US0373831009US0373831005 is not valid
D56000543287U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
GB0002634946FR0000988040 is valid</pre>
US0373831005 is not valid
</pre>
 
Or, making alternative choices from the standard libraries:
=={{header|J}}==
<syntaxhighlight lang="haskell">import Control.Monad ((<=<))
import Data.Bifunctor (first)
import Data.List (foldl') -- '
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
 
-------------------- VALID ISIN STRING -------------------
{{update|J|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.}}
 
validISIN :: String -> Bool
<lang j>splt=: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' i. ' ' -.~ ":
validISIN =
checksum=: 3 : '10| - +/ splt (* 2 1 $~ #) |. splt splt y'
(&&) . isinPattern
<*> luhn . (show <=< stringInts)
 
isinPattern :: String -> Bool
assert 5 = checksum 'US037833100'
isinPattern s =
assert 0 = checksum 'US037833107'
12 == length s
assert 3 = checksum 'AU0000VXGZA'
&& all (`elem` capitals) l
assert 6 = checksum 'GB000263494'</lang>
&& all (`elem` (capitals <> digits)) m
&& head r `elem` digits
where
[l, m, r] = bites s [2, 9, 1]
 
luhn :: String -> Bool
=={{header|Java}}==
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
{{update|Java|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.}}
charMap = M.fromList $ zip (digits <> capitals) [0 ..]
 
stringInts :: String -> [Int]
<lang java>public class ISIN {
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"
]</syntaxhighlight>
{{Out}}
<pre>("US0378331005",True)
("US0373831005",False)
("U50378331005",False)
("US03378331005",False)
("AU0000XVGZA3",True)
("AU0000VXGZA3",True)
("FR0000988040",True)</pre>
 
=={{header|J}}==
 
'''Solution:'''
<syntaxhighlight lang="j">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</syntaxhighlight>
 
'''Required Examples:'''
<syntaxhighlight lang="j"> Tests=: 'US0378331005';'US0373831005';'U50378331005';'US03378331005';'AU0000XVGZA3';'AU0000VXGZA3';'FR0000988040'
validISIN&> Tests
1 0 0 0 1 1 1</syntaxhighlight>
 
=={{header|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.
 
<syntaxhighlight lang="java">public class ISIN {
public static void main(String[] args) {
String[] isins = {"US0378331005", "US0373831009", "D56000543287", "AU0000XVGZA3",
"AU0000VXGZA3US0378331005", "GB0002634946", "US0373831005"};
"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;
 
int checkDigit = Character.digit(isin.charAt(11), 10);
 
StringBuilder sb = new StringBuilder();
for (char c : isin.substring(0, 1112).toCharArray())
sb.append(Character.digit(c, 36));
 
return checkDigit == checkDigitluhnTest(sb.toString());
}
 
static intboolean checkDigitluhnTest(StringBuilderString sbnumber) {
int sums1 = 0, s2 = 0;
intString lenreverse = sbnew StringBuffer(number).reverse().lengthtoString();
for (int i = 10; i <= lenreverse.length(); i++) {
int ordinaldigit = Character.digit(sbreverse.charAt(i - 1), 10);
if//This ((lenis %for 2odd ==digits, 0they && i % 2 == 0) || (len % 2 ==are 1-indexed &&in i % 2 == 1))the {algorithm.
sum +=if (ordinali /% 5) + (2 *== ordinal0) % 10;{
} else { s1 += digit;
} else { // sumAdd +=2 ordinal;* digit for 0-4, add 2 * digit - 9 for 5-9.
s2 += 2 * digit;
if(digit >= 5){
s2 -= 9;
}
}
}
return (10s1 -+ (sums2) % 10)) %== 100;
}
}</langsyntaxhighlight>
 
<pre>US0378331005 is valid
US0373831009US0373831005 is not valid
D56000543287U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
GB0002634946FR0000988040 is valid</pre>
US0373831005 is not valid</pre>
 
=={{header|Perl 6jq}}==
{{works with|jq}}
'''Works with gojq, the Go implementation of jq'''
<syntaxhighlight lang="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:
Using the <tt>luhn-test</tt> function defined at ''[[Luhn test of credit card numbers#Perl 6]]'':
# 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:
{{works with|Rakudo|2016.07}}
type == "string"
<lang perl6>sub is-valid-ISIN (Str $ISIN --> Bool) {
and $ISIN ~~ /test("^ (?<cc>[A..-Z]>**2 <[A..Z0..9-Z])(?<sc>**[0-9A-Z]{9 })(?<cs>[0..-9]> )$/ or return False;")
and (decodeBase36 | luhntest);</syntaxhighlight>
'''The Task'''
my $base10 = $ISIN.comb.map({ :36($_) }).join;
<syntaxhighlight lang="jq">def task:
"US0378331005",
return luhn-test $base10;
"US0373831005",
}</lang>
"U50378331005",
"US03378331005",
"AU0000XVGZA3",
"AU0000VXGZA3",
"FR0000988040"
| . + " => " + (if is_ISIN then "valid" else "invalid" end);
 
task</syntaxhighlight>
Testing:
{{out}}
<pre>
US0378331005 => valid
US0373831005 => invalid
U50378331005 => invalid
US03378331005 => invalid
AU0000XVGZA3 => valid
AU0000VXGZA3 => valid
FR0000988040 => valid
</pre>
 
 
<lang perl6>say "$_ is{' not' unless validate-ISIN $_} valid"
=={{header|Julia}}==
for <US0378331005 US0373831005 U50378331005 US03378331005 AU0000XVGZA3 AU0000VXGZA3 FR0000988040></lang>
<syntaxhighlight lang="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</syntaxhighlight>
 
{{out}}
<pre>US0378331005 pass
US0373831005 fail
U50378331005 fail
US03378331005 fail
AU0000XVGZA3 pass
AU0000VXGZA3 pass
FR0000988040 pass</pre>
 
=={{header|Kotlin}}==
As the Luhn test method is only a few lines, it's reproduced here for convenience:
<syntaxhighlight lang="scala">// 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"}")
}
}</syntaxhighlight>
 
{{out}}
<pre>
US0378331005 is -> valid
US0373831005 is -> not valid
U50378331005 is -> not valid
US03378331005 is -> not valid
AU0000XVGZA3 is -> valid
AU0000VXGZA3 is -> valid
FR0000988040 is -> valid
</pre>
 
=={{header|langur}}==
The luhn test is repeated here for simplicity.
 
<syntaxhighlight lang="langur">val .luhntest = fn(.s) {
val .t = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
val .numbers = s2n .s
val .oddeven = len(.numbers) rem 2
 
for[=0] .i of .numbers {
_for += if .i rem 2 == .oddeven {
.numbers[.i]
} else {
.t[.numbers[.i]+1]
}
} div 10
}
 
val .isintest = fn(.s) {
.s -> re/^[A-Z][A-Z][0-9A-Z]{9}[0-9]$/ and
.luhntest(join s2n .s)
}
 
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)")
}</syntaxhighlight>
 
{{out}}
<pre>AU0000VXGZA3: true
AU0000XVGZA3: true
FR0000988040: true
U50378331005: false
US03378331005: false
US0373831005: false
US0378331005: true</pre>
 
=={{header|Lua}}==
<syntaxhighlight lang="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</syntaxhighlight>
{{out}}
<pre>US0378331005 true
US0373831005 false
US0373831005 false
US03378331005 false
AU0000XVGZA3 true
AU0000VXGZA3 true
FR0000988040 true</pre>
 
=={{header|Mathematica}} / {{header|Wolfram Language}}==
<syntaxhighlight lang="mathematica">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"}</syntaxhighlight>
{{out}}
<pre>{True, False, False, False, True, True, True}</pre>
 
=={{header|Nim}}==
<syntaxhighlight lang="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()}."</syntaxhighlight>
 
{{out}}
<pre>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.</pre>
 
=={{header|Perl}}==
We reuse the <tt>luhn_test()</tt> function from ''[[Luhn test of credit card numbers#Perl]]''.
<syntaxhighlight lang="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);
}</syntaxhighlight>
{{out}}
<pre>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</pre>
 
=={{header|Phix}}==
Note this (slightly better) version of Luhn() has the reverse() inside it, whereas the original did not.
<!--<syntaxhighlight lang="phix">(phixonline)-->
<span style="color: #008080;">with</span> <span style="color: #008080;">javascript_semantics</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">Luhn</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">st</span><span style="color: #0000FF;">)</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">s</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">0</span><span style="color: #0000FF;">,</span> <span style="color: #000000;">d</span>
<span style="color: #000000;">st</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">reverse</span><span style="color: #0000FF;">(</span><span style="color: #000000;">st</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">st</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #000000;">d</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">st</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]-</span><span style="color: #008000;">'0'</span>
<span style="color: #000000;">s</span> <span style="color: #0000FF;">+=</span> <span style="color: #008080;">iff</span><span style="color: #0000FF;">(</span><span style="color: #7060A8;">mod</span><span style="color: #0000FF;">(</span><span style="color: #000000;">i</span><span style="color: #0000FF;">,</span><span style="color: #000000;">2</span><span style="color: #0000FF;">)?</span><span style="color: #000000;">d</span><span style="color: #0000FF;">,</span><span style="color: #000000;">d</span><span style="color: #0000FF;">*</span><span style="color: #000000;">2</span><span style="color: #0000FF;">-(</span><span style="color: #000000;">d</span><span style="color: #0000FF;">></span><span style="color: #000000;">4</span><span style="color: #0000FF;">)*</span><span style="color: #000000;">9</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">return</span> <span style="color: #7060A8;">remainder</span><span style="color: #0000FF;">(</span><span style="color: #000000;">s</span><span style="color: #0000FF;">,</span><span style="color: #000000;">10</span><span style="color: #0000FF;">)=</span><span style="color: #000000;">0</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">function</span> <span style="color: #000000;">valid_ISIN</span><span style="color: #0000FF;">(</span><span style="color: #004080;">string</span> <span style="color: #000000;">st</span><span style="color: #0000FF;">)</span>
<span style="color: #000080;font-style:italic;">-- returns 1 if valid, else 0/2/3/4.
-- (feel free to return 0 instead of 2/3/4)</span>
<span style="color: #008080;">if</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">st</span><span style="color: #0000FF;">)!=</span><span style="color: #000000;">12</span> <span style="color: #008080;">then</span> <span style="color: #008080;">return</span> <span style="color: #000000;">2</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">st</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">to</span> <span style="color: #000000;">1</span> <span style="color: #008080;">by</span> <span style="color: #0000FF;">-</span><span style="color: #000000;">1</span> <span style="color: #008080;">do</span>
<span style="color: #004080;">integer</span> <span style="color: #000000;">ch</span> <span style="color: #0000FF;">=</span> <span style="color: #000000;">st</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">>=</span><span style="color: #008000;">'A'</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">></span><span style="color: #008000;">'Z'</span> <span style="color: #008080;">then</span> <span style="color: #008080;">return</span> <span style="color: #000000;">3</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #000000;">st</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">..</span><span style="color: #000000;">i</span><span style="color: #0000FF;">]</span> <span style="color: #0000FF;">=</span> <span style="color: #7060A8;">sprintf</span><span style="color: #0000FF;">(</span><span style="color: #008000;">"%d"</span><span style="color: #0000FF;">,</span><span style="color: #000000;">ch</span><span style="color: #0000FF;">-</span><span style="color: #000000;">55</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">i</span><span style="color: #0000FF;"><=</span><span style="color: #000000;">2</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">4</span>
<span style="color: #008080;">elsif</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;"><</span><span style="color: #008000;">'0'</span> <span style="color: #008080;">or</span> <span style="color: #000000;">ch</span><span style="color: #0000FF;">></span><span style="color: #008000;">'9'</span> <span style="color: #008080;">then</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">3</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<span style="color: #008080;">return</span> <span style="color: #000000;">Luhn</span><span style="color: #0000FF;">(</span><span style="color: #000000;">st</span><span style="color: #0000FF;">)</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">function</span>
<span style="color: #008080;">constant</span> <span style="color: #000000;">tests</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #008000;">"US0378331005"</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- valid </span>
<span style="color: #008000;">"US0373831005"</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- not valid The transposition typo is caught by the checksum constraint.</span>
<span style="color: #008000;">"U50378331005"</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- not valid The substitution typo is caught by the format constraint.</span>
<span style="color: #008000;">"US03378331005"</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- not valid The duplication typo is caught by the format constraint.</span>
<span style="color: #008000;">"AU0000XVGZA3"</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- valid </span>
<span style="color: #008000;">"AU0000VXGZA3"</span><span style="color: #0000FF;">,</span> <span style="color: #000080;font-style:italic;">-- valid Unfortunately, not all transposition typos are caught by the checksum constraint.</span>
<span style="color: #008000;">"FR0000988040"</span><span style="color: #0000FF;">},</span> <span style="color: #000080;font-style:italic;">-- valid</span>
<span style="color: #000000;">reasons</span> <span style="color: #0000FF;">=</span> <span style="color: #0000FF;">{</span><span style="color: #008000;">"wrong checksum"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"valid"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"wrong length"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"bad char"</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"wrong country"</span><span style="color: #0000FF;">}</span>
<span style="color: #008080;">for</span> <span style="color: #000000;">i</span><span style="color: #0000FF;">=</span><span style="color: #000000;">1</span> <span style="color: #008080;">to</span> <span style="color: #7060A8;">length</span><span style="color: #0000FF;">(</span><span style="color: #000000;">tests</span><span style="color: #0000FF;">)</span> <span style="color: #008080;">do</span>
<span style="color: #7060A8;">printf</span><span style="color: #0000FF;">(</span><span style="color: #000000;">1</span><span style="color: #0000FF;">,</span><span style="color: #008000;">"%s : %s\n"</span><span style="color: #0000FF;">,{</span><span style="color: #000000;">tests</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">],</span><span style="color: #000000;">reasons</span><span style="color: #0000FF;">[</span><span style="color: #000000;">valid_ISIN</span><span style="color: #0000FF;">(</span><span style="color: #000000;">tests</span><span style="color: #0000FF;">[</span><span style="color: #000000;">i</span><span style="color: #0000FF;">])+</span><span style="color: #000000;">1</span><span style="color: #0000FF;">]})</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">for</span>
<!--</syntaxhighlight>-->
{{out}}
<pre>
US0378331005 : valid
US0373831005 : wrong checksum
U50378331005 : wrong country
US03378331005 : wrong length
AU0000XVGZA3 : valid
AU0000VXGZA3 : valid
FR0000988040 : valid
</pre>
 
=={{header|PicoLisp}}==
Using the <tt>luhn</tt> function defined at ''[[Luhn test of credit card numbers#PicoLisp]]'':
<syntaxhighlight lang="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" ) ) )</syntaxhighlight>
{{out}}
<pre>(0 NIL NIL NIL 0 0 0)</pre>
 
=={{header|PowerShell}}==
<syntaxhighlight lang="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
}
</syntaxhighlight>
<syntaxhighlight lang="powershell">
"US0378331005","US0373831005","US0337833103","AU0000XVGZA3","AU0000VXGZA3","FR0000988040" | ForEach-Object {
[PSCustomObject]@{
ISIN = $_
IsValid = Test-ISIN -Number $_
}
}
</syntaxhighlight>
{{Out}}
<pre>
ISIN IsValid
---- -------
US0378331005 True
US0373831005 False
US0337833103 False
AU0000XVGZA3 True
AU0000VXGZA3 True
FR0000988040 True
</pre>
 
=={{header|PureBasic}}==
<syntaxhighlight lang="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()</syntaxhighlight>
{{Out}}
<pre>US0378331005 TRUE
US0373831005 FALSE
U50378331005 FALSE
US03378331005 FALSE
AU0000XVGZA3 TRUE
AU0000VXGZA3 TRUE
FR0000988040 TRUE</pre>
 
=={{header|Python}}==
 
<langsyntaxhighlight lang="python">def check_isin(a):
if len(a) != 12 or not all(c.isalpha() for c in a[:2]) or not all(c.isalnum() for c in a[2:]):
return False
s = "".join(str(int(c, 36)) for c in a)
return 0 == (sum(sum(divmod(2 * (ord(c) - 48), 10)) for c in s[-2::-2]) +
sum(ord(c) - 48 for c in s[-1::-2])) % 10
 
# 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]</syntaxhighlight>
 
=={{header|Quackery}}==
 
<code>luhn</code> is defined at [[Luhn test of credit card numbers#Quackery]].
 
<syntaxhighlight lang="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$
list(map(check_isin, ["US0378331005", "US0373831005", "U50378331005", "US03378331005",
say " is "
"AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"]))
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
</syntaxhighlight>
 
{{out}}
 
<pre>US0378331005 is valid.
# [True, False, False, False, True, True, True]</lang>
US0373831005 is not valid.
U50378331005 is not valid.
US03378331005 is not valid.
AU0000XVGZA3 is valid.
AU0000VXGZA3 is valid.
FR0000988040 is valid.</pre>
 
=={{header|Racket}}==
 
<syntaxhighlight lang="racket">
{{update|Racket|Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the ''[[Luhn test of credit card numbers]]'' task instead of duplicating it.}}
#lang racket
 
;; convert a base36 character (#\0 - #\Z) to its equivalent
<lang racket>#lang racket
;; 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
(define-logger issn)
;; this is a character-by-character substitution not a conversion
(current-logger issn-logger)
;; 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 ((letter-char->digits a-point-integer) c)
(define [isin-test? s]
(call-with-values
(let ([RE (pregexp "^[A-Z]{2}[A-Z0-9]{9}[0-9]{1}$")])
(λ () (quotient/remainder (+ 10 (- (char->integer c) a-point-integer)) 10))
list)) (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"))
(define char->digits
(let ((char-code:A (char->integer #\A))
(char-code:a (char->integer #\a))
(char-code:0 (char->integer #\0)))
(match-lambda
[(? char-whitespace?) null]
[(? char-numeric? c) (list (- (char->integer c) char-code:0))]
[(? char-upper-case? (app (letter-char->digits char-code:A) dd)) dd]
[(? char-lower-case? (app (letter-char->digits char-code:a) dd)) dd])))
 
(map isin-test? test-cases)
(define (string->ISIN-digits s)
;; -> '(#t #f #f #f #t #t #t)
(apply append (map char->digits (string->list s))))
</syntaxhighlight>
 
{{out}}
(define (ISIN-checksum s)
(define (mod-10 n) (modulo n 10))
(define sum
(for/fold ((sum 0))
((d (reverse (string->ISIN-digits s)))
(i (in-naturals)))
(mod-10
(+ sum
(cond
[(odd? i) d]
;; (even? i) henceforth...
[(> d 4) (+ 1 (mod-10 (* d 2)))]
[else (* d 2)])))))
(mod-10 (- 10 sum)))
 
'(#t #f #f #f #t #t #t)
(define check-ISIN
(match-lambda
[(regexp #rx"(...........)(.)"
(list isin (and body (app ISIN-checksum sum)) (app string->number cksum)))
(define good? (= sum cksum))
(log-debug "check-ISIN: ~s. ~s wants check sum ~a. got ~a [~a]"
isin body sum cksum good?)
good?]))
 
=={{header|Raku}}==
(module+ test
(formerly Perl 6)
(require tests/eli-tester)
{{works with|Rakudo|2018.12}}
(test
(char->digits #\A) => '(1 0)
(char->digits #\a) => '(1 0)
(char->digits #\Z) => '(3 5)
(char->digits #\z) => '(3 5)
(char->digits #\0) => '(0)
(char->digits #\9) => '(9)
(char->digits #\space) => '()
(string->ISIN-digits "US037833100") => '(3 0 2 8 0 3 7 8 3 3 1 0 0)
(ISIN-checksum "US037833100") => 5
(ISIN-checksum "US037383100") => 9
(ISIN-checksum "AU0000XVGZA") => 3
(ISIN-checksum "AU0000VXGZA") => 3
(check-ISIN "US0378331005") => #t
(check-ISIN "SU0378331005") => #t
(check-ISIN "US0373831005") => #f))</lang>
 
Using the <tt>luhn-test</tt> function from the ''[[Luhn test of credit card numbers#Raku|Luhn test of credit card numbers]]'' task.
All tests pass.
 
<syntaxhighlight lang="raku" line>my $ISIN = /
=={{header|REXX}}==
^ <[A..Z]>**2 <[A..Z0..9]>**9 <[0..9]> $
<?{ luhn-test $/.comb.map({ :36($_) }).join }>
/;
 
sub luhn-test ($number --> Bool) {
{{update|REXX|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.}}
my @digits = $number.comb.reverse;
my $sum = @digits[0,2...*].sum
+ @digits[1,3...*].map({ |($_ * 2).comb }).sum;
return $sum %% 10;
}
 
# Testing:
<lang rexx>/*REXX program calculates the checksum digit for an International Securities ID number.*/
 
say "$_ is { m/$ISIN/ ?? "valid" !! "not valid"}" for <
US0378331005
US0373831005
U50378331005
US03378331005
AU0000XVGZA3
AU0000VXGZA3
FR0000988040
>;</syntaxhighlight>
 
{{out}}
<pre>
US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid
</pre>
 
=={{header|REXX}}==
<syntaxhighlight lang="rexx">/*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" ,
if z='' then z= "US037833100 US037383100 AU0000XVGZA AU0000VXGZA GB000263494"
'AU0000VXGZA3 FR0000988040' /* [↑] ISINsuse given?the default Thenlist useof default ISINs.*/
do n=1 for words(z); x=word(z, n) /* [↓] process each ofall the specified ISINs. */
if length(x) <do 9n=1 thenfor words(z); x=rightword(z, 9, 0n); y= x /*obtain an ISIN too short?from Thenthe pad withZ zeroes 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.*/
$=$ || pos( substr(x, k, 1), '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
if p==0 then y= /*trigger "not" valid below.*/
else $= $ || p-1 /*convert X string (base 36 ──► dec).*/
end /*k*/ /* [↑] convert alphabetic ──► digits.*/
@.= /*placeholder [↓] constructfor twothe groups"not" ofin digitsmessage.*/
if length(y)\==12 do g=1 for length($); then e@=g // 2 "not" /*e:see if the oddness/evennessISIN ofis theexactly group12 chars. */
if \datatype( @.e=@.e || substrleft($x, g2), 1'U') then @= "not" /* " " " /*" [↑] 1st e=1,2 it'schars odd; e=0, it'scap. evenlet.*/
if \datatype(right(x,1),'W') endthen @= "not" /*g*/ " " " " last char not a /* [↑] // is the remainder in REXX. digit*/
if @=='' then if \luhn($) then @= "not" /* " " " " /* ┌── doublepassed the digits of theLuhn grouptest.*/
@.e=translate say right(@.ex, 24681357930) right(@, 1234567895) "valid" /*display ◄─┘the that containsyea the lastor ISIN dignay message.*/
s=0 end /*n*/ /* [↑] 1st /*initialize the3 sumIFs ofcould've decimalbeen digits.combined*/
exit do m=0 for 2 /*stick [↓]a fork sumin bothit, groups ofwe're ISINall digitsdone. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
do i=1 for length(@.m) /* [↓] sum the ISIN digits for a group*/
Luhn: procedure; parse arg x; s=s + substr(@.m, i, 1) $= 0 /*addget acredit group'scard digitnumber; to thezero $ sum. */
y= reverse( left(0, length(x) // 2)x) /*add leading zero if needed, & reverse*/
end /*i*/
do j=1 to length(y)-1 by 2; _= 2 * substr(y, j+1, 1)
end /*m*/
$= $ + substr(y, j, 1) + left(_, 1) + substr(_, 2 , 1, /* [↑] calculate the checksum for ISIN*/0)
ch=right(10 - s//10, 1) end /*finalj*/ /* [↑] calculation forsum the checksum.odd and even digits.*/
return right($, 1)==0 /*return "1" if number passed Luhn test*/</syntaxhighlight>
say ' check sum is ' ch " for ISIN: " x /*display the checksum for the ISIN. */
{{out|output|text=&nbsp; when using the default inputs:}}
end /*n*/ /*stick a fork in it, we're all done. */</lang>
'''output''' &nbsp; when using the defaults for input:
<pre>
US0378331005 valid
check sum is 5 for ISIN: US037833100
US0373831005 not valid
check sum is 9 for ISIN: US037383100
U50378331005 not valid
check sum is 3 for ISIN: AU0000XVGZA
US03378331005 not valid
check sum is 3 for ISIN: AU0000VXGZA
AU0000XVGZA3 valid
check sum is 6 for ISIN: GB000263494
AU0000VXGZA3 valid
FR0000988040 valid
</pre>
 
=={{header|TclRing}}==
<syntaxhighlight lang="ring">
# Project : Validate International Securities Identification Number
 
decimals(0)
{{update|Tcl|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.}}
 
test = ["US0378331005",
<lang Tcl>package require Tcl 8.6 ;# mostly needed for [assert]. Substitute a simpler one or a NOP if required.</lang>
"US0373831005",
"U50378331005",
"US03378331005",
"AU0000XVGZA3",
"AU0000VXGZA3",
"FR0000988040"]
 
for n = 1 to len(test)
A proc like assert is always good to have around. This one tries to report values used in its expression using subst:
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
</syntaxhighlight>
Output:
<pre>
US0378331005 -> Valid
US0373831005 -> Invalid
U50378331005 -> Invalid
US03378331005 -> Invalid
AU0000XVGZA3 -> Valid
AU0000VXGZA3 -> Valid
FR0000988040 -> Valid
</pre>
 
=={{header|RPL}}==
<lang Tcl>proc assert {expr} { ;# for "static" assertions that throw nice errors
<code>LUHN?</code> is defined at [[Luhn test of credit card numbers#RPL|Luhn test of credit card numbers]]
{{works with|RPL|HP48-R}}
« '''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'''
<span style="color:blue">LUHN?</span>
{ "AD" "AT" "AU" "BE" "CA" "DE" "ES" "FR" "GB" "HK" "IT" "US" "ZW" } <span style="color:grey">@ country codes sample </span>
ROT 1 2 SUB POS AND
'''END'''
» '<span style="color:blue">ISIN?</span>' STO
 
{"US0378331005" "US0373831005" "U50378331005" "US03378331005" "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040"}
1 « <span style="color:blue">ISIN?</span> » DOLIST
{{out}}
<pre>
1: { 1 0 0 0 1 1 1 }
</pre>
 
=={{header|Ruby}}==
Using a pre-existing luhn method:
<syntaxhighlight lang="ruby">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]</syntaxhighlight>
 
=={{header|Rust}}==
 
<syntaxhighlight lang="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);
}
</syntaxhighlight>
 
=={{header|SAS}}==
<syntaxhighlight lang="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;</syntaxhighlight>
 
=={{header|Scala}}==
{{Out}}Best seen running in your browser either by [https://scalafiddle.io/sf/D9ax4Js/0 ScalaFiddle (ES aka JavaScript, non JVM)] or [https://scastie.scala-lang.org/yOymYqoPSEeA7K7rjgn65g Scastie (remote JVM)].
<syntaxhighlight lang="scala">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"))
 
}</syntaxhighlight>
 
=={{header|SQL PL}}==
{{works with|Db2 LUW}} version 9.7 or higher.
With SQL PL:
<syntaxhighlight lang="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 @
</syntaxhighlight>
Output:
<pre>
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
</pre>
 
=={{header|Tcl}}==
<syntaxhighlight lang="tcl">package require Tcl 8.6 ;# mostly needed for [assert]. Substitute a simpler one or a NOP if required.</syntaxhighlight>
A proc like assert is always good to have around. This one tries to report values used in its expression using subst:
<syntaxhighlight lang="tcl">proc assert {expr} { ;# for "static" assertions that throw nice errors
if {![uplevel 1 [list expr $expr]]} {
set msg "{$expr}"
Line 673 ⟶ 3,147:
tailcall throw {ASSERT ERROR} $msg
}
}</langsyntaxhighlight>
 
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:
<syntaxhighlight lang="tcl">namespace eval isin {
 
<lang Tcl>namespace eval isin {
proc _init {} { ;# sets up the map used on every call
variable map
Line 693 ⟶ 3,165:
}
 
# copied from "Luhn test of credit card numbers"
proc cksum {isin} {
# included here for ease of testing, and because it is short
set isin [normalize $isin]
proc luhn digitString {
assert {[string is digit -strict $isin]}
setif digits{[regexp {[split^0-9]} $isindigitString]} {error "not a number"]}
ifset {[llengthsum $digits] % 2} {0
set digits [list 0flip {*}$digits]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]
}
foreachreturn [expr {o($sum e}% $digits10) {== 0}]
incr sum [expr {$o + ($e * 2) % 9}]
}
expr {(10 - ($sum % 10)) % 10}
}
 
proc validate {isin} {
setif isin{![regexp {^[normalizeA-Z]{2}[A-Z0-9]{9}[0-9]$} $isin]} {return false}
regexpluhn {^(.*)(.)$}[normalize $isin -> body sum]
expr {$sum eq [cksum $body]}
}
}</lang>
 
}</syntaxhighlight>
Finally, some tcltests pinched from other examples in this page:
To run the test suite, we use the tcltest framework included with Tcl:
<syntaxhighlight lang="tcl">package require tcltest
 
<lang Tcl>package require tcltest
tcltest::test isin-1 "Test isin validation" -body {
foreach {strisin sumok} {
US037833100US0378331005 5 yes
US037383100US0373831005 9 no
SU037833100U50378331005 5 no
AU0000XVGZAUS03378331005 3 no
AU0000VXGZAAU0000XVGZA3 3 yes
GB000263494AU0000VXGZA3 6 yes
FR0000988040 yes
} {
assertif {[isin::cksum $str]ok} eq $sum}{
assert {![isin::validate $str$sumisin]}
} else {
 
assert {![isin::validate $isin]}
set err [expr {1+int(rand()*8)}] ;# create a random checksum error
set sum [expr {$sum + $err % 10}]
assert {![isin::validate $str$sum]}
}
return ok
} -result ok</syntaxhighlight>
</lang>
 
=={{header|Visual BasicTransact-SQL}}==
 
<syntaxhighlight lang="transact-sql">
{{update|Visual Basic|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.}}
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
{{works with|VB6}}
IF @bValid = 1
<lang vb>
BEGIN
Option Explicit
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
Function MakeIsinCode(Exchange As String, security As String)
DECLARE @strLuhn VarChar(40) = REVERSE(@strTest); -- usage: set once, never changed
Dim numLeadingZeroes As Integer
DECLARE @strS2Values VarChar(10) = '0246813579'; -- constant: maps digits to their S2 summed values
SET @p = 0; -- reset loop counter
numLeadingZeroes = 9 - Len(security)
DECLARE @intValue INT;
DECLARE @intSum INT = 0;
Dim leader As String
-- loop through the reversed string, get the value (even-positioned digits are mapped) and add it to @intSum
WHILE @p < LEN(@strLuhn)
leader = Exchange & String(numLeadingZeroes, "0") & security
BEGIN
SET @p = @p+1;
MakeIsinCode = leader & CStr(IsinCheckDigit(leader))
SET @intValue = CONVERT(INT, SUBSTRING(@strLuhn,@p,1) ) -- value of the digit at position @p in the string
End Function
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
Function IsinCheckDigit(ByVal security As String) As Integer
END
Dim digits As String
</syntaxhighlight>
Testing
Dim i As Integer
<syntaxhighlight lang="transact-sql">
-- Testing. The following tests all pass.
For i = 1 To Len(security)
;WITH ISIN_Tests AS
Dim ch As String
( SELECT 'US0378331005' AS ISIN, 1 Expected
UNION SELECT 'US0373831005',0
ch = UCase(Mid(security, i, 1))
UNION SELECT 'U50378331005',0
UNION SELECT 'US03378331005',0
If ch >= "A" And ch <= "Z" Then
UNION SELECT 'AU0000XVGZA3',1
' A to Z translated to "10", "11", .. "35"
UNION SELECT 'AU0000VXGZA3',1
digits = digits & CStr(Asc(ch) - 55)
UNION SELECT 'FR0000988040',1
ElseIf ch >= "0" And ch <= "9" Then
UNION SELECT '0___garbage',0
digits = digits & ch
UNION SELECT '',0
)
SELECT ISIN, Expected, dbo._ISINCheck(ISIN) AS TestResult FROM ISIN_Tests ORDER BY ISIN
</syntaxhighlight>
 
=={{header|VBScript}}==
<syntaxhighlight lang="vb">' 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 </syntaxhighlight>
{{out}}
<pre>
US0378331005 valid
US0373831005 invalid ??1
U50378331005 invalid ??2
US03378331005 invalid ??3
AU0000XVGZA3 valid
AU0000VXGZA3 valid
FR0000988040 valid
</pre>
 
=={{header|Visual Basic}}==
{{works with|Visual Basic|VB6 Standard}}
Calls LuhnCheckPassed() function described at [[Luhn_test_of_credit_card_numbers#Visual_Basic]]
<syntaxhighlight lang="vb">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</syntaxhighlight>
Test:
<syntaxhighlight lang="vb">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</syntaxhighlight>
 
=={{header|Visual Basic .NET}}==
{{trans|C#}}
<syntaxhighlight lang="vbnet">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
Err.Raisetemp 50001,= ,Asc(c) - Asc("SecurityA"c) must+ contain only letters and digits"10
End If
Next Return temp
End Function
 
Dim total As Integer
Function LuhnTest(number As String) As Boolean
Dim tmp As Integer
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
totalEnd = 0Function
 
Function Digitize(isin As String) As String
'If rightmost even, "other" digits for doubling are 2,4,6. If rightmost odd, they're 1,3,5.
Return String.Join("", isin.Select(Function(c) $"{DigitValue(c)}"))
'rightmost digit is always doubled, so start with it and work backwards
DimEnd other As BooleanFunction
 
other = True
Function IsValidIsin(isin As String) As Boolean
Return IsinRegex.IsMatch(isin) AndAlso LuhnTest(Digitize(isin))
For i = Len(digits) To 1 Step -1
End Function
tmp = CInt(Mid(digits, i, 1))
 
Sub If other ThenMain()
Dim isins() = If tmp < 5 Then{
' 0 to 4 map to 0"US0378331005",2,4,6,8
total = total + (tmp * 2)"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} 5is tonot 9valid", map to 1,3,5,7,9isin)
total = total + ((tmp * 2) - 9)
End If
ElseNext
End Sub
total = total + tmp
End If
'Toggle doubling flag
other = Not other
Next
'Last Mod 10 is to wrap 10 to zero
IsinCheckDigit = (10 - (total Mod 10)) Mod 10
End Function
</lang>
 
End Module</syntaxhighlight>
=={{header|zkl}}==
{{out}}
<pre>US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid</pre>
 
=={{header|V (Vlang)}}==
{{update|zkl|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.}}
{{trans|go}}
<syntaxhighlight lang="v (vlang)">import regex
 
const (
{{trans|Groovy}}
inc = [
<lang zkl>const CHARS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
[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 {
fcn checksum(prefix){
mut r,_,_ := regex.regex_base('^[A-Z]{2}[A-Z0-9]{9}\d$')
digits:=prefix.toUpper().reduce(fcn(s,it){ s + CHARS.index(it) },"");
if !r.matches_string(n) {
groups:=digits.split("").reduce(fcn(acc,i){ T(acc[1],acc[0] + i) },T(T,T));
return false
ds:=groups[1].reduce(fcn(s,it){ s + 2*it },"").split("").extend(groups[0]);
}
(10 - ds.sum(0) % 10) % 10
mut sum := 0
}</lang>
mut p := 0
<lang zkl>foreach prefix in (T("AU0000VXGZA","GB000263494","US037833100","US037833107")){
for i := 10; i >= 0; i-- {
println(prefix," --> ",checksum(prefix));
p = 1 - p
}</lang>
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")
}
}
}</syntaxhighlight>
{{out}}
<pre>expected true for US0378331005, got false
expected true for AU0000XVGZA3, got false
expected true for AU0000VXGZA3, got false
expected true for FR0000988040, got false
</pre>
 
=={{header|Wren}}==
{{libheader|Wren-str}}
{{libheader|Wren-iterate}}
{{libheader|Wren-fmt}}
The Luhn test method is reproduced here for convenience.
<syntaxhighlight lang="wren">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)")
}</syntaxhighlight>
 
{{out}}
<pre>
US0378331005 -> valid
US0373831005 -> not valid
U50378331005 -> not valid
US03378331005 -> not valid
AU0000XVGZA3 -> valid
AU0000VXGZA3 -> valid
FR0000988040 -> valid
</pre>
 
=={{header|XPL0}}==
<syntaxhighlight lang="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);
];
]</syntaxhighlight>
 
{{out}}
<pre>
US0378331005 is valid
US0373831005 is not valid
U50378331005 is not valid
US03378331005 is not valid
AU0000XVGZA3 is valid
AU0000VXGZA3 is valid
FR0000988040 is valid
</pre>
 
=={{header|Yabasic}}==
{{trans|FreeBASIC}}
<syntaxhighlight lang="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
</syntaxhighlight>
 
=={{header|zkl}}==
Uses the luhn test from [[Luhn_test_of_credit_card_numbers#zkl]] (copied here as it is short).
<syntaxhighlight lang="zkl">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)
}</syntaxhighlight>
<syntaxhighlight lang="zkl">println(" ISIN Valid?");
foreach isin in (T("US0378331005","US0373831005","U50378331005",
"US03378331005","AU0000XVGZA3","AU0000VXGZA3","FR0000988040")){
println(isin," --> ",validateISIN(isin));
}</syntaxhighlight>
{{out}}
<pre>
ISIN Valid?
AU0000VXGZA --> 3
GB000263494US0378331005 --> 6True
US037833100US0373831005 --> 5False
US037833107U50378331005 --> 0False
US03378331005 --> False
AU0000XVGZA3 --> True
AU0000VXGZA3 --> True
FR0000988040 --> True
</pre>
889

edits