Validate International Securities Identification Number: Difference between revisions
Added XPL0 example. |
Alextretyak (talk | contribs) Added 11l |
||
Line 63: | Line 63: | ||
<br> |
<br> |
||
<hr> |
<hr> |
||
=={{header|11l}}== |
|||
{{trans|Python}} |
|||
<lang 11l>F check_isin(a) |
|||
I a.len != 12 |
|||
R 0B |
|||
[Int] s |
|||
L(c) a |
|||
I c.is_digit() |
|||
I L.index < 2 |
|||
R 0B |
|||
s.append(c.code - 48) |
|||
E I c.is_uppercase() |
|||
I L.index == 11 |
|||
R 0B |
|||
V (d, m) = divmod(c.code - 55, 10) |
|||
s [+]= [d, m] |
|||
E |
|||
R 0B |
|||
V v = sum(s[((len)-1..0).step(-2)]) |
|||
L(=k) s[((len)-2..).step(-2)] |
|||
k = 2 * k |
|||
v += I k > 9 {k - 9} E k |
|||
R v % 10 == 0 |
|||
print([‘US0378331005’, ‘US0373831005’, ‘U50378331005’, ‘US03378331005’, |
|||
‘AU0000XVGZA3’, ‘AU0000VXGZA3’, ‘FR0000988040’].map(s -> check_isin(s)))</lang> |
|||
{{out}} |
|||
<pre> |
|||
[1B, 0B, 0B, 0B, 1B, 1B, 1B] |
|||
</pre> |
|||
=={{header|360 Assembly}}== |
=={{header|360 Assembly}}== |
Revision as of 23:51, 17 April 2021
You are encouraged to solve this task according to the task description, using any language you may know.
An International Securities Identification Number (ISIN) is a unique international identifier for a financial security such as a stock or bond.
Write a function or program that takes a string as input, and checks whether it is a valid ISIN.
It is only valid if it has the correct format, and the embedded checksum is correct.
Demonstrate that your code passes the test-cases listed below.
The format of an ISIN is as follows:
For this task, you may assume that any 2-character alphabetic sequence is a valid country code.
The checksum can be validated as follows:
- Replace letters with digits, by converting each character from base 36 to base 10, e.g.
AU0000XVGZA3
→1030000033311635103
. - Perform the Luhn test on this base-10 number.
There is a separate task for this test: Luhn test of credit card numbers.
You don't have to replicate the implementation of this test here – you can just call the existing function from that task. (Add a comment stating if you did this.)
ISIN | Validity | Comment |
---|---|---|
US0378331005 | valid | |
US0373831005 | not valid | The transposition typo is caught by the checksum constraint. |
U50378331005 | not valid | The substitution typo is caught by the format constraint. |
US03378331005 | not valid | The duplication typo is caught by the format constraint. |
AU0000XVGZA3 | valid | |
AU0000VXGZA3 | valid | Unfortunately, not all transposition typos are caught by the checksum constraint. |
FR0000988040 | valid |
(The comments are just informational. Your function should simply return a Boolean result. See #Raku for a reference solution.)
Useful resources:
- Interactive online ISIN validator
- Wikipedia article: International Securities Identification Number
Related tasks:
11l
<lang 11l>F check_isin(a)
I a.len != 12 R 0B [Int] s L(c) a I c.is_digit() I L.index < 2 R 0B s.append(c.code - 48) E I c.is_uppercase() I L.index == 11 R 0B V (d, m) = divmod(c.code - 55, 10) s [+]= [d, m] E R 0B V v = sum(s[((len)-1..0).step(-2)]) L(=k) s[((len)-2..).step(-2)] k = 2 * k v += I k > 9 {k - 9} E k R v % 10 == 0
print([‘US0378331005’, ‘US0373831005’, ‘U50378331005’, ‘US03378331005’,
‘AU0000XVGZA3’, ‘AU0000VXGZA3’, ‘FR0000988040’].map(s -> check_isin(s)))</lang>
- Output:
[1B, 0B, 0B, 0B, 1B, 1B, 1B]
360 Assembly
<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</lang>
- Output:
1 US0378331005 OK 2 US0373831005 ?err1 3 U50378331005 ?err2 4 US03378331005 ?err3 5 AU0000XVGZA3 OK 6 AU0000VXGZA3 OK 7 FR0000988040 OK
Ada
Calling the existing Luhn algorithm implementation from the Luhn test of credit card numbers task.
<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 -- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ... Offset: constant Integer := Character'Pos('A')-10; Invalid_Character: exception; begin if S = "" then return ""; elsif S(S'First) = ' ' then -- skip blanks return To_Digits(S(S'First+1 .. S'Last)); elsif S(S'First) in Decimal then return S(S'First) & To_Digits(S(S'First+1 .. S'Last)); elsif S(S'First) in Letter then return To_Digits(Integer'Image(Character'Pos(S(S'First))-Offset)) & To_Digits(S(S'First+1 .. S'Last)); else raise Invalid_Character; end if; end To_Digits; function Is_Valid_ISIN(S: ISIN_Type) return Boolean is Number : String := To_Digits(S); begin return S(S'First) in Letter and S(S'First+1) in Letter and S(S'Last) in Decimal and Luhn_Test(Number); end Is_Valid_ISIN;
Test_Cases : constant Array(1..6) of ISIN_Type := ("US0378331005", "US0373831005", "U50378331005", -- excluded by type with fixed length -- "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040");
begin
for I in Test_Cases'Range loop Ada.Text_IO.Put_Line(Test_Cases(I) & ":" & Boolean'Image(Is_Valid_ISIN(Test_Cases(I)))); end loop; -- using wrong length will result in an exception: Ada.Text_IO.Put("US03378331005:"); Ada.Text_IO.Put_Line(Boolean'Image(Is_Valid_Isin("US03378331005")));
exception
when others => Ada.Text_IO.Put_Line("Exception occured");
end ISIN;</lang>
Output:
US0378331005:TRUE US0373831005:FALSE U50378331005:FALSE AU0000XVGZA3:TRUE AU0000VXGZA3:TRUE FR0000988040:TRUE US03378331005:Exception occured
ALGOL W
Uses the LuhnTest procedure from the Luhn test of credit card numbers task. <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.</lang>
- Output:
US0378331005 is valid US0373831005 is invalid U50378331005 is invalid US03378331005 is invalid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
AppleScript
This script calls a handler posted for the Luhn test of credit card numbers task.
<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</lang>
- Output:
<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}}</lang>
AWK
<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)
} </lang>
- Output:
1 US0378331005 0 US0373831005 0 U50378331005 0 US03378331005 1 AU0000XVGZA3 1 AU0000VXGZA3 1 FR0000988040
C
<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 */</lang>
C#
{ <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}"); } } }
}</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
C++
<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; } </lang>
Caché ObjectScript
<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) }
}</lang>
- Examples:
USER>For { Read isin Quit:isin="" Write ": "_##class(Utils.Check).ISIN(isin), ! } US0378331005: 1 US0373831005: 0 U50378331005: 0 US03378331005: 0 AU0000XVGZA3: 1 AU0000VXGZA3: 1 FR0000988040: 1 USER>
Clojure
<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)))
</lang> luhn? is based on Luhn test of credit card numbers#Clojure.
- Output:
US0378331005: valid US0373831005: invalid U50378331005: invalid US03378331005: invalid AU0000XVGZA3: valid AU0000VXGZA3: valid FR0000988040: valid
COBOL
<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.</lang>
- Output:
prompt$ cobc -xj ISINTest.cbl should be valid US0378331005 30280378331005 027 050 is valid should not be valid US0373831005 30280373831005 022 046 luhn test failed should not be valid U50378331005 invalid countrycode should not be valid US03378331005 invalid length should be valid AU0000XVGZA3 1030000033311635103 018 030 is valid should be valid AU0000VXGZA3 1030000031331635103 018 030 is valid should be valid FR0000988040 15270000988040 020 050 is valid
Common Lisp
<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))))</lang>
- Output:
US0378331005: valid US0373831005: invalid U50378331005: invalid US03378331005: invalid AU0000XVGZA3: valid AU0000VXGZA3: valid FR0000988040: valid
D
Code for the luhn test was taken from [[1]] <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);
}</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Elixir
used Luhn module from here <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?" ~w(US0378331005
US0373831005 U50378331005 US03378331005 AU0000XVGZA3 AU0000VXGZA3 FR0000988040)
|> Enum.each(&IO.puts "#{&1}\t#{isin?.(&1)}")</lang>
- Output:
ISIN Valid? US0378331005 true US0373831005 false U50378331005 false US03378331005 false AU0000XVGZA3 true AU0000VXGZA3 true FR0000988040 true
Factor
We re-use the luhn?
word from Luhn test of credit card numbers#Factor.
<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{
US0378331005 US0373831005 U50378331005 US03378331005 AU0000XVGZA3 AU0000VXGZA3 FR0000988040
}
- valid-length? ( str -- ? ) length 12 = ;
- valid-country-code? ( str -- ? ) first2 [ Letter? ] both? ;
- valid-security-code? ( str -- ? )
[ 2 11 ] dip subseq [ alpha? ] all? ;
- valid-checksum-digit? ( str -- ? ) last digit? ;
- valid-format? ( str -- ? ) {
[ valid-length? ] [ valid-country-code? ] [ valid-security-code? ] [ valid-checksum-digit? ] } && ;
- base36>base10 ( str -- n )
>upper [ dup LETTER? [ 55 - number>string ] [ 1string ] if ] { } map-as concat string>number ;
- isin? ( str -- ? )
{ [ valid-format? ] [ base36>base10 luhn? ] } && ;
- main ( -- )
test-cases [ dup isin? "" " not" ? "%s is%s valid\n" printf ] each ;
MAIN: main</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Fortran
<lang fortran>program isin
use ctype implicit none character(20) :: test(7) = ["US0378331005 ", & "US0373831005 ", & "U50378331005 ", & "US03378331005 ", & "AU0000XVGZA3 ", & "AU0000VXGZA3 ", & "FR0000988040 "] print *, check_isin(test)
contains
elemental logical function check_isin(a) character(*), intent(in) :: a integer :: s(24) integer :: i, j, k, n, v
check_isin = .false.
n = len_trim(a) if (n /= 12) return ! Convert to an array of digits j = 0 do i = 1, n k = iachar(a(i:i)) if (k >= 48 .and. k <= 57) then if (i < 3) return k = k - 48 j = j + 1 s(j) = k else if (k >= 65 .and. k <= 90) then if (i == 12) return k = k - 65 + 10 j = j + 1 s(j) = k / 10 j = j + 1 s(j) = mod(k, 10) else return end if end do
! Compute checksum v = 0 do i = j - 1, 1, -2 k = 2 * s(i) if (k > 9) k = k - 9 v = v + k end do do i = j, 1, -2 v = v + s(i) end do check_isin = 0 == mod(v, 10) end function
end program</lang>
FreeBASIC
<lang freebasic>' version 27-10-2016 ' compile with: fbc -s console
- Ifndef TRUE ' define true and false for older freebasic versions
#Define FALSE 0 #Define TRUE Not FALSE
- EndIf
Function luhntest(cardnr As String) As Long
cardnr = Trim(cardnr) ' remove spaces
Dim As String reverse_nr = cardnr Dim As Long i, j, s1, s2, l = Len(cardnr) -1
' reverse string For i = 0 To l reverse_nr[i] = cardnr[l - i] Next ' sum odd numbers For i = 0 To l Step 2 s1 = s1 + (reverse_nr[i] - Asc("0")) Next ' sum even numbers For i = 1 To l Step 2 j = reverse_nr[i] - Asc("0") j = j * 2 If j > 9 Then j = j Mod 10 +1 s2 = s2 + j Next
If (s1 + s2) Mod 10 = 0 Then Return TRUE Else Return FALSE End If
End Function
' ------=< MAIN >=-----
Dim As String test_str Dim As String test_set(1 To ...) = { "US0378331005", "US0373831005", _
"U50378331005", "US03378331005", "AU0000XVGZA3", _ "AU0000VXGZA3", "FR0000988040" }
Dim As Long i, l, n, x
For i = 1 To UBound(test_set)
test_str = "" l = Len(test_set(i)) If l <> 12 Then Print test_set(i), "Invalid, length <> 12 char." Continue For End If If test_set(i)[0] < Asc("A") Or test_set(i)[1] < Asc("A") Then Print test_set(i), "Invalid, number needs to start with 2 characters" Continue For End If For n = 0 To l -1 x = test_set(i)[n] - Asc("0") ' if test_set(i)[i] is a letter we to correct for that If x > 9 Then x = x -7 If x < 10 Then test_str = test_str + Str(x) Else ' two digest number test_str = test_str + Str(x \ 10) + Str(x Mod 10) End If Next Print test_set(i), IIf(luhntest(test_str) = TRUE, "Valid","Invalid, checksum error")
Next
' empty keyboard buffer
While InKey <> "" : Wend
Print : Print "hit any key to end program"
Sleep
End</lang>
- Output:
US0378331005 Valid US0373831005 Invalid, checksum error U50378331005 Invalid, number needs to start with 2 characters US03378331005 Invalid, length <> 12 char. AU0000XVGZA3 Valid AU0000VXGZA3 Valid FR0000988040 Valid
Go
<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 ValidISIN(n string) bool { if !r.MatchString(n) { return false } var sum, p int for i := 10; i >= 0; i-- { p = 1 - p if d := n[i]; d < 'A' { sum += inc[p][d-'0'] } else { d -= 'A' sum += inc[p][d%10] p = 1 - p sum += inc[p][d/10+1] } } sum += int(n[11] - '0') return sum%10 == 0 }</lang>
<lang go>package main
import "testing"
func TestValidISIN(t *testing.T) { testcases := []struct { isin string valid bool }{ {"US0378331005", true}, {"US0373831005", false}, {"U50378331005", false}, {"US03378331005", false}, {"AU0000XVGZA3", true}, {"AU0000VXGZA3", true}, {"FR0000988040", true}, }
for _, testcase := range testcases { actual := ValidISIN(testcase.isin) if actual != testcase.valid { t.Errorf("expected %v for %q, got %v", testcase.valid, testcase.isin, actual) } } }</lang>
Groovy
<lang groovy>CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
int checksum(String prefix) {
def digits = prefix.toUpperCase().collect { CHARS.indexOf(it).toString() }.sum() def groups = digits.collect { CHARS.indexOf(it) }.inject([[], []]) { acc, i -> [acc[1], acc[0] + i] } def ds = groups[1].collect { (2 * it).toString() }.sum().collect { CHARS.indexOf(it) } + groups[0] (10 - ds.sum() % 10) % 10
}
assert checksum('AU0000VXGZA') == 3 assert checksum('GB000263494') == 6 assert checksum('US037833100') == 5 assert checksum('US037833107') == 0</lang>
Haskell
<lang Haskell>module ISINVerification2 where
import Data.Char (isUpper, isDigit, digitToInt)
verifyISIN :: String -> Bool verifyISIN isin =
correctFormat isin && mod (oddsum + multiplied_even_sum) 10 == 0 where reverted = reverse $ convertToNumber isin theOdds = fst $ collectOddandEven reverted theEvens = snd $ collectOddandEven reverted oddsum = sum $ map digitToInt theOdds multiplied_even_sum = addUpDigits $ map ((* 2) . digitToInt) theEvens
capitalLetters :: String capitalLetters = ['A','B' .. 'Z']
numbers :: String numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
correctFormat :: String -> Bool correctFormat isin =
(length isin == 12) && all (`elem` capitalLetters) (take 2 isin) && all (\c -> elem c capitalLetters || elem c numbers) (drop 2 $ take 11 isin) && elem (last isin) numbers
convertToNumber :: String -> String convertToNumber = concatMap convert
where convert :: Char -> String convert c = if isDigit c then show $ digitToInt c else show (fromEnum c - 55)
collectOddandEven :: String -> (String, String) collectOddandEven term
| odd $ length term = ( concat [ take 1 $ drop n term | n <- [0,2 .. length term - 1] ] , concat [ take 1 $ drop d term | d <- [1,3 .. length term - 2] ]) | otherwise = ( concat [ take 1 $ drop n term | n <- [0,2 .. length term - 2] ] , concat [ take 1 $ drop d term | d <- [1,3 .. length term - 1] ])
addUpDigits :: [Int] -> Int addUpDigits list =
sum $ map (\d -> if d > 9 then sum $ map digitToInt $ show d else d) list
printSolution :: String -> IO () printSolution str = do
putStr $ str ++ " is" if verifyISIN str then putStrLn " valid" else putStrLn " not valid"
main :: IO () main = do
let isinnumbers = [ "US0378331005" , "US0373831005" , "U50378331005" , "US03378331005" , "AU0000XVGZA3" , "AU0000VXGZA3" , "FR0000988040" ] mapM_ printSolution isinnumbers</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Or, making alternative choices from the standard libraries: <lang haskell>import qualified Data.Map as M import Data.Bifunctor (first) import Data.Maybe (fromMaybe)
validISIN, isinPattern, luhn :: String -> Bool validISIN = (&&) . isinPattern <*> (luhn . (show =<<) . stringInts)
isinPattern s =
12 == length s && all (`elem` capitals) l && all (`elem` (capitals ++ digits)) m && head r `elem` digits where [l, m, r] = bites [2, 9, 1] s
luhn x = 0 == rem (s1 + s2) 10
where odds = [(: []), const []] evens = reverse odds stream f = concat $ zipWith ($) (cycle f) (stringInts $ reverse x) s1 = sum (stream odds) s2 = sum $ sum . stringInts . show . (2 *) <$> stream evens
charMap :: M.Map Char Int charMap = M.fromList $ zip (digits ++ capitals) [0 ..]
stringInts :: String -> [Int] stringInts = fromMaybe [] . traverse (`M.lookup` charMap)
bites :: [Int] -> [a] -> a bites ns xs =
(reverse . fst) $ foldr (\x (a, r) -> first (: a) (splitAt x r)) ([], xs) (reverse ns)
capitals, digits :: String capitals = ['A' .. 'Z']
digits = ['0' .. '9']
main :: IO () main =
mapM_ (print . ((,) <*> validISIN)) [ "US0378331005" , "US0373831005" , "U50378331005" , "US03378331005" , "AU0000XVGZA3" , "AU0000VXGZA3" , "FR0000988040" ]</lang>
- Output:
("US0378331005",True) ("US0373831005",False) ("U50378331005",False) ("US03378331005",False) ("AU0000XVGZA3",True) ("AU0000VXGZA3",True) ("FR0000988040",True)
J
Solution: <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</lang>
Required Examples: <lang j> Tests=: 'US0378331005';'US0373831005';'U50378331005';'US03378331005';'AU0000XVGZA3';'AU0000VXGZA3';'FR0000988040'
validISIN&> Tests
1 0 0 0 1 1 1</lang>
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.
<lang java>public class ISIN {
public static void main(String[] args) { String[] isins = { "US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040" }; for (String isin : isins) System.out.printf("%s is %s\n", isin, ISINtest(isin) ? "valid" : "not valid"); } static boolean ISINtest(String isin) { isin = isin.trim().toUpperCase(); if (!isin.matches("^[A-Z]{2}[A-Z0-9]{9}\\d$")) return false; StringBuilder sb = new StringBuilder(); for (char c : isin.substring(0, 12).toCharArray()) sb.append(Character.digit(c, 36)); return luhnTest(sb.toString()); }
static boolean luhnTest(String number) { int s1 = 0, s2 = 0; String reverse = new StringBuffer(number).reverse().toString(); for (int i = 0; i < reverse.length(); i++){ int digit = Character.digit(reverse.charAt(i), 10); //This is for odd digits, they are 1-indexed in the algorithm. if (i % 2 == 0){ s1 += digit; } else { // Add 2 * digit for 0-4, add 2 * digit - 9 for 5-9. s2 += 2 * digit; if(digit >= 5){ s2 -= 9; } } } return (s1 + s2) % 10 == 0; }
}</lang>
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Julia
<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</lang>
- Output:
US0378331005 pass US0373831005 fail U50378331005 fail US03378331005 fail AU0000XVGZA3 pass AU0000VXGZA3 pass FR0000988040 pass
Kotlin
As the Luhn test method is only a few lines, it's reproduced here for convenience: <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"}") }
}</lang>
- Output:
US0378331005 -> valid US0373831005 -> not valid U50378331005 -> not valid US03378331005 -> not valid AU0000XVGZA3 -> valid AU0000VXGZA3 -> valid FR0000988040 -> valid
langur
The luhn test is repeated here for simplicity (from Luhn_test_of_credit_card_numbers#langur).
<lang langur>val .luhntest = f(.s) {
val .t = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9] val .numbers = s2n .s val .oddeven = len(.numbers) rem 2
for[=0] .i of .numbers { _for += if(.i rem 2 == .oddeven: .numbers[.i]; .t[.numbers[.i]+1]) } div 10
}
val .isintest = f(.s) {
matching(re/^[A-Z][A-Z][0-9A-Z]{9}[0-9]$/, .s) and .luhntest(join s2n .s)
}
val .tests = h{
"US0378331005": true, "US0373831005": false, "U50378331005": false, "AU0000XVGZA3": true, "AU0000VXGZA3": true, "FR0000988040": true, "US03378331005": false,
}
for .key in sort(keys .tests) {
val .pass = .isintest(.key) write .key, ": ", .pass writeln if(.pass == .tests[.key]: ""; " (ISIN TEST FAILED)")
}</lang>
- Output:
AU0000VXGZA3: true AU0000XVGZA3: true FR0000988040: true U50378331005: false US03378331005: false US0373831005: false US0378331005: true
Lua
<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</lang>
- Output:
US0378331005 true US0373831005 false US0373831005 false US03378331005 false AU0000XVGZA3 true AU0000VXGZA3 true FR0000988040 true
Perl
We reuse the luhn_test() function from Luhn test of credit card numbers#Perl. <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);
}</lang>
- Output:
1..7 ok 1 - Test 1 ok 2 - Test 2 ok 3 - Test 3 ok 4 - Test 4 ok 5 - Test 5 ok 6 - Test 6 ok 7 - Test 7
Phix
Note this (slightly better) version of Luhn() has the reverse() inside it, whereas the original did not. <lang Phix>function Luhn(string st) integer s=0, d
st = reverse(st) for i=1 to length(st) do d = st[i]-'0' s += iff(mod(i,2)?d,d*2-(d>4)*9) end for return remainder(s,10)=0
end function
function valid_ISIN(string st) -- returns 1 if valid, else 0/2/3/4. -- (feel free to return 0 instead of 2/3/4)
if length(st)!=12 then return 2 end if for i=length(st) to 1 by -1 do integer ch = st[i] if ch>='A' then if ch>'Z' then return 3 end if st[i..i] = sprintf("%d",ch-55) elsif i<=2 then return 4 elsif ch<'0' or ch>'9' then return 3 end if end for return Luhn(st)
end function
sequence tests = {"US0378331005", -- valid
"US0373831005", -- not valid The transposition typo is caught by the checksum constraint. "U50378331005", -- not valid The substitution typo is caught by the format constraint. "US03378331005", -- not valid The duplication typo is caught by the format constraint. "AU0000XVGZA3", -- valid "AU0000VXGZA3", -- valid Unfortunately, not all transposition typos are caught by the checksum constraint. "FR0000988040"} -- valid
constant reasons = {"wrong checksum","valid","wrong length","bad char","wrong country"}
for i=1 to length(tests) do
string ti = tests[i] printf(1,"%s : %s\n",{ti,reasons[valid_ISIN(ti)+1]})
end for</lang>
- Output:
US0378331005 : valid US0373831005 : wrong checksum U50378331005 : wrong country US03378331005 : wrong length AU0000XVGZA3 : valid AU0000VXGZA3 : valid FR0000988040 : valid
PicoLisp
Using the luhn function defined at Luhn test of credit card numbers#PicoLisp: <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" ) ) )</lang>
- Output:
(0 NIL NIL NIL 0 0 0)
PowerShell
<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
} </lang> <lang PowerShell> "US0378331005","US0373831005","US0337833103","AU0000XVGZA3","AU0000VXGZA3","FR0000988040" | ForEach-Object {
[PSCustomObject]@{ ISIN = $_ IsValid = Test-ISIN -Number $_ }
} </lang>
- Output:
ISIN IsValid ---- ------- US0378331005 True US0373831005 False US0337833103 False AU0000XVGZA3 True AU0000VXGZA3 True FR0000988040 True
PureBasic
<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()</lang>
- Output:
US0378331005 TRUE US0373831005 FALSE U50378331005 FALSE US03378331005 FALSE AU0000XVGZA3 TRUE AU0000VXGZA3 TRUE FR0000988040 TRUE
Python
<lang python>def check_isin(a):
if len(a) != 12 or not all(c.isalpha() for c in a[:2]) or not all(c.isalnum() for c in a[2:]): return False s = "".join(str(int(c, 36)) for c in a) return 0 == (sum(sum(divmod(2 * (ord(c) - 48), 10)) for c in s[-2::-2]) + sum(ord(c) - 48 for c in s[::-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]</lang>
Racket
<lang racket>
- lang racket
- convert a base36 character (#\0 - #\Z) to its equivalent
- in base 10 as a string ("0" - "35")
(define (base36-char->base10-string c)
(let ([char-int (char->integer (char-upcase c))] [zero-int (char->integer #\0)] [nine-int (char->integer #\9)] [A-int (char->integer #\A)] [Z-int (char->integer #\Z)]) (cond [(and (>= char-int zero-int) (<= char-int nine-int)) (~a c)] [(and (>= char-int A-int) (<= char-int Z-int)) (~a (+ (- char-int A-int) 10))] [else null])))
- substitute equivalent base 10 numbers for base 36 characters in string
- this is a character-by-character substitution not a conversion
- of a base36 number to a base10 number
(define (base36-string-characters->base10-string-characters s)
(for/fold ([joined ""]) ([tenstr (map base36-char->base10-string (string->list (string-upcase s)))]) (values (string-append joined tenstr))))
- This uses the Racket Luhn solution
(define [isin-test? s]
(let ([RE (pregexp "^[A-Z]{2}[A-Z0-9]{9}[0-9]{1}$")]) (and (regexp-match? RE s) (luhn-test (string->number (base36-string-characters->base10-string-characters s))))))
(define test-cases '("US0378331005" "US0373831005" "U50378331005" "US03378331005" "AU0000XVGZA3" "AU0000VXGZA3" "FR0000988040"))
(map isin-test? test-cases)
- -> '(#t #f #f #f #t #t #t)
</lang>
- Output:
'(#t #f #f #f #t #t #t)
Raku
(formerly Perl 6)
Using the luhn-test function from the Luhn test of credit card numbers task.
<lang perl6>my $ISIN = /
^ <[A..Z]>**2 <[A..Z0..9]>**9 <[0..9]> $ <?{ luhn-test $/.comb.map({ :36($_) }).join }>
/;
sub luhn-test ($number --> Bool) {
my @digits = $number.comb.reverse; my $sum = @digits[0,2...*].sum + @digits[1,3...*].map({ |($_ * 2).comb }).sum; return $sum %% 10;
}
- Testing:
say "$_ is { m/$ISIN/ ?? "valid" !! "not valid"}" for < US0378331005 US0373831005 U50378331005 US03378331005 AU0000XVGZA3 AU0000VXGZA3 FR0000988040 >;</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
REXX
<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" ,
'AU0000VXGZA3 FR0000988040' /* [↑] use the default list of ISINs.*/ /* [↓] process all specified ISINs.*/ do n=1 for words(z); x=word(z, n); y= x /*obtain an ISIN from the Z list. */ $= /* [↓] construct list of ISIN digits. */ do k=1 for length(x); _= substr(x,k,1) /*the ISIN may contain alphabetic chars*/ p= pos(_, 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ) /*X must contain A──►Z, 0──►9.*/ if p==0 then y= /*trigger "not" valid below.*/ else $= $ || p-1 /*convert X string (base 36 ──► dec).*/ end /*k*/ /* [↑] convert alphabetic ──► digits.*/ @= /*placeholder for the "not" in message.*/ if length(y)\==12 then @= "not" /*see if the ISIN is exactly 12 chars. */ if \datatype( left(x,2),'U') then @= "not" /* " " " " 1st 2 chars cap. let.*/ if \datatype(right(x,1),'W') then @= "not" /* " " " " last char not a digit*/ if @== then if \luhn($) then @= "not" /* " " " " passed the Luhn test.*/ say right(x, 30) right(@, 5) "valid" /*display the yea or nay message.*/ end /*n*/ /* [↑] 1st 3 IFs could've been combined*/
exit /*stick a fork in it, we're all done. */ /*──────────────────────────────────────────────────────────────────────────────────────*/ Luhn: procedure; parse arg x; $= 0 /*get credit card number; zero $ sum. */
y= reverse( left(0, length(x) // 2)x) /*add leading zero if needed, & reverse*/ do j=1 to length(y)-1 by 2; _= 2 * substr(y, j+1, 1) $= $ + substr(y, j, 1) + left(_, 1) + substr(_, 2 , 1, 0) end /*j*/ /* [↑] sum the odd and even digits.*/ return right($, 1)==0 /*return "1" if number passed Luhn test*/</lang>
- output when using the default inputs:
US0378331005 valid US0373831005 not valid U50378331005 not valid US03378331005 not valid AU0000XVGZA3 valid AU0000VXGZA3 valid FR0000988040 valid
Ring
<lang ring>
- Project : Validate International Securities Identification Number
decimals(0)
test = ["US0378331005",
"US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"]
for n = 1 to len(test)
testold = test[n] ascii1 = ascii(left(test[n],1)) ascii2 = ascii(substr(test[n],2,1)) if len(test[n]) != 12 or (ascii1 < 65 or ascii1 > 90) or (ascii2 < 65 or ascii2 > 90) see test[n] + " -> Invalid" + nl loop ok for m = 1 to len(test[n]) if ascii(test[n][m]) > 64 and ascii(test[n][m]) < 91 asc = ascii(test[n][m]) - 55 test[n] = left(test[n],m-1) + string(asc) + right(test[n],len(test[n])-m) ok next see testold + " -> " + cardtest(test[n]) + nl
next
func cardtest(numstr)
revstring = revstr(numstr) s1 = revodd(revstring) s2 = reveven(revstring) s3 =right(string(s1+s2), 1) if s3 = "0" return "Valid" else return "Invalid" ok
func revstr(str)
strnew = "" for nr = len(str) to 1 step -1 strnew = strnew + str[nr] next return strnew
func revodd(str)
strnew = "" for nr = 1 to len(str) step 2 strnew = strnew + str[nr] next sumodd = 0 for p = 1 to len(strnew) sumodd = sumodd + number(strnew[p]) next return sumodd
func reveven(str)
strnew = "" for nr = 2 to len(str) step 2 strnew = strnew + str[nr] next lsteven = [] for p = 1 to len(strnew) add(lsteven, string(2*number(strnew[p]))) next arreven = list(len(lsteven)) for q = 1 to len(lsteven) sum = 0 for w = 1 to len(lsteven[q]) sum = sum + lsteven[q][w] next arreven[q] = sum next sumarr = 0 for x = 1 to len(arreven) sumarr = sumarr + arreven[x] next return sumarr
</lang> Output:
US0378331005 -> Valid US0373831005 -> Invalid U50378331005 -> Invalid US03378331005 -> Invalid AU0000XVGZA3 -> Valid AU0000VXGZA3 -> Valid FR0000988040 -> Valid
Ruby
Using a pre-existing luhn method: <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]</lang>
Rust
<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);
} </lang>
SAS
<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;</lang>
Scala
- Output:
Best seen running in your browser either by ScalaFiddle (ES aka JavaScript, non JVM) or Scastie (remote JVM).
<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"))
}</lang>
SQL PL
version 9.7 or higher.
With SQL PL: <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 @
</lang> Output:
db2 -td@ db2 => BEGIN ... db2 (cont.) => END @ DB20000I The SQL command completed successfully. db2 => VALUES VALIDATE_ISIN('US0378331005')@ 1 ------ 0 1 record(s) selected. 30280378331005 It is a valid number 27+23=50 db2 => VALUES VALIDATE_ISIN('US0373831005')@ 1 ------ 1 1 record(s) selected. 30280373831005 It is NOT a valid number 22+24=46 db2 => VALUES VALIDATE_ISIN('U50378331005')@ 1 ------ SQL0438N Application raised error or warning with diagnostic text: "Country code with invalid characters". SQLSTATE=ISIN1 db2 => VALUES VALIDATE_ISIN('U503378331005')@ 1 ------ SQL0433N Value "U503378331005" is too long. SQLSTATE=22001 db2 => VALUES VALIDATE_ISIN('AU0000XVGZA3')@ 1 ------ 0 1 record(s) selected. 1030000033311635103 It is a valid number 18+12=30 db2 => VALUES VALIDATE_ISIN('AU0000VXGZA3')@ 1 ------ 0 1 record(s) selected. 1030000031331635103 It is a valid number 18+12=30 db2 => VALUES VALIDATE_ISIN('FR0000988040')@ 1 ------ 0 1 record(s) selected. 15270000988040 It is a valid number 20+30=50
Tcl
<lang Tcl>package require Tcl 8.6 ;# mostly needed for [assert]. Substitute a simpler one or a NOP if required.</lang> A proc like assert is always good to have around. This one tries to report values used in its expression using subst: <lang Tcl>proc assert {expr} { ;# for "static" assertions that throw nice errors
if {![uplevel 1 [list expr $expr]]} { set msg "{$expr}" catch {append msg " {[uplevel 1 [list subst -noc $expr]]}"} tailcall throw {ASSERT ERROR} $msg }
}</lang> isin itself is a simple package. We compute the alphabet when the package is loaded in _init, because that's more fun than typing out the table: <lang Tcl>namespace eval isin {
proc _init {} { ;# sets up the map used on every call variable map set alphabet abcdefghijklmnopqrstuvwxyz set n 9 lmap c [split $alphabet ""] { lappend map $c [incr n] } } _init
proc normalize {isin} { variable map string map $map [string tolower [string trim $isin]] }
# copied from "Luhn test of credit card numbers" # included here for ease of testing, and because it is short proc luhn digitString { if {[regexp {[^0-9]} $digitString]} {error "not a number"} set sum 0 set flip 1 foreach ch [lreverse [split $digitString {}]] { incr sum [lindex { {0 1 2 3 4 5 6 7 8 9} {0 2 4 6 8 1 3 5 7 9} } [expr {[incr flip] & 1}] $ch] } return [expr {($sum % 10) == 0}] }
proc validate {isin} { if {![regexp {^[A-Z]{2}[A-Z0-9]{9}[0-9]$} $isin]} {return false} luhn [normalize $isin] }
}</lang> To run the test suite, we use the tcltest framework included with Tcl: <lang Tcl>package require tcltest
tcltest::test isin-1 "Test isin validation" -body {
foreach {isin ok} { US0378331005 yes US0373831005 no U50378331005 no US03378331005 no AU0000XVGZA3 yes AU0000VXGZA3 yes FR0000988040 yes } { if {$ok} { assert {[isin::validate $isin]} } else { assert {![isin::validate $isin]} } } return ok
} -result ok</lang>
Transact-SQL
<lang Transact-SQL> CREATE FUNCTION dbo._ISINCheck( @strISIN VarChar(40) ) RETURNS bit AS BEGIN --*** Test an ISIN code and return 1 if it is valid, 0 if invalid. DECLARE @bValid bit;
SET @bValid = CASE WHEN @strISIN LIKE '[A-Z][A-Z][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][A-Z,0-9][0-9]' THEN 1 ELSE 0 END IF @bValid = 1 BEGIN DECLARE @strTest VarChar(40) = ; DECLARE @strAdd VarChar(2); DECLARE @p INT = 0; WHILE @p < LEN(@strISIN) BEGIN SET @p = @p+1; SET @strAdd = SUBSTRING(@strISIN,@p,1); IF @strAdd LIKE '[A-Z]' SET @strAdd = CONVERT(VarChar(2),ASCII(UPPER(@strAdd))-55); SET @strTest = @strTest + @strAdd; END;
-- Proceed with Luhn test DECLARE @strLuhn VarChar(40) = REVERSE(@strTest); -- usage: set once, never changed DECLARE @strS2Values VarChar(10) = '0246813579'; -- constant: maps digits to their S2 summed values SET @p = 0; -- reset loop counter DECLARE @intValue INT; DECLARE @intSum INT = 0; -- loop through the reversed string, get the value (even-positioned digits are mapped) and add it to @intSum WHILE @p < LEN(@strLuhn) BEGIN SET @p = @p+1; SET @intValue = CONVERT(INT, SUBSTRING(@strLuhn,@p,1) ) -- value of the digit at position @p in the string IF @p % 2 = 0 SET @intValue = CONVERT(INT,SUBSTRING(@strS2Values,@intValue+1,1)) SET @intSum = @intSum + @intValue END -- If the of the digits' mapped values ends in 0 (modulo 10 = 0) then the Luhn test succeeds SET @bValid = CASE WHEN @intSum % 10 = 0 THEN 1 ELSE 0 END END;
RETURN @bValid END </lang> Testing <lang Transact-SQL> -- Testing. The following tests all pass.
- WITH ISIN_Tests AS
( SELECT 'US0378331005' AS ISIN, 1 Expected
UNION SELECT 'US0373831005',0 UNION SELECT 'U50378331005',0 UNION SELECT 'US03378331005',0 UNION SELECT 'AU0000XVGZA3',1 UNION SELECT 'AU0000VXGZA3',1 UNION SELECT 'FR0000988040',1 UNION SELECT '0___garbage',0 UNION SELECT ,0
) SELECT ISIN, Expected, dbo._ISINCheck(ISIN) AS TestResult FROM ISIN_Tests ORDER BY ISIN </lang>
VBScript
<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 </lang>
- Output:
US0378331005 valid US0373831005 invalid ??1 U50378331005 invalid ??2 US03378331005 invalid ??3 AU0000XVGZA3 valid AU0000VXGZA3 valid FR0000988040 valid
Visual Basic
Calls LuhnCheckPassed() function described at Luhn_test_of_credit_card_numbers#Visual_Basic <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</lang> Test: <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</lang>
Visual Basic .NET
<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 temp = Asc(c) - Asc("A"c) + 10 End If Return temp End Function
Function LuhnTest(number As String) As Boolean Return number.Select(Function(c, i) (AscW(c) - 48) << ((number.Length - i - 1) And 1)).Sum(Function(n) If(n > 9, n - 9, n)) Mod 10 = 0 End Function
Function Digitize(isin As String) As String Return String.Join("", isin.Select(Function(c) $"{DigitValue(c)}")) End Function
Function IsValidIsin(isin As String) As Boolean Return IsinRegex.IsMatch(isin) AndAlso LuhnTest(Digitize(isin)) End Function
Sub Main() Dim isins() = { "US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040" }
For Each isin In isins If IsValidIsin(isin) Then Console.WriteLine("{0} is valid", isin) Else Console.WriteLine("{0} is not valid", isin) End If Next End Sub
End Module</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Wren
The Luhn test method is reproduced here for convenience. <lang ecmascript>import "/str" for Char import "/trait" for Stepped import "/fmt" for Conv, Fmt
var luhn = Fn.new { |s|
s = s[-1..0] var s1 = Stepped.new(s, 2).reduce(0) { |sum, d| sum + d.bytes[0] - 48 } var s2 = Stepped.new(s[1..-1], 2).reduce(0) { |sum, d| var d2 = (d.bytes[0] - 48) * 2 return sum + ((d2 > 9) ? d2%10 + 1 : d2) } return (s1 + s2)%10 == 0
}
var isin = Fn.new { |s|
if (!(s is String && s.count == 12)) return false for (i in 0..11) { var c = s[i] if (i <= 1) { if (!Char.isUpper(c)) return false } else if (i >= 2 && i <= 10) { if (!Char.isUpper(c) && !Char.isDigit(c)) return false } else { if (!Char.isDigit(c)) return false } } var dec = "" for (i in 0...s.count) dec = dec + "%(Conv.atoi(s[i], 36))" return luhn.call(dec)
}
var tests = [
"US0378331005", "US0373831005", "U50378331005", "US03378331005", "AU0000XVGZA3", "AU0000VXGZA3", "FR0000988040"
]
for (test in tests) {
var ans = (isin.call(test)) ? "valid" : "not valid" System.print("%(Fmt.s(-13, test)) -> %(ans)")
}</lang>
- Output:
US0378331005 -> valid US0373831005 -> not valid U50378331005 -> not valid US03378331005 -> not valid AU0000XVGZA3 -> valid AU0000VXGZA3 -> valid FR0000988040 -> valid
XPL0
<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); ];
]</lang>
- Output:
US0378331005 is valid US0373831005 is not valid U50378331005 is not valid US03378331005 is not valid AU0000XVGZA3 is valid AU0000VXGZA3 is valid FR0000988040 is valid
Yabasic
<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 </lang>
zkl
Uses the luhn test from Luhn_test_of_credit_card_numbers#zkl (copied here as it is short). <lang zkl>fcn validateISIN(isin){
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)
}</lang> <lang zkl>println(" ISIN Valid?"); foreach isin in (T("US0378331005","US0373831005","U50378331005", "US03378331005","AU0000XVGZA3","AU0000VXGZA3","FR0000988040")){
println(isin," --> ",validateISIN(isin));
}</lang>
- Output:
ISIN Valid? US0378331005 --> True US0373831005 --> False U50378331005 --> False US03378331005 --> False AU0000XVGZA3 --> True AU0000VXGZA3 --> True FR0000988040 --> True
- Programming Tasks
- Solutions by Programming Task
- 11l
- 360 Assembly
- Ada
- ALGOL W
- AppleScript
- AWK
- C
- C sharp
- C++
- Caché ObjectScript
- Clojure
- COBOL
- Common Lisp
- D
- Elixir
- Factor
- Fortran
- FreeBASIC
- Go
- Groovy
- Groovy examples needing attention
- Examples needing attention
- Haskell
- J
- Java
- Julia
- Kotlin
- Langur
- Lua
- Perl
- Phix
- PicoLisp
- PowerShell
- PureBasic
- Python
- Racket
- Raku
- REXX
- Ring
- Ruby
- Rust
- SAS
- Scala
- SQL PL
- Tcl
- Transact-SQL
- VBScript
- Visual Basic
- Visual Basic .NET
- Wren
- Wren-str
- Wren-trait
- Wren-fmt
- XPL0
- Yabasic
- Zkl