IBAN
You are encouraged to solve this task according to the task description, using any language you may know.
This page uses content from Wikipedia. The original article was at IBAN. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
The International Bank Account Number (IBAN) is an internationally agreed means of identifying bank accounts across national borders with a reduced risk of propagating transcription errors.
The IBAN consists of up to 34 alphanumeric characters:
- first the two-letter ISO 3166-1 alpha-2 country code,
- then two check digits, and
- finally a country-specific Basic Bank Account Number (BBAN).
The check digits enable a sanity check of the bank account number to confirm its integrity even before submitting a transaction.
- Task
Validate the following fictitious IBAN: GB82 WEST 1234 5698 7654 32
Details of the algorithm can be found on the Wikipedia page.
11l
F mod97(numberstring)
V segstart = 0
V step = 9
V prepended = ‘’
V number = 0
L segstart < numberstring.len - step
number = Int(prepended‘’numberstring[segstart .< segstart + step])
V remainder = number % 97
prepended = String(remainder)
I remainder < 10
prepended = ‘0’prepended
segstart += step
step = 7
number = Int(prepended‘’numberstring[segstart ..])
R number % 97
V country2length = [‘IS’ = 26, ‘IT’ = 27, ‘MT’ = 31, ‘MU’ = 30, ‘MR’ = 27, ‘MK’ = 19, ‘IE’ = 22, ‘MD’ = 24,
‘ME’ = 22, ‘MC’ = 27, ‘IL’ = 23, ‘DE’ = 22, ‘DO’ = 28, ‘DK’ = 18, ‘PK’ = 24, ‘HR’ = 21,
‘HU’ = 28, ‘FI’ = 18, ‘LU’ = 20, ‘LT’ = 20, ‘LV’ = 21, ‘KW’ = 30, ‘LI’ = 21, ‘PS’ = 29,
‘PT’ = 25, ‘LB’ = 28, ‘GB’ = 22, ‘GE’ = 22, ‘CR’ = 21, ‘TR’ = 26, ‘GI’ = 23, ‘GL’ = 18,
‘CY’ = 28, ‘CZ’ = 24, ‘GR’ = 27, ‘GT’ = 28, ‘KZ’ = 20, ‘TN’ = 24, ‘CH’ = 21, ‘SE’ = 24,
‘SA’ = 24, ‘SM’ = 27, ‘SI’ = 19, ‘SK’ = 24, ‘PL’ = 28, ‘BR’ = 29, ‘FR’ = 27, ‘FO’ = 18,
‘BA’ = 20, ‘BG’ = 22, ‘BE’ = 16, ‘BH’ = 22, ‘VG’ = 24, ‘RO’ = 24, ‘RS’ = 22, ‘NO’ = 15,
‘NL’ = 18, ‘AZ’ = 28, ‘EE’ = 20, ‘AT’ = 20, ‘ES’ = 24, ‘AL’ = 28, ‘AD’ = 24, ‘AE’ = 23]
F valid_iban(iban_)
V iban = iban_.replace(‘ ’, ‘’).replace("\t", ‘’)
I !re:‘[\dA-Z]+’.match(iban)
R 0B
I iban.len != :country2length[iban[0.<2]]
R 0B
iban = iban[4..]‘’iban[0.<4]
R mod97(iban.map(ch -> String(Int(ch, radix' 36))).join(‘’)) == 1
L(account) [‘GB82 WEST 1234 5698 7654 32’,
‘GB82 TEST 1234 5698 7654 32’]
print(‘#. validation is: #.’.format(account, valid_iban(account)))
- Output:
GB82 WEST 1234 5698 7654 32 validation is: 1B GB82 TEST 1234 5698 7654 32 validation is: 0B
Ada
package Iban_Code is
function Is_Legal(Iban : String) return Boolean;
end Iban_Code;
with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Containers.Hashed_Maps;
with Ada.Strings.Hash;
package body Iban_Code is
subtype Nation is String (1..2);
package String_Integer is new Ada.Containers.Hashed_Maps
(Nation, Integer, Ada.Strings.Hash, Equivalent_Keys => "=");
Nations : String_Integer.Map;
function Is_Legal(Iban : String) return Boolean
is
Temp : String(Iban'Range) := (others => ' ');
Count : Integer;
Ch : Character;
Num : Integer := 0;
begin
-- remove blank spaces and check characters
Count := Temp'First;
for I in Iban'Range loop
case Iban(I) is
when ' ' => null;
when 'a'..'z' =>
Temp(Count) := To_Upper(Iban(I));
Count := Count + 1;
when 'A'..'Z'|'0'..'9' =>
Temp(Count) := Iban(I);
Count := Count + 1;
when others => return False;
end case;
end loop;
-- check nation code and length
if not Nations.Contains (Temp(1..2)) or else
Nations.Element (Temp(1..2))/= Count - 1 then
return False;
end if;
-- move the 4 initial characters to the end
Temp(Temp'First..Count-1) := Temp(5..Count-1) & Temp(Temp'First..4);
-- compute remainder modulo 97
for I in Temp'First..Count-1 loop
Ch := Temp(I);
if Ch in '0'..'9' then
Num := Integer'Value(Integer'Image(Num) & Ch) mod 97;
else
Num := (Num * 100 +
(Character'Pos(Ch) - Character'Pos('A') + 10)) mod 97;
end if;
end loop;
return Num = 1;
end Is_Legal;
begin
Nations.insert("AL", 28); Nations.insert("AD", 24);
Nations.insert("AT", 20); Nations.insert("AZ", 28);
Nations.insert("BE", 16); Nations.insert("BH", 22);
Nations.insert("BA", 20); Nations.insert("BR", 29);
Nations.insert("BG", 22); Nations.insert("CR", 21);
Nations.insert("HR", 21); Nations.insert("CY", 28);
Nations.insert("CZ", 24); Nations.insert("DK", 18);
Nations.insert("DO", 28); Nations.insert("EE", 20);
Nations.insert("FO", 18); Nations.insert("FI", 18);
Nations.insert("FR", 27); Nations.insert("GE", 22);
Nations.insert("DE", 22); Nations.insert("GI", 23);
Nations.insert("GR", 27); Nations.insert("GL", 18);
Nations.insert("GT", 28); Nations.insert("HU", 28);
Nations.insert("IS", 26); Nations.insert("IE", 22);
Nations.insert("IL", 23); Nations.insert("IT", 27);
Nations.insert("KZ", 20); Nations.insert("KW", 30);
Nations.insert("LV", 21); Nations.insert("LB", 28);
Nations.insert("LI", 21); Nations.insert("LT", 20);
Nations.insert("LU", 20); Nations.insert("MK", 19);
Nations.insert("MT", 31); Nations.insert("MR", 27);
Nations.insert("MU", 30); Nations.insert("MC", 27);
Nations.insert("MD", 24); Nations.insert("ME", 22);
Nations.insert("NL", 18); Nations.insert("NO", 15);
Nations.insert("PK", 24); Nations.insert("PS", 29);
Nations.insert("PL", 28); Nations.insert("PT", 25);
Nations.insert("RO", 24); Nations.insert("SM", 27);
Nations.insert("SA", 24); Nations.insert("RS", 22);
Nations.insert("SK", 24); Nations.insert("SI", 19);
Nations.insert("ES", 24); Nations.insert("SE", 24);
Nations.insert("CH", 21); Nations.insert("TN", 24);
Nations.insert("TR", 26); Nations.insert("AE", 23);
Nations.insert("GB", 22); Nations.insert("VG", 24);
end Iban_Code;
Testing:
with Ada.Text_Io; use Ada.Text_Io;
with Iban_Code;
procedure Check_Iban is
procedure Check(Iban : String) is
begin
if Iban_Code.Is_Legal(Iban) then
Put_Line(Iban & " is valid.");
else
Put_Line(Iban & " is not valid.");
end if;
end Check;
begin
Check("GB82 WEST 1234 5698 7654 32");
Check("GB82WEST12345698765432");
Check("gb82 west 1234 5698 7654 32");
Check("GB82 TEST 1234 5698 7654 32");
Check("GB82 WEST 1243 5698 7654 32");
end Check_Iban;
- Output:
GB82 WEST 1234 5698 7654 32 is valid. GB82WEST12345698765432 is valid. gb82 west 1234 5698 7654 32 is valid. GB82 TEST 1234 5698 7654 32 is not valid. GB82 WEST 1243 5698 7654 32 is not valid.
AppleScript
on countryCodes()
-- A list of 34 lists. The nth list (1-indexed) contains country codes for countries having n-character IBANS.
return {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {"NO"}, {"BE"}, ¬
{}, {"DK", "FO", "FI", "GL", "NL"}, {"MK", "SI"}, {"AT", "BA", "EE", "KZ", "XK", "LT", "LU"}, ¬
{"HR", "LV", "LI", "CH"}, {"BH", "BG", "CR", "GE", "DE", "IE", "ME", "RS", "GB", "VA"}, ¬
{"TL", "GI", "IQ", "IL", "AE"}, {"AD", "CZ", "MD", "PK", "RO", "SA", "SK", "ES", "SE", "TN", "VG"}, ¬
{"PT", "ST"}, {"IS", "TR"}, {"FR", "GR", "IT", "MR", "MC", "SM"}, ¬
{"AL", "AZ", "BY", "CY", "DO", "SV", "GT", "HU", "LB", "PL"}, {"BR", "EG", "PS", "QA", "UA"}, ¬
{"JO", "KW", "MU"}, {"MT", "SC"}, {"LC"}, {}, {}}
end countryCodes
on validateIBAN(iban)
-- Remove any spaces.
if (iban contains space) then set iban to replaceText(iban, space, "")
considering diacriticals but ignoring case
-- Check the length both overall and against the presumed country code.
set characterCount to (count iban)
if ((characterCount > 34) or (text 1 thru 2 of iban is not in item characterCount of countryCodes())) then return false
-- Move the first four characters to the end.
set iban to text 5 thru -1 of iban & (text 1 thru 4 of iban)
-- Replace any unadorned Latin letters with the appropriate number characters ("A" = "11" … "Z" = "35").
repeat with letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
if (iban contains letter) then set iban to replaceText(iban, letter, ((letter's id) mod 32 + 9) as text)
end repeat
end considering
-- Check that what's left only contains digit characters.
if (replaceText(iban, characters of "0123456789", "") is not "") then return false
-- Calculate the mod-97 remainder.
set characterCount to (count iban)
set c to (characterCount - 1) mod 7 + 1
set mod97 to (text 1 thru c of iban) mod 97
repeat with c from (c + 1) to characterCount by 7
set mod97 to (mod97 * 10000000 + (text c thru (c + 6) of iban)) mod 97
end repeat
return (mod97 = 1)
end validateIBAN
on replaceText(txt, searchStr, replaceText)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to searchStr
set txt to txt's text items
set AppleScript's text item delimiters to replaceText
set txt to txt as text
set AppleScript's text item delimiters to astid
return txt
end replaceText
-- Test code (IBANs borrowed from other solutions on this page.):
local testIBANs, output, thisIBAN, astid
set testIBANs to {"GB82 WEST 1234 5698 7654 32", "gb82 west 1234 5698 7654 32", "GB82 TEST 1234 5698 7654 32", ¬
"SA03 8000 0000 6080 1016 7519", "ZZ12 3456 7890 1234 5678 12", "IL62 0108 0000 0009 9999 999"}
set output to {}
repeat with thisIBAN in testIBANs
set end of output to thisIBAN & item ((validateIBAN(thisIBAN) as integer) + 1) of {": invalid", ": valid"}
end repeat
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to linefeed
set output to output as text
set AppleScript's text item delimiters to astid
return output
- Output:
"GB82 WEST 1234 5698 7654 32: valid
gb82 west 1234 5698 7654 32: valid
GB82 TEST 1234 5698 7654 32: invalid
SA03 8000 0000 6080 1016 7519: valid
ZZ12 3456 7890 1234 5678 12: invalid
IL62 0108 0000 0009 9999 999: valid"
Arturo
ibanSize: #[
AL: 28, AD: 24, AT: 20, AZ: 28, BE: 16
BH: 22, BA: 20, BR: 29, BG: 22, CR: 21, HR: 21, CY: 28
CZ: 24, DK: 18, DO: 28, EE: 20, FO: 18, FI: 18, FR: 27
GE: 22, DE: 22, GI: 23, GR: 27, GL: 18, GT: 28, HU: 28
IS: 26, IE: 22, IL: 23, IT: 27, KZ: 20, KW: 30, LV: 21
LB: 28, LI: 21, LT: 20, LU: 20, MK: 19, MT: 31, MR: 27
MU: 30, MC: 27, MD: 24, ME: 22, NL: 18, NO: 15, PK: 24
PS: 29, PL: 28, PT: 25, RO: 24, SM: 27, SA: 24, RS: 22
SK: 24, SI: 19, ES: 24, SE: 24, CH: 21, TN: 24, TR: 26
AE: 23, GB: 22, VG: 24
]
base36: (map 0..9 'x -> to :string x) ++
map 97..122 'x -> upper to :string to :char x
valid?: function [iban][
iban: replace iban " " ""
if not? contains? iban {/[0-9A-Z]+/} -> return false
if not? (size iban)=ibanSize\[slice iban 0 1] -> return false
iban: (slice iban 4 (size iban)-1) ++ slice iban 0 3
iban: join map split iban 'ch -> to :string index base36 ch
iban: to :integer iban
return 1=iban%97
]
loop ["GB82 WEST 1234 5698 7654 32"
"GB82 TEST 1234 5698 7654 32"] 'ib
-> print [ib "=> valid?" valid? ib]
- Output:
GB82 WEST 1234 5698 7654 32 => valid? true GB82 TEST 1234 5698 7654 32 => valid? false
AutoHotkey
IBANs := ["GB82 WEST 1234 5698 7654 32"
, "gb82 WEST 1234 5698 7654 32"
, "GB82WEST12345698765432"
, "GB82 WEST 234 5698 7654 32"
, "GB82 WEST 1234 5698 7654 33"
, "AE82 WEST 1234 5698 7654 32"]
for k, v in IBANs
Output .= v " is" (ValidIBAN(v) ? "" : " not") " valid.`n"
MsgBox, % Output
ValidIBAN(n) {
static CC := {AL:28, AD:24, AT:20, AZ:28, BH:22, BE:16, BA:20, BR:29, BG:22, CR:21
, HR:21, CY:28, CZ:24, DK:18, DO:28, EE:20, FO:18, FI:18, FR:27, GE:22
, DE:22, GI:23, GR:27, GL:18, GT:28, HU:28, IS:26, IE:22, IL:23, IT:27
, JO:30, KZ:20, KW:30, LV:21, LB:28, LI:21, LT:20, LU:20, MK:19, MT:31
, MR:27, MU:30, MC:27, MD:24, ME:22, NL:18, NO:15, PK:24, PS:29, PL:28
, PT:25, QA:29, RO:24, SM:27, SA:24, RS:22, SK:24, SI:19, ES:24, SE:24
, CH:21, TN:24, TR:26, AE:23, GB:22, VG:24}
StringReplace, n, n, % A_Space,, A
;Check that the total IBAN length is correct as per the country
if (StrLen(n) != CC[SubStr(n, 1, 2)])
return false
StringUpper, n, n
;Move the four initial characters to the end of the string
n := SubStr(n, 5) SubStr(n, 1, 4)
;Replace each letter in the string with two digits
Loop, Parse, n
{
if A_LoopField is alpha
nn .= Asc(A_LoopField) - 55
else
nn .= A_LoopField
}
return Mod97(nn) = 1
}
Mod97(a) {
while a {
rem := Mod(rem SubStr(a, 1, 15), 97)
a := SubStr(a, 16)
}
return rem
}
- Output:
GB82 WEST 1234 5698 7654 32 is valid. gb82 WEST 1234 5698 7654 32 is valid. GB82WEST12345698765432 is valid. GB82 WEST 234 5698 7654 32 is not valid. GB82 WEST 1234 5698 7654 33 is not valid. AE82 WEST 1234 5698 7654 32 is not valid.
AWK
This requires a gawk with extensions and GNU MP+MPFR support - it's usually the case. Some country codes are missing, the output is itself parsable.
@load "ordchr"
function invalid() { print("INVALID " $0); next }
function valid() { print("VALID__ " $0) }
BEGIN {
ccibanlen["AL"] = 28; ccibanlen["AD"] = 24; ccibanlen["AT"] = 20;
ccibanlen["AZ"] = 28; ccibanlen["BH"] = 22; ccibanlen["BA"] = 20;
ccibanlen["BR"] = 29; ccibanlen["BG"] = 22; ccibanlen["CR"] = 21;
ccibanlen["HR"] = 21; ccibanlen["CY"] = 28; ccibanlen["CZ"] = 24;
ccibanlen["DK"] = 18; ccibanlen["DO"] = 28; ccibanlen["EE"] = 20;
ccibanlen["FO"] = 18; ccibanlen["FI"] = 18; ccibanlen["FR"] = 27;
ccibanlen["GE"] = 22; ccibanlen["DE"] = 22; ccibanlen["GI"] = 23;
ccibanlen["GR"] = 27; ccibanlen["GL"] = 18; ccibanlen["GT"] = 28;
ccibanlen["HU"] = 28; ccibanlen["IS"] = 26; ccibanlen["IE"] = 22;
ccibanlen["IT"] = 27; ccibanlen["KZ"] = 20; ccibanlen["KW"] = 30;
ccibanlen["LV"] = 21; ccibanlen["LB"] = 28; ccibanlen["LI"] = 21;
ccibanlen["LT"] = 20; ccibanlen["LU"] = 20; ccibanlen["MK"] = 19;
ccibanlen["MT"] = 31; ccibanlen["MR"] = 27; ccibanlen["MU"] = 30;
ccibanlen["MC"] = 27; ccibanlen["MD"] = 24; ccibanlen["ME"] = 22;
ccibanlen["NL"] = 18; ccibanlen["NO"] = 15; ccibanlen["PK"] = 24;
ccibanlen["PS"] = 29; ccibanlen["PL"] = 28; ccibanlen["PT"] = 25;
ccibanlen["RO"] = 24; ccibanlen["SM"] = 27; ccibanlen["SA"] = 24;
ccibanlen["RS"] = 22; ccibanlen["SK"] = 24; ccibanlen["SI"] = 19;
ccibanlen["ES"] = 24; ccibanlen["SE"] = 24; ccibanlen["CH"] = 21;
ccibanlen["TN"] = 24; ccibanlen["TR"] = 26; ccibanlen["AE"] = 23;
ccibanlen["GB"] = 22; ccibanlen["VG"] = 24; ccibanlen["BE"] = 16;
}
{
iban = toupper($0)
gsub(/\s+/, "", iban)
ccode = substr(iban, 1, 2)
if ( ! match(iban, /^[A-Z0-9]+$/) ||
! (ccode in ccibanlen) ||
length(iban) != ccibanlen[ccode])
invalid()
ibanrev = gensub(/^(.{4})(.+)/, "\\2\\1", 1, iban)
ibancsum = ""
for (i = 1; i <= length(ibanrev); i++) {
currchar = substr(ibanrev, i, 1)
if (match(currchar, /[A-Z]/))
currchar = ord(currchar) - 55
ibancsum = ibancsum currchar
}
ibancsum % 97 == 1 ? valid() : invalid()
}
Creating a test file and launching the script:
cat > test.iban
FR33 ^__^ 0BAD
AA11 1234 6543 1212
FR33 1234 5432
CH93 0076 2011 6238 5295 7
GB82 WEST 1234 5698 7654 32
GB82 TEST 1234 5698 7654 32
^D
gawk -Mf iban.gawk test.iban
Output:
INVALID FR33 ^__^ 0BAD
INVALID AA11 1234 6543 1212
INVALID FR33 1234 5432
VALID__ CH93 0076 2011 6238 5295 7
VALID__ GB82 WEST 1234 5698 7654 32
INVALID GB82 TEST 1234 5698 7654 32
BBC BASIC
REM Used the following as official standard:
REM http://www.cnb.cz/cs/platebni_styk/iban/download/EBS204.pdf
REM Pairs of ISO 3166 country code & expected IBAN length for this country
COULEN$="AL28 AD24 AT20 AZ28 BE16 BH22 BA20 BR29 BG22 CR21 HR21 CY28 CZ24 DK18 DO28 EE20 "+\
\ "FO18 FI18 FR27 GE22 DE22 GI23 GR27 GL18 GT28 HU28 IS26 IE22 IL23 IT27 KZ20 KW30 "+\
\ "LV21 LB28 LI21 LT20 LU20 MK19 MT31 MR27 MU30 MC27 MD24 ME22 NL18 NO15 PK24 PS29 "+\
\ "PL28 PT25 RO24 SM27 SA24 RS22 SK24 SI19 ES24 SE24 CH21 TN24 TR26 AE23 GB22 VG24"
PROCIBANcheck("GB82 WEST 1234 5698 7654 32"):REM Paper IBAN notation (with the spaces)
PROCIBANcheck("GB82WEST12345698765432") :REM Digital IBAN notation (without the spaces)
PROCIBANcheck("gb82 west 1234 5698 7654 32")
PROCIBANcheck("GB82 TEST 1234 5698 7654 32")
PROCIBANcheck("GR16 0110 1250 0000 0001 2300 695")
PROCIBANcheck("GB29 NWBK 6016 1331 9268 19")
PROCIBANcheck("SA03 8000 0000 6080 1016 7519")
PROCIBANcheck("CH93 0076 2011 6238 5295 7")
PROCIBANcheck("IL62 0108 0000 0009 9999 999")
PROCIBANcheck("IL62-0108-0000-0009-9999-999")
PROCIBANcheck("US12 3456 7890 0987 6543 210")
PROCIBANcheck("GR16 0110 1250 0000 0001 2300 695X")
END
DEF PROCIBANcheck(iban$)
LOCAL err$,i%,match%,explen%,digiban$,tmpiban$,bignum$,c%,kk%
REM Search for country code and fetch expected length
i%=1:explen%=0
WHILE explen%=0 AND i%<LENCOULEN$
IF LEFT$(iban$,2)=MID$(COULEN$,i%,2) explen%=VALMID$(COULEN$,i%+2,2)
i%+=5
ENDWHILE
match%=explen%>0
REM Continue if country code found
IF match% THEN
REM Remove space = convert to digital IBAN
digiban$=""
FOR i%=1TOLENiban$
IF MID$(iban$,i%,1)>" " digiban$+=MID$(iban$,i%,1)
NEXT
REM Compare length with expected length
match%=explen%=LENdigiban$
REM Continue if length is correct
IF match% THEN
REM Create temporary string with country code appended
tmpiban$=MID$(digiban$,5)+MID$(digiban$,1,2)
REM Make big number, replacing letters by numbers using next conversion table: A=10 ... Z=35
bignum$=""
FOR i%=1TOLENtmpiban$
c%=ASCMID$(tmpiban$,i%,1)
IF c%>57 bignum$+=STR$(c%-55) ELSE bignum$+=STR$(c%-48)
NEXT
REM MOD 97 on bignum$+"00" and subtract result from 98 to obtain control number
kk%=98-FNmod97(bignum$+"00")
REM Compare with control number in IBAN
match%=VALMID$(iban$,3,2)=kk%
REM Continue if control number matches
IF match% THEN
REM Append kk% to bignum$ and determine if MOD 97 results in 1
match%=FNmod97(bignum$+RIGHT$("0"+STR$kk%,2))=1
REM Continue if MOD 97
IF match% THEN
REM Was last test
ELSE
err$="result from modulo 97"
ENDIF
ELSE
err$="check digits, should be: "+STR$kk%
ENDIF
ELSE
err$="code length, expected length: "+STR$explen%
ENDIF
ELSE
err$="country code: "+LEFT$(iban$,2)
ENDIF
IF match% PRINT " "; ELSE PRINT "in";:err$="***error!*** invalid "+err$
PRINT "valid IBAN: ";iban$TAB(50)err$
ENDPROC
DEF FNmod97(num$)
LOCAL mod$
mod$=LEFT$(num$,2)
num$=MID$(num$,3)
WHILE num$>""
mod$=RIGHT$("0"+STR$(VAL(mod$+LEFT$(num$,7))MOD97),2)
num$=MID$(num$,8)
ENDWHILE
=VALmod$
- Output:
valid IBAN: GB82 WEST 1234 5698 7654 32 valid IBAN: GB82WEST12345698765432 invalid IBAN: gb82 west 1234 5698 7654 32 ***error!*** invalid country code: gb invalid IBAN: GB82 TEST 1234 5698 7654 32 ***error!*** invalid check digits, should be: 78 valid IBAN: GR16 0110 1250 0000 0001 2300 695 valid IBAN: GB29 NWBK 6016 1331 9268 19 valid IBAN: SA03 8000 0000 6080 1016 7519 valid IBAN: CH93 0076 2011 6238 5295 7 valid IBAN: IL62 0108 0000 0009 9999 999 invalid IBAN: IL62-0108-0000-0009-9999-999 ***error!*** invalid code length, expected length: 23 invalid IBAN: US12 3456 7890 0987 6543 210 ***error!*** invalid country code: US invalid IBAN: GR16 0110 1250 0000 0001 2300 695X ***error!*** invalid code length, expected length: 27
Befunge
>>" :NABI">:#,_>:~:"`"`48**-:55+-#v_$0::6g0>>8g-!\7g18g-!*!v>v>>
<<_#v:#78#`8#+<^+!!*-*84\-9:g8:p8\<oo>1#$-#<0$>>>#v_"dilav" ^#<<
>>^ >*-:20p9`9*1+55+**20g+"a"%10g1+00g%:4-!|^g6::_v#-*88:+1_vv>>
<<^+`"Z"\+`\"0"\*`\"A"\`"9":::\-*86:g8p01:<<40p00_v#!--+99g5<v<<
>>" si rebmun tahT">:#,_ 55+".",,@ >0"dilavni">>>
"-(&(/$$*$(*.-*.('$)*%0-&**.$'(.'/.+,''&&*.)**&,-.&.(*(*-!(%01-)
BFBBBBFFTNRRGGCCGCGGSSKSKSSDDHDHCPLPTLLLLPPAAVEIIAAAIEMMMNGMMMMI
ERGAHRIONLSOTRZYBRLIKIWMZAEOKREUHSBTRIVTUKLEDGESTLTZESEDCOEKUTRL
- Output:
IBAN: GB82 WEST 1234 5698 7654 32 That number is valid.
IBAN: GB82 EAST 1234 5698 7654 32 That number is invalid.
BQN
sym ← ∾"0A"+⟜↕¨10‿26
CheckIBAN ← 1=·(97|⊣+(10⋆1+>⟜9)⊸×)´∘⌽4⌽36(>/⊢)sym⊸⊐
CheckIBAN "GB82 WEST 1234 5698 7654 32"
- Output:
1
Bracmat
( ( IBAN-check
= table country cd len N c
. (AL.28) (AD.24) (AT.20) (AZ.28) (BE.16) (BH.22) (BA.20) (BR.29)
(BG.22) (CR.21) (HR.21) (CY.28) (CZ.24) (DK.18) (DO.28) (EE.20)
(FO.18) (FI.18) (FR.27) (GE.22) (DE.22) (GI.23) (GR.27) (GL.18)
(GT.28) (HU.28) (IS.26) (IE.22) (IL.23) (IT.27) (KZ.20) (KW.30)
(LV.21) (LB.28) (LI.21) (LT.20) (LU.20) (MK.19) (MT.31) (MR.27)
(MU.30) (MC.27) (MD.24) (ME.22) (NL.18) (NO.15) (PK.24) (PS.29)
(PL.28) (PT.25) (RO.24) (SM.27) (SA.24) (RS.22) (SK.24) (SI.19)
(ES.24) (SE.24) (CH.21) (TN.24) (TR.26) (AE.23) (GB.22) (VG.24)
: ?table
& @(!arg:?country [2 ?cd [4 ?arg)
& str$(!arg !country !cd):?arg
& ( !table:? (!country.?len) ?
& :?N
& ( @( !arg
: ?
( %@?c ?
& ( !c:#
| !c:~<A:~>Z
& asc$!c+-1*asc$A+10:?c
& 1+!len:?len
| !c:" "&:?c
|
)
& !N !c:?N
& ~
)
)
| str$!N:?N:#
& ( @(!N:? [!len)
& ( mod$(!N,97):1&out$OK
| out$"wrong check digits"
)
| out$"wrong length"
)
| @(!N:? ~#%?c ?)
& out$(str$("invalid character: '" !c "'"))
)
| out$(str$("invalid country code: '" !country "'"))
)
)
& IBAN-check$"GB82 WEST 1234 5698 7654 32 9"
& IBAN-check$"GX82 WEST 1234 5698 7654 32"
& IBAN-check$"GB82 WEST 1234 5698 7654 32"
& IBAN-check$GB82WEST12345698765432
& IBAN-check$"gb82 west 1234 5698 7654 32"
& IBAN-check$"GB82 TEST 1234 5698 7654 32"
& IBAN-check$"GB82 WEST 1243 5698 7654 32"
& IBAN-check$"GB82 west 1243 5698 7654 32"
);
- Output:
wrong length invalid country code: 'GX' OK OK invalid country code: 'gb' wrong check digits wrong check digits invalid character: 'w'
C
#include <alloca.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define V(cc, exp) if (!strncmp(iban, cc, 2)) return len == exp
/* Validate country code against expected length. */
int valid_cc(const char *iban, int len)
{
V("AL", 28); V("AD", 24); V("AT", 20); V("AZ", 28); V("BE", 16); V("BH", 22); V("BA", 20); V("BR", 29);
V("BG", 22); V("CR", 21); V("HR", 21); V("CY", 28); V("CZ", 24); V("DK", 18); V("DO", 28); V("EE", 20);
V("FO", 18); V("FI", 18); V("FR", 27); V("GE", 22); V("DE", 22); V("GI", 23); V("GR", 27); V("GL", 18);
V("GT", 28); V("HU", 28); V("IS", 26); V("IE", 22); V("IL", 23); V("IT", 27); V("KZ", 20); V("KW", 30);
V("LV", 21); V("LB", 28); V("LI", 21); V("LT", 20); V("LU", 20); V("MK", 19); V("MT", 31); V("MR", 27);
V("MU", 30); V("MC", 27); V("MD", 24); V("ME", 22); V("NL", 18); V("NO", 15); V("PK", 24); V("PS", 29);
V("PL", 28); V("PT", 25); V("RO", 24); V("SM", 27); V("SA", 24); V("RS", 22); V("SK", 24); V("SI", 19);
V("ES", 24); V("SE", 24); V("CH", 21); V("TN", 24); V("TR", 26); V("AE", 23); V("GB", 22); V("VG", 24);
return 0;
}
/* Remove blanks from s in-place, return its new length. */
int strip(char *s)
{
int i = -1, m = 0;
while(s[++i]) {
s[i - m] = s[i];
m += s[i] <= 32;
}
s[i - m] = 0;
return i - m;
}
/* Calculate the mod 97 of an arbitrarily large number (as a string). */
int mod97(const char *s, int len)
{
int i, j, parts = len / 7;
char rem[10] = "00";
for (i = 1; i <= parts + (len % 7 != 0); ++i) {
strncpy(rem + 2, s + (i - 1) * 7, 7);
j = atoi(rem) % 97;
rem[0] = j / 10 + '0';
rem[1] = j % 10 + '0';
}
return atoi(rem) % 97;
}
int valid_iban(char *iban)
{
int i, j, l = 0, sz = strip(iban);
char *rot, *trans;
/* Ensure upper alphanumeric input and count letters. */
for (i = 0; i < sz; ++i) {
if (!isdigit(iban[i]) && !isupper(iban[i]))
return 0;
l += !!isupper(iban[i]);
}
if (!valid_cc(iban, sz))
return 0;
/* Move the first four characters to the end. */
rot = alloca(sz);
strcpy(rot, iban + 4);
strncpy(rot + sz - 4, iban, 4);
/* Allocate space for the transformed IBAN. */
trans = alloca(sz + l + 1);
trans[sz + l] = 0;
/* Convert A to 10, B to 11, etc. */
for (i = j = 0; i < sz; ++i, ++j) {
if (isdigit(rot[i]))
trans[j] = rot[i];
else {
trans[j] = (rot[i] - 55) / 10 + '0';
trans[++j] = (rot[i] - 55) % 10 + '0';
}
}
return mod97(trans, sz + l) == 1;
}
int main(int _, char **argv)
{
while (--_, *++argv)
printf("%s is %svalid.\n", *argv, valid_iban(*argv) ? "" : "in");
return 0;
}
- Output:
iban 'GB82 WEST 1234 5698 7654 32' GB82TEST12345698765432 GB82WEST12345698765432 is valid. GB82TEST12345698765432 is invalid.
C#
public class IbanValidator : IValidateTypes
{
public ValidationResult Validate(string value)
{
// Check if value is missing
if (string.IsNullOrEmpty(value))
return ValidationResult.ValueMissing;
if (value.Length < 2)
return ValidationResult.ValueTooSmall;
var countryCode = value.Substring(0, 2).ToUpper();
int lengthForCountryCode;
var countryCodeKnown = Lengths.TryGetValue(countryCode, out lengthForCountryCode);
if (!countryCodeKnown)
{
return ValidationResult.CountryCodeNotKnown;
}
// Check length.
if (value.Length < lengthForCountryCode)
return ValidationResult.ValueTooSmall;
if (value.Length > lengthForCountryCode)
return ValidationResult.ValueTooBig;
value = value.ToUpper();
var newIban = value.Substring(4) + value.Substring(0, 4);
newIban = Regex.Replace(newIban, @"\D", match => (match.Value[0] - 55).ToString());
var remainder = BigInteger.Parse(newIban) % 97;
if (remainder != 1)
return ValidationResult.ValueFailsModule97Check;
return ValidationResult.IsValid;
}
public enum ValidationResult
{
IsValid,
ValueMissing,
ValueTooSmall,
ValueTooBig,
ValueFailsModule97Check,
CountryCodeNotKnown
}
private static readonly IDictionary<string, int> Lengths = new Dictionary<string, int>
{
{"AL", 28},
{"AD", 24},
{"AT", 20},
{"AZ", 28},
{"BE", 16},
{"BH", 22},
{"BA", 20},
{"BR", 29},
{"BG", 22},
{"CR", 21},
{"HR", 21},
{"CY", 28},
{"CZ", 24},
{"DK", 18},
{"DO", 28},
{"EE", 20},
{"FO", 18},
{"FI", 18},
{"FR", 27},
{"GE", 22},
{"DE", 22},
{"GI", 23},
{"GR", 27},
{"GL", 18},
{"GT", 28},
{"HU", 28},
{"IS", 26},
{"IE", 22},
{"IL", 23},
{"IT", 27},
{"KZ", 20},
{"KW", 30},
{"LV", 21},
{"LB", 28},
{"LI", 21},
{"LT", 20},
{"LU", 20},
{"MK", 19},
{"MT", 31},
{"MR", 27},
{"MU", 30},
{"MC", 27},
{"MD", 24},
{"ME", 22},
{"NL", 18},
{"NO", 15},
{"PK", 24},
{"PS", 29},
{"PL", 28},
{"PT", 25},
{"RO", 24},
{"SM", 27},
{"SA", 24},
{"RS", 22},
{"SK", 24},
{"SI", 19},
{"ES", 24},
{"SE", 24},
{"CH", 21},
{"TN", 24},
{"TR", 26},
{"AE", 23},
{"GB", 22},
{"VG", 24}
};
}
Demonstrating:
public class When_the_IbanValidator_is_told_to_Validate
{
[Fact]
public void It_should_return_an_error_when_there_is_no_value_provided()
{
// Assert
const string value = "";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.ValueMissing, result);
}
[Fact]
public void It_should_return_an_error_when_the_value_length_is_to_short()
{
// Assert
const string value = "BE1800165492356";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.ValueTooSmall, result);
}
[Fact]
public void It_should_return_an_error_when_the_value_length_is_to_big()
{
// Assert
const string value = "BE180016549235656";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.ValueTooBig, result);
}
[Fact]
public void It_should_return_an_error_when_the_value_fails_the_module_check()
{
// Assert
const string value = "BE18001654923566";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.ValueFailsModule97Check, result);
}
[Fact]
public void It_should_return_an_error_when_an_unkown_country_prefix_used()
{
// Assert
const string value = "XX82WEST12345698765432";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.CountryCodeNotKnown, result);
}
[Fact]
public void It_should_return_valid_when_a_valid_value_is_provided()
{
// Assert
const string value = "BE18001654923565";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.IsValid, result);
}
[Fact]
public void It_should_return_valid_when_a_valid_foreign_value_is_provided()
{
// Assert
const string value = "GB82WEST12345698765432";
var validator = new IbanValidator();
// Act
var result = validator.Validate(value);
// Assert
Assert.Equal(ValidationResult.IsValid, result);
}
}
C++
#include <string>
#include <iostream>
#include <boost/algorithm/string.hpp>
#include <map>
#include <algorithm>
#include <cctype>
using namespace boost::algorithm;
bool isValid(const std::string &ibanstring) {
static std::map<std::string, int> countrycodes {
{"AL", 28}, {"AD", 24}, {"AT", 20}, {"AZ", 28 },
{"BE", 16}, {"BH", 22}, {"BA", 20}, {"BR", 29 },
{"BG", 22}, {"CR", 21}, {"HR", 21}, {"CY", 28 },
{"CZ", 24}, {"DK", 18}, {"DO", 28}, {"EE", 20 },
{"FO", 18}, {"FI", 18}, {"FR", 27}, {"GE", 22 },
{"DE", 22}, {"GI", 23}, {"GR", 27}, {"GL", 18 },
{"GT", 28}, {"HU", 28}, {"IS", 26}, {"IE", 22 },
{"IL", 23}, {"IT", 27}, {"KZ", 20}, {"KW", 30 },
{"LV", 21}, {"LB", 28}, {"LI", 21}, {"LT", 20 },
{"LU", 20}, {"MK", 19}, {"MT", 31}, {"MR", 27 },
{"MU", 30}, {"MC", 27}, {"MD", 24}, {"ME", 22 },
{"NL", 18}, {"NO", 15}, {"PK", 24}, {"PS", 29 },
{"PL", 28}, {"PT", 25}, {"RO", 24}, {"SM", 27 },
{"SA", 24}, {"RS", 22}, {"SK", 24}, {"SI", 19 },
{"ES", 24}, {"SE", 24}, {"CH", 21}, {"TN", 24 },
{"TR", 26}, {"AE", 23}, {"GB", 22}, {"VG", 24 }
};
std::string teststring(ibanstring);
erase_all(teststring, " "); //defined in boost/algorithm/string.hpp
if (countrycodes.find(teststring.substr(0, 2)) == countrycodes.end())
return false;
if (teststring.length() != countrycodes[teststring.substr(0, 2)])
return false;
if (!all(teststring, is_alnum()))
return false;
to_upper(teststring);
std::rotate(teststring.begin(), teststring.begin() + 4, teststring.end());
std::string numberstring; // will contain the letter substitutions
for (const auto &c : teststring) {
if (std::isdigit(c))
numberstring += c;
if (std::isupper(c))
numberstring += std::to_string(static_cast<int>(c) - 55);
}
// implements a stepwise check for mod 97 in chunks of 9 at the first time,
// then in chunks of seven prepended by the last mod 97 operation converted
// to a string
int segstart = 0;
int step = 9;
std::string prepended;
long number = 0;
while (segstart < numberstring.length() - step) {
number = std::stol(prepended + numberstring.substr(segstart, step));
int remainder = number % 97;
prepended = std::to_string(remainder);
if (remainder < 10)
prepended = "0" + prepended;
segstart = segstart + step;
step = 7;
}
number = std::stol(prepended + numberstring.substr(segstart));
return (number % 97 == 1);
}
void SayValidity(const std::string &iban) {
std::cout << iban << (isValid(iban) ? " is " : " is not ") << "valid\n";
}
int main() {
SayValidity("GB82 WEST 1234 5698 7654 32");
SayValidity("GB82TEST12345698765432");
return 0;
}
- Output:
GB82 WEST 1234 5698 7654 32 is valid! GB82TEST12345698765432 is not valid!
Caché ObjectScript
Class Utils.Validate [ Abstract ]
{
ClassMethod VerifyIBAN(pIBAN As %String = "") As %Boolean
{
// remove spaces and define parts
Set iban=$Translate(pIBAN, " ")
Set cc=$Extract(iban, 1, 2)
Set cd=$Extract(iban, 3, 4)
Set bban=$Extract(iban, 5, *)
// ensure IBAN is correct format
If $Match(iban, ..GetIBANPattern(cc))=0 Quit 0
// compare result and return
Quit cd=..GetIBANCheckDigit(cc, bban)
}
ClassMethod GetIBANCheckDigit(pCC As %String, pBBAN As %String) As %Integer [ Internal, Private ]
{
Set str=pBBAN_pCC_"00"
For i=1:1 {
Set chr=$Extract(str, i) If chr="" Quit
If chr?1U Set $Extract(str, i)=$ASCII(chr)-55
}
Set cd=98-..GetModulus(str, 97)
Quit $Select($Length(cd)=2: cd, 1: "0"_cd)
}
ClassMethod GetModulus(pNum As %Integer, pDiv As %Integer) As %Integer [ Internal, Private ]
{
While $Length(pNum)>9 {
Set $Extract(pNum, 1, 9)=$Extract(pNum, 1, 9)#pDiv
}
Quit pNum#pDiv
}
ClassMethod GetIBANPattern(pCC As %String = "") As %String [ Internal, Private ]
{
Quit $Case(pCC,
"AL": "^AL\d{10}[0-9A-Z]{16}$",
"AD": "^AD\d{10}[0-9A-Z]{12}$",
"AT": "^AT\d{18}$",
"BH": "^BH\d{2}[A-Z]{4}[0-9A-Z]{14}$",
"BE": "^BE\d{14}$",
"BA": "^BA\d{18}$",
"BG": "^BG\d{2}[A-Z]{4}\d{6}[0-9A-Z]{8}$",
"HR": "^HR\d{19}$",
"CY": "^CY\d{10}[0-9A-Z]{16}$",
"CZ": "^CZ\d{22}$",
"DK": "^DK\d{16}$|^FO\d{16}$|^GL\d{16}$",
"DO": "^DO\d{2}[0-9A-Z]{4}\d{20}$",
"EE": "^EE\d{18}$",
"FI": "^FI\d{16}$",
"FR": "^FR\d{12}[0-9A-Z]{11}\d{2}$",
"GE": "^GE\d{2}[A-Z]{2}\d{16}$",
"DE": "^DE\d{20}$",
"GI": "^GI\d{2}[A-Z]{4}[0-9A-Z]{15}$",
"GR": "^GR\d{9}[0-9A-Z]{16}$",
"HU": "^HU\d{26}$",
"IS": "^IS\d{24}$",
"IE": "^IE\d{2}[A-Z]{4}\d{14}$",
"IL": "^IL\d{21}$",
"IT": "^IT\d{2}[A-Z]\d{10}[0-9A-Z]{12}$",
"KZ": "^[A-Z]{2}\d{5}[0-9A-Z]{13}$",
"KW": "^KW\d{2}[A-Z]{4}22!$",
"LV": "^LV\d{2}[A-Z]{4}[0-9A-Z]{13}$",
"LB": "^LB\d{6}[0-9A-Z]{20}$",
"LI": "^LI\d{7}[0-9A-Z]{12}$",
"LT": "^LT\d{18}$",
"LU": "^LU\d{5}[0-9A-Z]{13}$",
"MK": "^MK\d{5}[0-9A-Z]{10}\d{2}$",
"MT": "^MT\d{2}[A-Z]{4}\d{5}[0-9A-Z]{18}$",
"MR": "^MR13\d{23}$",
"MU": "^MU\d{2}[A-Z]{4}\d{19}[A-Z]{3}$",
"MC": "^MC\d{12}[0-9A-Z]{11}\d{2}$",
"ME": "^ME\d{20}$",
"NL": "^NL\d{2}[A-Z]{4}\d{10}$",
"NO": "^NO\d{13}$",
"PL": "^PL\d{10}[0-9A-Z]{,16}n$",
"PT": "^PT\d{23}$",
"RO": "^RO\d{2}[A-Z]{4}[0-9A-Z]{16}$",
"SM": "^SM\d{2}[A-Z]\d{10}[0-9A-Z]{12}$",
"SA": "^SA\d{4}[0-9A-Z]{18}$",
"RS": "^RS\d{20}$",
"SK": "^SK\d{22}$",
"SI": "^SI\d{17}$",
"ES": "^ES\d{22}$",
"SE": "^SE\d{22}$",
"CH": "^CH\d{7}[0-9A-Z]{12}$",
"TN": "^TN59\d{20}$",
"TR": "^TR\d{7}[0-9A-Z]{17}$",
"AE": "^AE\d{21}$",
"GB": "^GB\d{2}[A-Z]{4}\d{14}$",
: " ")
}
}
- Examples:
USER>For { Read iban Quit:iban="" Write " => ", ##class(Utils.Validate).VerifyIBAN(iban), ! } GB82 WEST 1234 5698 7654 32 => 1 GB82 TEST 1234 5698 7654 32 => 0 GR16 0110 1250 0000 0001 2300 695 => 1 GB29 NWBK 6016 1331 9268 19 => 1 SA03 8000 0000 6080 1016 7519 => 1 CH93 0076 2011 6238 5295 7 => 1 IL62 0108 0000 0009 9999 999 => 1 USER>
Clojure
(def explen
{"AL" 28 "AD" 24 "AT" 20 "AZ" 28 "BE" 16 "BH" 22 "BA" 20 "BR" 29
"BG" 22 "CR" 21 "HR" 21 "CY" 28 "CZ" 24 "DK" 18 "DO" 28 "EE" 20
"FO" 18 "FI" 18 "FR" 27 "GE" 22 "DE" 22 "GI" 23 "GR" 27 "GL" 18
"GT" 28 "HU" 28 "IS" 26 "IE" 22 "IL" 23 "IT" 27 "KZ" 20 "KW" 30
"LV" 21 "LB" 28 "LI" 21 "LT" 20 "LU" 20 "MK" 19 "MT" 31 "MR" 27
"MU" 30 "MC" 27 "MD" 24 "ME" 22 "NL" 18 "NO" 15 "PK" 24 "PS" 29
"PL" 28 "PT" 25 "RO" 24 "SM" 27 "SA" 24 "RS" 22 "SK" 24 "SI" 19
"ES" 24 "SE" 24 "CH" 21 "TN" 24 "TR" 26 "AE" 23 "GB" 22 "VG" 24})
(defn valid-iban? [iban]
(let [iban (apply str (remove #{\space \tab} iban))]
(cond
; Ensure upper alphanumeric input.
(not (re-find #"^[\dA-Z]+$" iban)) false
; Validate country code against expected length.
(not= (explen (subs iban 0 2)) (count iban)) false
:else
(let [rot (flatten (apply conj (split-at 4 iban)))
trans (map #(read-string (str "36r" %)) rot)]
(= 1 (mod (bigint (apply str trans)) 97))))))
(prn (valid-iban? "GB82 WEST 1234 5698 7654 32") ; true
(valid-iban? "GB82 TEST 1234 5698 7654 32")) ; false
COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. iban-main.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 iban PIC X(50).
01 iban-flag PIC X.
88 is-valid VALUE "Y", FALSE "N".
PROCEDURE DIVISION.
main-line.
MOVE "GB82 WEST 1234 5698 7654 32" TO iban
PERFORM display-validity
MOVE "GB82 TEST 1234 5698 7654 32" TO iban
PERFORM display-validity
GOBACK
.
display-validity.
CALL "validate-iban" USING CONTENT iban, REFERENCE iban-flag
IF is-valid
DISPLAY FUNCTION TRIM(iban) " is valid."
ELSE
DISPLAY FUNCTION TRIM(iban) " is not valid."
END-IF
.
END PROGRAM iban-main.
IDENTIFICATION DIVISION.
PROGRAM-ID. validate-iban.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 country-lengths-area VALUE "AD24AE23AL28AT20AZ28BA20BE16"
& "BG22BH22BR29CH21CR21CY28CZ24DE22DK18DO28EE20ES24FI18FO18F"
& "R27GB22GE22GI23GL18GR27GT28HR21HU28IE22IL23IS26IT27KW30KZ"
& "20LB28LI21LT20LU20LV21MC27MD24ME22MK19MR27MT31MU30NL18NO1"
& "5PK24PL28PS29PT25RO24RS22SA24SE24SI19SK24SM27TN24TR26VG24"
.
03 country-lengths OCCURS 64 TIMES
INDEXED BY country-lengths-idx.
05 country-code PIC XX.
05 country-len PIC 99.
01 offset PIC 99.
01 i PIC 99.
01 len PIC 99.
LINKAGE SECTION.
01 iban PIC X(50).
01 valid-flag PIC X.
88 is-valid VALUE "Y", FALSE "N".
PROCEDURE DIVISION USING iban, valid-flag.
MOVE FUNCTION UPPER-CASE(iban) TO iban
CALL "remove-spaces" USING iban
*> Check if country-code and length are correct
INITIALIZE len
INSPECT iban TALLYING len FOR CHARACTERS BEFORE SPACE
SET country-lengths-idx TO 1
SEARCH country-lengths
AT END
SET is-valid TO FALSE
GOBACK
WHEN country-code (country-lengths-idx) = iban (1:2)
IF country-len (country-lengths-idx) NOT = len
SET is-valid TO FALSE
GOBACK
END-IF
END-SEARCH
CALL "create-iban-number" USING CONTENT len, REFERENCE iban
*> Mod 97 number formed.
IF FUNCTION MOD(iban, 97) = 1
SET is-valid TO TRUE
ELSE
SET is-valid TO FALSE
END-IF
.
IDENTIFICATION DIVISION.
PROGRAM-ID. remove-spaces.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 i PIC 99.
01 offset PIC 99.
LINKAGE SECTION.
01 str PIC X(50).
PROCEDURE DIVISION USING str.
INITIALIZE offset
PERFORM VARYING i FROM 1 BY 1 UNTIL i > 50
EVALUATE TRUE
WHEN str (i:1) = SPACE
ADD 1 TO offset
WHEN offset NOT = ZERO
MOVE str (i:1) TO str (i - offset:1)
END-EVALUATE
END-PERFORM
MOVE SPACES TO str (50 - offset + 1:)
.
END PROGRAM remove-spaces.
IDENTIFICATION DIVISION.
PROGRAM-ID. create-iban-number.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 first-four PIC X(4).
01 iban-num PIC X(50).
01 digit-num PIC 99 VALUE 1.
01 i PIC 99.
01 letter-num PIC 99.
LINKAGE SECTION.
01 len PIC 99.
01 iban PIC X(50).
PROCEDURE DIVISION USING len, iban.
*> Move characters into final positions.
MOVE iban (1:4) TO first-four
MOVE iban (5:) TO iban
MOVE first-four TO iban (len - 3:)
*> Convert letters to numbers.
INITIALIZE iban-num, digit-num ALL TO VALUE
PERFORM VARYING i FROM 1 BY 1
UNTIL i > len OR iban (i:1) = SPACE
IF iban (i:1) IS NUMERIC
MOVE iban (i:1) TO iban-num (digit-num:1)
ADD 1 TO digit-num
ELSE
COMPUTE letter-num =
FUNCTION ORD(iban (i:1)) - FUNCTION ORD("A") + 10
MOVE letter-num TO iban-num (digit-num:2)
ADD 2 TO digit-num
END-IF
END-PERFORM
MOVE iban-num TO iban
.
END PROGRAM create-iban-number.
END PROGRAM validate-iban.
- Output:
GB82 WEST 1234 5698 7654 32 is valid. GB82 TEST 1234 5698 7654 32 is not valid.
Common Lisp
;;
;; List of the IBAN code lengths per country.
;;
(defvar *IBAN-code-length* '((15 . ("NO"))
(16 . ("BE"))
(18 . ("DK" "FO" "FI" "GL" "NL"))
(19 . ("MK" "SI"))
(20 . ("AT" "BA" "EE" "KZ" "LT" "LU"))
(21 . ("CR" "HR" "LV" "LI" "CH"))
(22 . ("BH" "BG" "GE" "DE" "IE" "ME" "RS" "GB"))
(23 . ("GI" "IL" "AE"))
(24 . ("AD" "CZ" "MD" "PK" "RO" "SA" "SK" "ES" "SE" "TN" "VG"))
(25 . ("PT"))
(26 . ("IS" "TR"))
(27 . ("FR" "GR" "IT" "MR" "MC" "SM"))
(28 . ("AL" "AZ" "CY" "DO" "GT" "HU" "LB" "PL"))
(29 . ("BR" "PS"))
(30 . ("KW" "MU"))
(31 . ("MT"))))
;;
;; The IBAN-character function verifies whether the number contains the correct characters only. There is
;; a built in function to verify for alphanumeric characters, but it includes characters beyond ASCII range.
;;
(defun IBAN-characters (iban)
(flet ((valid-alphanum (ch)
(or (and (char<= #\A ch)
(char>= #\Z ch))
(and (char<= #\0 ch)
(char>= #\9 ch)))))
(loop for char across iban
always (valid-alphanum char))))
;;
;; The function IBAN-length verifies that the length of the number is correct. The code lengths
;; are retrieved from the table *IBAN-code-lengths*.
;;
(defun IBAN-length (iban)
(loop :for (len . country) :in *IBAN-code-length*
:with iban-country = (subseq iban 0 2)
:do
(when (find iban-country country :test #'string=) (return (= len (length iban))))))
;;
;; The function IBAN-to-integer converts an IBAN code into an integer number.
;; Note: The conversion follows the rules stated in the wiki page.
;;
(defun IBAN-to-integer (iban)
(let ((character-base (- (char-code #\A) 10)))
(parse-integer
(format nil "~{~a~}" (map 'list #'(lambda(X) (if (alpha-char-p X) (- (char-code X) character-base) X ))
(concatenate 'string (subseq iban 4) (subseq iban 0 4)))))))
;;
;; The function IBAN-verify checks that the code contains right character set, has the
;; country specific length and has the correct check sum.
;;
(defun IBAN-verify (iban)
(flet ((validp (X) (and (IBAN-characters X)
(IBAN-length X)
(= 1 (mod (IBAN-to-integer X) 97)))))
(validp (remove #\Space iban))))
Output:
* (iban-verify "GB82 WEST 1234 5698 7654 32") T * (iban-verify "GB82 TEST 1234 5698 7654 32") NIL
D
import std.stdio, std.string, std.regex, std.conv, std.bigint,
std.algorithm, std.ascii;
immutable int[string] country2len;
static this() {
country2len = ["AL":28, "AD":24, "AT":20, "AZ":28, "BE":16,
"BH":22, "BA":20, "BR":29, "BG":22, "CR":21, "HR":21, "CY":28,
"CZ":24, "DK":18, "DO":28, "EE":20, "FO":18, "FI":18, "FR":27,
"GE":22, "DE":22, "GI":23, "GR":27, "GL":18, "GT":28, "HU":28,
"IS":26, "IE":22, "IL":23, "IT":27, "KZ":20, "KW":30, "LV":21,
"LB":28, "LI":21, "LT":20, "LU":20, "MK":19, "MT":31, "MR":27,
"MU":30, "MC":27, "MD":24, "ME":22, "NL":18, "NO":15, "PK":24,
"PS":29, "PL":28, "PT":25, "RO":24, "SM":27, "SA":24, "RS":22,
"SK":24, "SI":19, "ES":24, "SE":24, "CH":21, "TN":24, "TR":26,
"AE":23, "GB":22, "VG":24];
}
bool validIBAN(string iban) {
// Ensure upper alphanumeric input.
iban = iban.removechars(whitespace);
if (!iban.match(r"^[\dA-Z]+$"))
return false;
// Validate country code against expected length.
if (iban.length != country2len[iban[0 .. 2]])
return false;
// Shift and convert. BASE 36: 0..9,A..Z -> 0..35.
iban = iban[4 .. $] ~ iban[0 .. 4];
return iban.map!(c => [c].to!int(36).text).join.BigInt % 97 == 1;
}
void main() {
foreach (account; ["GB82 WEST 1234 5698 7654 32",
"GB82 TEST 1234 5698 7654 32"])
writefln("%s validation is: %s", account, account.validIBAN);
}
- Output:
GB82 WEST 1234 5698 7654 32 validation is: true GB82 TEST 1234 5698 7654 32 validation is: false
DBL
;
; Validate IBAN for DBL version 4 by Dario B.
;
; Compile with "dbl -r" to reinitialize all data each time
; the module (main or subroutine) is entered.
;
RECORD
J, D5
ISVALID, D1
IBAN, A35,'GB82 WEST 1234 5698 7654 32'
, A35,'GB82 TEST 1234 5698 7654 32'
, A35,'GR16 0110 1250 0000 0001 2300 695'
, A35,'GB29 NWBK 6016 1331 9268 19'
, A35,'SA03 8000 0000 6080 1016 7519'
, A35,'CH93 0076 2011 6238 5295 7'
, A35,'IL62 0108 0000 0009 9999 999'
, A35,'US12 3456 7890 0987 6543 210'
, A35,'GR16 0110 1250 0000 0001 2300 695X'
PROC
;-------------------------------------------------------------------------------
XCALL FLAGS (0007000000,1) ;Suppress STOP message
OPEN (1,O,'TT:') ;Open video
FOR J=1 UNTIL 9
DO BEGIN
XCALL CHECK_IBAN (IBAN(J),ISVALID)
IF (ISVALID.EQ.1)
THEN DISPLAY (1,IBAN(J),' is valid',10)
ELSE DISPLAY (1,IBAN(J),' is not valid',10)
END
CLOSE 1
END
;===============================================================================
; CHECK AND VALIDATE IBAN SUBROUTINE
;===============================================================================
SUBROUTINE CHECK_IBAN
IBAN, A ;IBAN code
ISVALID, D ;0=is not valid 1=is valid
RECORD
K, D5
J, D5
D2, D2
D3, D3
D9, D9
A9, A9
IBANC, A35
IINT, A80
PARTS, D2
MOD, D5
.DEFINE NC,75 ;numbers of ISO / LEN in table
ISOCK, [NC]A4,'AD24','AE23','AL28','AT20','AZ28','BA20','BE16','BG22','BH22','BR29',
&'BY28','CH21','CR22','CY28','CZ24','DE22','DK18','DO28','EE20','ES24',
&'FI18','FO18','FR27','GB22','GE22','GI23','GL18','GR27','GT28','HR21',
&'HU28','IE22','IL23','IQ23','IS26','IT27','JO30','KW30','KZ20','LB28',
&'LC32','LI21','LT20','LU20','LV21','MC27','MD24','ME22','MK19','MR27',
&'MT31','MU30','NL18','NO15','PK24','PL28','PS29','PT25','QA29','RO24',
&'RS22','SA24','SC31','SE24','SI19','SK24','SM27','ST25','SV28','TL23',
&'TN24','TR26','UA29','VG24','XK20'
PROC
;-------------------------------------------------------------------------------
CLEAR IBANC,IINT ;Not required when compiled with dbl -r
;Remove blanks
K=
FOR J=1 UNTIL %TRIM(IBAN) DO IF (IBAN(J:1).NE.' ')
BEGIN
INCR K
IBANC(K:1)=IBAN(J:1)
END
;Check ISO code and len
UPCASE IBANC
FOR J=1 UNTIL NC DO IF (IBANC(1:2).EQ.ISOCK[J](1:2)) EXITLOOP
IF (J.GT.NC)
BEGIN
;ISO not found in table, returns ISVALID=0
ISVALID=
XRETURN
END
D2=ISOCK[J](3:2)
IF (%TRIM(IBANC).NE.D2)
BEGIN
;wrong len returns ISVALID=0
ISVALID=
XRETURN
END
; Now, do this
; IBAN: GB82WEST12345698765432
; Rearrange: WEST12345698765432GB82
; Convert to integer: 3214282912345698765432161182
; Compute remainder: 3214282912345698765432161182 mod 97 = 1
;Move the first four characters to the end
IBANC(%TRIM(IBANC)+1:4)=IBANC(1:4)
IBANC=IBANC(5,$LEN(IBANC))
;Convert A to 10, B to 11 ... Z
K=1
FOR J=1 UNTIL %TRIM(IBANC)
DO BEGIN
IF (IBANC(J:1).GE.'A'.AND.IBANC(J:1).LE.'Z')
THEN BEGIN
XCALL DECML (IBANC(J:1),D3)
IINT(K:2)=D3-55,'XX'
K=K+2
END
ELSE BEGIN
IINT(K:1)=IBANC(J:1)
INCR K
END
END
;Calculate the mod 97 of large number.
;Implements a stepwise check for mod 97 in chunks of 9 at the first time,
;then in chunks of seven prepended by the last mod 97 operation converted
;to a string
PARTS=1+(%TRIM(IINT)-9)/7
IF ((PARTS-1)*7.LT.%TRIM(IINT)-9) INCR PARTS
FOR J=1 UNTIL PARTS
DO BEGIN
IF (J.EQ.1) THEN A9=IINT(1:9)
ELSE BEGIN
A9(1:2)=MOD,'XX'
A9(3:7)=IINT(10+(J-2)*7:7)
END
D9=A9
MOD=D9-(D9/97)*97
END
IF (MOD.EQ.1) ISVALID=1
ELSE ISVALID=
XRETURN
END
- Output:
GB82 WEST 1234 5698 7654 32 is valid GB82 TEST 1234 5698 7654 32 is not valid GR16 0110 1250 0000 0001 2300 695 is valid GB29 NWBK 6016 1331 9268 19 is valid SA03 8000 0000 6080 1016 7519 is valid CH93 0076 2011 6238 5295 7 is valid IL62 0108 0000 0009 9999 999 is valid US12 3456 7890 0987 6543 210 is not valid GR16 0110 1250 0000 0001 2300 695X is not valid
EasyLang
cc$[] = [ "AD" "AE" "AL" "AO" "AT" "AZ" "BA" "BE" "BF" "BG" "BH" "BI" "BJ" "BR" "CG" "CH" "CI" "CM" "CR" "CV" "CY" "CZ" "DE" "DK" "DO" "DZ" "EE" "EG" "ES" "FI" "FO" "FR" "GA" "GB" "GE" "GI" "GL" "GR" "GT" "HR" "HU" "IE" "IL" "IR" "IS" "IT" "JO" "KW" "KZ" "LB" "LI" "LT" "LU" "LV" "MC" "MD" "ME" "MG" "MK" "ML" "MR" "MT" "MU" "MZ" "NL" "NO" "PK" "PL" "PS" "PT" "QA" "RO" "RS" "SA" "SE" "SI" "SK" "SM" "SN" "TN" "TR" "UA" "VG" ]
ln[] = [ 24 23 28 25 20 28 20 16 27 22 22 16 28 29 27 21 28 27 21 25 28 24 22 18 28 24 20 27 24 18 18 27 27 22 22 23 18 27 28 21 28 22 23 26 26 27 30 30 20 28 21 20 20 21 27 24 22 27 19 28 27 31 30 25 18 15 24 28 29 25 29 24 22 24 24 19 24 27 28 24 26 29 24 ]
#
func validcc iban$ .
c$ = substr iban$ 1 2
for i to len cc$[]
if c$ = cc$[i]
return if len iban$ = ln[i]
.
.
return 0
.
func mod97 s$ .
while s$ <> ""
h$ = r div 10
h$ &= r mod 10
h$ &= substr s$ 1 13
s$ = substr s$ 14 9999
r = number h$ mod 97
.
return r
.
func isvalid hiban$ .
for c$ in strchars hiban$
c = strcode c$
if c > 32
if c >= 97 and c < 122
c -= 32
c$ = strchar c
elif c < 48 or c > 57 and c < 65 or c > 90
return 0
.
miban$ &= c$
.
.
if validcc miban$ = 0
return 0
.
for c$ in strchars substr miban$ 5 999 & substr miban$ 1 4
c = strcode c$
if c >= 65 and c <= 90
t$ &= (c - 55) div 10
t$ &= (c - 55) mod 10
else
t$ &= c$
.
.
return if mod97 t$ = 1
.
proc check s$ . .
write s$ & " is "
if isvalid s$ = 1
print "valid"
else
print "not valid"
.
.
check "GB82 WEST 1234 5698 7654 32"
check "GB82WEST12345698765432"
check "gb82 west 1234 5698 7654 32"
check "GB82 TEST 1234 5698 7654 32"
check "GB82 WEST 1243 5698 7654 32"
check "GB82 west 1243 5698 7654 32"
- Output:
GB82 WEST 1234 5698 7654 32 is valid GB82WEST12345698765432 is valid gb82 west 1234 5698 7654 32 is valid GB82 TEST 1234 5698 7654 32 is not valid GB82 WEST 1243 5698 7654 32 is not valid GB82 west 1243 5698 7654 32 is not valid
Elixir
defmodule IBAN do
@len %{ AL: 28, AD: 24, AT: 20, AZ: 28, BE: 16, BH: 22, BA: 20, BR: 29,
BG: 22, CR: 21, HR: 21, CY: 28, CZ: 24, DK: 18, DO: 28, EE: 20,
FO: 18, FI: 18, FR: 27, GE: 22, DE: 22, GI: 23, GR: 27, GL: 18,
GT: 28, HU: 28, IS: 26, IE: 22, IL: 23, IT: 27, KZ: 20, KW: 30,
LV: 21, LB: 28, LI: 21, LT: 20, LU: 20, MK: 19, MT: 31, MR: 27,
MU: 30, MC: 27, MD: 24, ME: 22, NL: 18, NO: 15, PK: 24, PS: 29,
PL: 28, PT: 25, RO: 24, SM: 27, SA: 24, RS: 22, SK: 24, SI: 19,
ES: 24, SE: 24, CH: 21, TN: 24, TR: 26, AE: 23, GB: 22, VG: 24 }
def valid?(iban) do
iban = String.replace(iban, ~r/\s/, "")
if Regex.match?(~r/^[\dA-Z]+$/, iban) do
cc = String.slice(iban, 0..1) |> String.to_atom
if String.length(iban) == @len[cc] do
{left, right} = String.split_at(iban, 4)
num = String.codepoints(right <> left)
|> Enum.map_join(fn c -> String.to_integer(c,36) end)
|> String.to_integer
rem(num,97) == 1
else
false
end
else
false
end
end
end
[ "GB82 WEST 1234 5698 7654 32",
"gb82 west 1234 5698 7654 32",
"GB82 WEST 1234 5698 7654 320",
"GB82WEST12345698765432",
"GB82 TEST 1234 5698 7654 32",
"ZZ12 3456 7890 1234 5678 12" ]
|> Enum.each(fn iban -> IO.puts "#{IBAN.valid?(iban)}\t#{iban}" end)
- Output:
true GB82 WEST 1234 5698 7654 32 false gb82 west 1234 5698 7654 32 false GB82 WEST 1234 5698 7654 320 true GB82WEST12345698765432 false GB82 TEST 1234 5698 7654 32 false ZZ12 3456 7890 1234 5678 12
F#
open System
open System.Text.RegularExpressions
// A little utility to thread a negative test result (Option.None) through a
// pipeline of tests
let inline (|~>) valOption proc =
match valOption with
| Some(value) -> proc value
| None -> None
[<EntryPoint>]
let main argv =
let iban = if argv.Length = 0 then "" else argv.[0]
iban
|> (fun iban -> // Check for illegal characters
if Regex.IsMatch(iban, @"[^0-9A-Za-z ]") then None else Some(iban.ToUpper().Replace(" ", "")))
|~> (fun iban -> // Check length per country code
let lengthPerCountry =
dict [
("AL", 28); ("AD", 24); ("AT", 20); ("AZ", 28); ("BE", 16); ("BH", 22); ("BA", 20); ("BR", 29);
("BG", 22); ("CR", 21); ("HR", 21); ("CY", 28); ("CZ", 24); ("DK", 18); ("DO", 28); ("EE", 20);
("FO", 18); ("FI", 18); ("FR", 27); ("GE", 22); ("DE", 22); ("GI", 23); ("GR", 27); ("GL", 18);
("GT", 28); ("HU", 28); ("IS", 26); ("IE", 22); ("IL", 23); ("IT", 27); ("KZ", 20); ("KW", 30);
("LV", 21); ("LB", 28); ("LI", 21); ("LT", 20); ("LU", 20); ("MK", 19); ("MT", 31); ("MR", 27);
("MU", 30); ("MC", 27); ("MD", 24); ("ME", 22); ("NL", 18); ("NO", 15); ("PK", 24); ("PS", 29);
("PL", 28); ("PT", 25); ("RO", 24); ("SM", 27); ("SA", 24); ("RS", 22); ("SK", 24); ("SI", 19);
("ES", 24); ("SE", 24); ("CH", 21); ("TN", 24); ("TR", 26); ("AE", 23); ("GB", 22); ("VG", 24);
]
let country = iban.Substring(0, Math.Min(2, iban.Length))
match lengthPerCountry.TryGetValue(country) with
| true, length -> // country should have iban of this length
if length = iban.Length then Some(iban) else None
| _ -> None // country not known
)
|~> (fun iban -> Some(iban.Substring(4) + iban.Substring(0,4)))
|~> (fun iban ->
let replaceBase36LetterWithBase10String (s : string) (c :char) = s.Replace(c.ToString(), ((int)c - (int)'A' + 10).ToString())
Some(List.fold replaceBase36LetterWithBase10String iban [ 'A' .. 'Z' ]))
|~> (fun iban -> // iban mod 97
// We could have used BigInteger, but with a loop by 7 char each
// over the long digit string we get away with Int32 arithmetic
// (as described in the Wikipedia article)
let reduceOnce r n = Int32.Parse(r.ToString() + n) % 97
let rest =
Regex.Matches(iban.Substring(2), @"\d{1,7}") |> Seq.cast |> Seq.map (fun x -> x.ToString())
|> Seq.fold reduceOnce (reduceOnce 0 (iban.Substring(0,2)))
// an iban needs a rest of 1
if rest = 1 then Some(1) else None
)
|> function | Some(_) -> "a valid IBAN" | None -> "an invalid IBAN"
|> printfn "%s is %s" iban
0
- Output:
>Rosetta.exe "GB82 WEST 1234 5698 7654 32" GB82 WEST 1234 5698 7654 32 is a valid IBAN >Rosetta.exe "GB82 TEST 1234 5698 7654 32" GB82 TEST 1234 5698 7654 32 is an invalid IBAN
Factor
USING: assocs combinators.short-circuit formatting kernel math
math.parser regexp sequences sets qw unicode ;
IN: rosetta-code.iban
<PRIVATE
CONSTANT: countries H{
{ 15 qw{ NO } }
{ 16 qw{ BE } }
{ 18 qw{ DK FO FI GL NL } }
{ 19 qw{ MK SI } }
{ 20 qw{ AT BA EE KZ LT LU } }
{ 21 qw{ CR HR LV LI CH } }
{ 22 qw{ BH BG GE DE IE ME RS GB } }
{ 23 qw{ GI IL AE } }
{ 24 qw{ AD CZ MD PK RO SA SK ES SE TN VG } }
{ 25 qw{ PT } }
{ 26 qw{ IS TR } }
{ 27 qw{ FR GR IT MR MC SM } }
{ 28 qw{ AL AZ CY DO GT HU LB PL } }
{ 29 qw{ BR PS } }
{ 30 qw{ KW MU } }
{ 31 qw{ MT } }
}
: valid-chars? ( str -- ? ) R/ [A-Z0-9]+/ matches? ;
: valid-length? ( str -- ? )
[ 2 head ] [ length ] bi countries at member? ;
: valid-checksum? ( str -- ? )
4 cut swap append [ digit> number>string ] { } map-as
concat string>number 97 mod 1 = ;
PRIVATE>
: valid-iban? ( str -- ? )
" " without {
[ valid-chars? ] [ valid-length? ] [ valid-checksum? ]
} 1&& ;
: iban-demo ( -- )
"GB82 WEST 1234 5698 7654 32"
"GB82 TEST 1234 5698 7654 32"
[
dup valid-iban? "may be a valid" "is an invalid" ?
"%s %s IBAN\n" printf
] bi@ ;
MAIN: iban-demo
- Output:
GB82 WEST 1234 5698 7654 32 may be a valid IBAN GB82 TEST 1234 5698 7654 32 is an invalid IBAN
Forth
include lib/ulcase.4th \ for S>UPPER
include lib/triple.4th \ for UT/MOD
include lib/cstring.4th \ for C/STRING
include lib/todbl.4th \ for S>DOUBLE
0 constant ud>t \ convert unsigned double to triple
88529281 constant 97^4 \ first stage modulus
char A 10 - negate +constant c>u \ convert character to IBAN digit
: bank>t u>d rot 3 - 0 ?do 10 mu* loop 1000000000 ut* ;
\ convert country part to unsigned
: country>u ( a n -- u)
c/string c>u 10000 * >r c/string c>u 100 * >r number 100 mod abs r> + r> +
;
\ convert bank part to unsigned
: bank>u \ a n -- u)
c/string c>u 1000000 * >r \ get first digit and shift
c/string c>u 10000 * >r \ get second digit and shift
c/string c>u 100 * >r \ get third digit and shift
drop c@ c>u r> + r> + r> + \ combine all digits to number
;
: iban>t ( a n -- triple)
s>upper \ convert to upper case and get country
over 4 country>u >r 4 /string \ get bank part, save length, convert
over 4 bank>u >r 4 /string tuck s>double
1000000 mu* r> -rot r> u>d d+ 2>r \ now assemble everything except bank
bank>t 2r> ud>t t+ \ shift bank part and convert to triple
;
( a n -- f)
: iban? iban>t 97^4 ut/mod 2drop 97 mod 1 = ;
\ perform modulus 97 in two stages
: checkiban ( --)
." Enter your IBAN: " refill drop 0 parse -trailing iban?
if ." Valid" else ." Invalid" then cr
;
checkiban
- Output:
linux:~> pp4th -x chkiban.4th Enter your IBAN: GB82WEST12345698765432 Valid linux:~> pp4th -x chkiban.4th Enter your IBAN: GB82TEST12345698765432 Invalid
Fortran
program ibancheck
use ISO_FORTRAN_ENV
implicit none
character(4), dimension(75) :: cc = (/ &
"AD24","AE23","AL28","AT20","AZ28","BA20","BE16","BG22","BH22","BR29", &
"BY28","CH21","CR22","CY28","CZ24","DE22","DK18","DO28","EE20","ES24", &
"FI18","FO18","FR27","GB22","GE22","GI23","GL18","GR27","GT28","HR21", &
"HU28","IE22","IL23","IQ23","IS26","IT27","JO30","KW30","KZ20","LB28", &
"LC32","LI21","LT20","LU20","LV21","MC27","MD24","ME22","MK19","MR27", &
"MT31","MU30","NL18","NO15","PK24","PL28","PS29","PT25","QA29","RO24", &
"RS22","SA24","SC31","SE24","SI19","SK24","SM27","ST25","SV28","TL23", &
"TN24","TR26","UA29","VG24","XK20" /)
character(34), dimension(12) :: ibans = (/ "GB82 WEST 1234 5698 7654 32 ", &
"GB82WEST12345698765432 ", &
"gb82 west 1234 5698 7654 32 ", &
"GB82 TEST 1234 5698 7654 32 ", &
"GR16 0110 1250 0000 0001 2300 695 ", &
"GB29 NWBK 6016 1331 9268 19 ", &
"SA03 8000 0000 6080 1016 7519 ", &
"CH93 0076 2011 6238 5295 7 ", &
"IL62 0108 0000 0009 9999 999 ", &
"IL62-0108-0000-0009-9999-999 ", &
"US12 3456 7890 0987 6543 210 ", &
"GR16 0110 1250 0000 0001 2300 695X" /)
integer :: i
do i=1, size(ibans)
if (checkIBAN(trim(ibans(i)))) then
print *, " valid IBAN: ", trim(ibans(i))
else
print *, "invalid IBAN: ", trim(ibans(i))
end if
end do
return
contains
function checkIBAN(ibancode) result(valid)
character(len=*), intent(in) :: ibancode
character(len=len(ibancode)) :: iban
logical :: valid
integer(int32) :: j, ascii, ibanSize
character(100) :: ibanRearrange, ibantoint
character(2) :: temp
valid = .false.
iban = remove_blanks(ibancode)
ibanSize = checkCountryCode(iban)
if (ibanSize == len(trim(iban))) then
ibanRearrange = iban(5:ibanSize)//iban(1:4)
ibantoint = ""
do j=1, ibanSize
ascii = ichar(ibanRearrange(j:j))
if ((ascii >= 65) .and. (ascii<=90)) then
write (temp,fmt='(I2)') ascii-55
ibantoint = trim(ibantoint) // temp
else
ibantoint = trim(ibantoint) // ibanRearrange(j:j)
end if
end do
if (mod97(ibantoint) == 1) then
valid = .true.
end if
end if
end function checkIBAN
function mod97(strint) result(res)
character(len=*), intent(in) :: strint
integer :: i, num, res
res = 0
do i=1, len(trim(strint))
read(strint(i:i),*) num
res = mod((res*10 + num),97);
end do
end function mod97
function checkCountryCode(iban) result(ibanlength)
character(len=*), intent(in) :: iban
integer(int16) :: ibanlength, i
ibanlength = 0
do i=1, size(cc)
if (iban(1:2) == cc(i)(1:2)) then
read(cc(i)(3:4),*) ibanlength
exit
end if
end do
end function checkCountryCode
Recursive Function Stripper(string,ch) Result(stripped)
Implicit None
character(len=*), intent(in) :: string
character, intent(in) :: ch
character(:), allocatable :: stripped
IF (LEN(string)==1) THEN
IF (string==ch) THEN
stripped = ''
ELSE
stripped = string
END IF
ELSE
IF (string(1:1)==ch) THEN
stripped = stripper(string(2:),ch)
ELSE
stripped = string(1:1)//stripper(string(2:),ch)
END IF
END IF
END Function stripper
Function Remove_Blanks(string) Result(stripped)
Implicit None
character(len=*), intent(in) :: string
character(:), allocatable :: stripped
stripped = trim(Stripper(trim(Stripper(string,' ')),achar(9)))
END Function Remove_Blanks
end program ibancheck
- Output:
valid IBAN: GB82 WEST 1234 5698 7654 32 valid IBAN: GB82WEST12345698765432 invalid IBAN: gb82 west 1234 5698 7654 32 invalid IBAN: GB82 TEST 1234 5698 7654 32 valid IBAN: GR16 0110 1250 0000 0001 2300 695 valid IBAN: GB29 NWBK 6016 1331 9268 19 valid IBAN: SA03 8000 0000 6080 1016 7519 valid IBAN: CH93 0076 2011 6238 5295 7 valid IBAN: IL62 0108 0000 0009 9999 999 invalid IBAN: IL62-0108-0000-0009-9999-999 invalid IBAN: US12 3456 7890 0987 6543 210 invalid IBAN: GR16 0110 1250 0000 0001 2300 695X
FreeBASIC
' FB 1.05.0 Win64
' List updated to release 72, 25 November 2016, of IBAN Registry (75 countries)
Dim Shared countryCodes As String
countryCodes = _
"AD24 AE23 AL28 AT20 AZ28 BA20 BE16 BG22 BH22 BR29 BY28 CH21 CR22 CY28 CZ24 DE22 " _
"DK18 DO28 EE20 ES24 FI18 FO18 FR27 GB22 GE22 GI23 GL18 GR27 GT28 HR21 HU28 IE22 " _
"IL23 IQ23 IS26 IT27 JO30 KW30 KZ20 LB28 LC32 LI21 LT20 LU20 LV21 MC27 MD24 ME22 " _
"MK19 MR27 MT31 MU30 NL18 NO15 PK24 PL28 PS29 PT25 QA29 RO24 RS22 SA24 SC31 SE24 " _
"SI19 SK24 SM27 ST25 SV28 TL23 TN24 TR26 UA29 VG24 XK20"
Function checkCountryCode(cc As String) As Boolean
Return Instr(countryCodes, cc)
End Function
' To avoid having to use the GMP library, a piece-wise calculation is used
Function mod97(s As String) As UInteger
Dim r As UInteger = ValULng(Left(s, 9)) Mod 97
Dim start As UInteger = 10
While start < Len(s)
r = ValULng(r & Mid(s, start, 7)) Mod 97
start += 7
Wend
Return r
End Function
Function validateIban(iban As Const String) As Boolean
' remove spaces from IBAN
Dim s As String = iban
Dim count As Integer = 0
For i As Integer = 0 To Len(s) - 1
If s[i] = 32 Then
For j As Integer = i + 1 To Len(s) - 1
s[j - 1] = s[j]
Next
count += 1
End If
If i = Len(s) - 1 - count Then Exit For
Next i
If count > 0 Then
s[Len(s) - count] = 0
Dim p As UInteger Ptr = CPtr(UInteger Ptr, @s)
*(p + 1) = Len(s) - count ''change length of string in descriptor
End If
' check country code
Dim isValid As Boolean = checkCountryCode(Left(s, 2) + Str(Len(s)))
If Not isValid Then Return False
' move first 4 characters to end
s = Mid(s, 5) + Left(s, 4)
' replace A to Z with numbers 10 To 35
For i As Integer = Len(s) To 1 Step -1
If s[i - 1] >= 65 AndAlso s[i - 1] <= 90 Then
s = Left(s, i - 1) + Str(s[i - 1] - 55) + Mid(s, i + 1)
End If
Next
' do mod97 calculation
Return mod97(s) = 1 '' remainder needs to be 1 for validity
End Function
Dim As String ibans(1 To 2) = {"GB82 WEST 1234 5698 7654 32", "GB82 TEST 1234 5698 7654 32"}
For i As Integer = 1 To 2
Dim isValid As Boolean = validateIban(ibans(i))
Print ibans(i); IIf(isValid, " : may be valid", " : is not valid")
Next
Print
Print "Press any key to quit"
Sleep
- Output:
GB82 WEST 1234 5698 7654 32 : may be valid GB82 TEST 1234 5698 7654 32 : is not valid
Free Pascal
{$mode objFPC}
uses
// for delSpace function
strUtils,
// GNU multiple-precision library
GMP;
var
/// this trie contains the correct IBAN length depending on the country code
IBANLengthInCountry: array['A'..'Z', 'A'..'Z'] of integer;
type
/// Human-readable representation of an IBAN without spaces
IBANRepresentation = string[34];
{
determines whether an IBANRepresentation is technically potentially correct
\param sample the human-readable IBAN representation
\return \param sample constains less than the most detectable errors
}
function isLegal(sample: IBANRepresentation): Boolean;
function checksumCorrect(sample: IBANRepresentation): Boolean;
type
digitSequence = string[68];
function convertLetters(const sequence: IBANRepresentation): digitSequence;
var
i: integer;
transliteration: string[2];
begin
// initialize length to zero
result := '';
for i := 1 to length(sequence) do
begin
// by default assume it is a Western-Arabic digit
transliteration := sequence[i];
// otherwise we’ll need to transliterate the Latin alphabet character
if not (sequence[i] in ['0'..'9']) then
begin
writeStr(transliteration, ord(sequence[i]) - ord('A') + 10);
end;
// appending transliteration to result also changes the length field
result := result + transliteration;
end;
end;
var
i, k: MPInteger;
begin
k := 97;
// put first four characters toward end
writeStr(sample, sample[5..length(sample)], sample[1..4]);
// interpret converted string as integer to base 10
Z_set_str(i, convertLetters(sample), 10);
i := Z_mod(i, k);
k := 1;
checksumCorrect := Z_cmp(i, k) = 0;
end;
begin
sample := upCase(sample);
{$push}
// just for emphasis: by default the FPC performs lazy evaluation
{$boolEval off}
isLegal := (length(sample) = IBANLengthInCountry[sample[1], sample[2]])
and checksumCorrect(sample);
{$pop}
end;
// === MAIN ==============================================================
begin
// for the sake of merely achieving the Rosetta Code Task
IBANLengthInCountry['G', 'B'] := 22;
// note, strings longer than 34 characters will be silently clipped
writeLn(isLegal(delSpace('GB82 WEST 1234 5698 7654 32')));
end.
- Output:
TRUE
Go
package main
import (
"regexp"
"strings"
"testing"
)
var lengthByCountryCode = map[string]int{
"AL": 28, "AD": 24, "AT": 20, "AZ": 28, "BE": 16, "BH": 22,
"BA": 20, "BR": 29, "BG": 22, "CR": 21, "HR": 21, "CY": 28,
"CZ": 24, "DK": 18, "DO": 28, "EE": 20, "FO": 18, "FI": 18,
"FR": 27, "GE": 22, "DE": 22, "GI": 23, "GR": 27, "GL": 18,
"GT": 28, "HU": 28, "IS": 26, "IE": 22, "IL": 23, "IT": 27,
"KZ": 20, "KW": 30, "LV": 21, "LB": 28, "LI": 21, "LT": 20,
"LU": 20, "MK": 19, "MT": 31, "MR": 27, "MU": 30, "MC": 27,
"MD": 24, "ME": 22, "NL": 18, "NO": 15, "PK": 24, "PS": 29,
"PL": 28, "PT": 25, "RO": 24, "SM": 27, "SA": 24, "RS": 22,
"SK": 24, "SI": 19, "ES": 24, "SE": 24, "CH": 21, "TN": 24,
"TR": 26, "AE": 23, "GB": 22, "VG": 24,
}
func isValidIBAN(iban string) bool {
if len(iban) < 4 {
return false
}
iban = regexp.MustCompile(`\s+`).ReplaceAllString(iban, "")
iban = strings.ToUpper(iban)
if ibanLen := lengthByCountryCode[iban[0:2]]; ibanLen == len(iban) {
return false
}
iban = iban[4:] + iban[:4]
result := 0
for _, ch := range iban {
var n int
if '0' <= ch && ch <= '9' {
n = int(ch) - '0'
} else if 'A' <= ch && ch <= 'Z' {
n = 10 + int(ch) - 'A'
} else {
return false
}
if n < 10 {
result = (10*result + n) % 97
} else {
result = (100*result + n) % 97
}
}
return result == 1
}
func TestIsValidIBAN(t *testing.T) {
tests := []struct {
iban string
valid bool
}{
{"GB82 WEST 1234 5698 7654 32", true},
{"GB82 TEST 1234 5698 7654 32", false},
}
for _, test := range tests {
if isValidIBAN(test.iban) == test.valid {
return
}
t.Errorf("Expected %q to be %v", test.iban, test.valid)
}
}
Groovy
def validateIBAN(String iban) {
def iso = [AL: 28, AD: 24, AT: 20, AZ: 28, BE: 16, BH: 22, BA: 20, BR: 29, BG: 22,
HR: 21, CY: 28, CZ: 24, DK: 18, DO: 28, EE: 20, FO: 18, FI: 18, FR: 27, GE: 22, DE: 22, GI: 23,
GL: 18, GT: 28, HU: 28, IS: 26, IE: 22, IL: 23, IT: 27, KZ: 20, KW: 30, LV: 21, LB: 28, LI: 21,
LT: 20, LU: 20, MK: 19, MT: 31, MR: 27, MU: 30, MC: 27, MD: 24, ME: 22, NL: 18, NO: 15, PK: 24,
PS: 29, PL: 28, PT: 25, RO: 24, SM: 27, SA: 24, RS: 22, SK: 24, SI: 19, ES: 24, SE: 24, CH: 21,
TN: 24, TR: 26, AE: 23, GB: 22, VG: 24, GR: 27, CR: 21]
iban = iban.replaceAll(/\s/, '').toUpperCase()
if (iban.size() < 4 || iso[iban[0..1]] != iban.size()) return false
iban = iban[4..-1] + iban[0..<4]
def number = iban.collect { Character.digit(it as char, 36) }.join('')
(number as BigInteger).mod(97) == 1
}
Testing:
[ 'GB82 WEST 1234 5698 7654 32',
'GB82 TEST 1234 5698 7654 32',
'GB81 WEST 1234 5698 7654 32',
'SA03 8000 0000 6080 1016 7519',
'CH93 0076 2011 6238 5295 7' ].each { iban ->
println "$iban is ${validateIBAN(iban) ? 'valid' : 'invalid'}"
}
- Output:
GB82 WEST 1234 5698 7654 32 is valid GB82 TEST 1234 5698 7654 32 is invalid GB81 WEST 1234 5698 7654 32 is invalid SA03 8000 0000 6080 1016 7519 is valid CH93 0076 2011 6238 5295 7 is valid
Haskell
This program uses the Maybe and Either monads to handle failures. Values of type 'Maybe a' can contain 'Nothing' (no value) or 'Just a' (a value of type 'a'). Values of type 'Either a b' contain 'Left b' (usually indicating failure) or 'Right c' (usually indicating success).
import Data.Char (toUpper)
import Data.Maybe (fromJust)
validateIBAN :: String -> Either String String
validateIBAN [] = Left "No IBAN number."
validateIBAN xs =
case lookupCountry of
Nothing -> invalidBecause "Country does not exist."
Just l -> if length normalized /= l
then invalidBecause "Number length does not match."
else check
where
-- remove blanks and make all letters uppercase
normalized = map toUpper $ concat $ words xs
-- get the country code
country = take 2 normalized
-- search number length
lookupCountry = lookup country countries
countries :: [(String, Int)]
countries = zip (words "AL AT BE BA BG HR CZ DO FO FR DE GR GT \
\IS IL KZ LV LI LU MT MU MD NL PK PL RO SA SK ES CH TR GB \
\AD AZ BH BR CR CY DK EE FI GE GI GL HU IE IT KW LB LT MK \
\MR MC ME NO PS PT SM RS SI SE TN AE VG")
[28,20,16,20,22,21,24,28,18,27,22,27,28,26,23,20,21,21,20,
31,30,24,18,24,28,24,24,24,24,21,26,22,24,28,22,29,21,28,18,
20,18,22,23,18,28,22,27,30,28,20,19,27,27,22,15,29,25,27,22,
19,24,24,23,24]
digits = ['0'..'9']
letters = ['A'..'Z']
-- letters to be replaced
replDigits = zip letters $ map show [10..35]
-- digits and letters allowed in the IBAN number
validDigits = digits ++ letters
-- see if all digits and letters in the IBAN number are allowed
sane = all (`elem` validDigits) normalized
-- take the first 4 digits from the number and put them at its end
(p1, p2) = splitAt 4 normalized
p3 = p2 ++ p1
-- convert the letters to numbers and
-- convert the result to an integer
p4 :: Integer
p4 = read $ concat $ map convertLetter p3
convertLetter x | x `elem` digits = [x]
| otherwise = fromJust $ lookup x replDigits
-- see if the number is valid
check = if sane
then if p4 `mod` 97 == 1
then Right xs
else invalidBecause "Validation failed."
else invalidBecause "Number contains illegal digits."
invalidBecause reason = Left $ "Invalid IBAN number " ++ xs ++
": " ++ reason
- Output:
validateIBAN "GB82 WEST 1234 5698 7654 32" Right "GB82 WEST 1234 5698 7654 32" validateIBAN "gb82 West 1234 5698 7654 32" Right "gb82 West 1234 5698 7654 32" validateIBAN "GB82 WEST 1234 5698 7654 31" Left "Invalid IBAN number GB82 WEST 1234 5698 7654 31: Validation failed." validateIBAN "GW82 WEST 1234 5698 7654 32" Left "Invalid IBAN number GW82 WEST 1234 5698 7654 32: Country does not exist." validateIBAN "GB82 WEST 1234 5698 7654 3" Left "Invalid IBAN number GB82 WEST 1234 5698 7654 3: Number length does not match." validateIBAN "GB82 _EST 1234 5698 7654 32" Left "Invalid IBAN number GB82 _EST 1234 5698 7654 32: Number contains illegal digits."
IS-BASIC
100 PROGRAM "IBAN.bas"
110 STRING CO$(1 TO 93)*2
120 NUMERIC LG(1 TO 93)
130 FOR I=1 TO 93
140 READ CO$(I),LG(I)
150 NEXT
160 DO
170 PRINT :PRINT "IBAN code: ":INPUT PROMPT ">":IB$
180 IF IB$="" THEN EXIT DO
190 IF IBAN(IB$) THEN
200 PRINT "CRC ok."
210 ELSE
220 SET #102:INK 3:PRINT "CRC error.":SET #102:INK 1
230 END IF
240 LOOP
250 DEF TRIM$(S$)
260 LET T$=""
270 FOR I=1 TO LEN(S$)
280 IF S$(I)>CHR$(47) AND S$(I)<CHR$(91) THEN LET T$=T$&S$(I)
290 NEXT
300 LET TRIM$=T$
310 END DEF
320 DEF IBAN(IB$)
330 LET IB$=TRIM$(UCASE$(IB$)):LET T$="":LET M,IBAN=0:LET N=FIND(IB$(1:2))
340 IF N=0 THEN PRINT "Invalid country code.":EXIT DEF
350 IF LEN(IB$)<>LG(N) THEN PRINT "Number length does not match.":EXIT DEF
360 LET IB$=IB$&IB$(1:4):LET IB$=IB$(5:)
370 CALL CONVERT(IB$)
380 FOR I=1 TO LEN(IB$)
390 LET T$=STR$(M)&IB$(I)
400 LET M=MOD(VAL(T$),97)
410 NEXT
420 IF M=1 THEN LET IBAN=-1
430 END DEF
440 DEF FIND(S$)
450 LET FIND=0
460 LET BO=LBOUND(CO$):LET UP=UBOUND(CO$)
470 DO
480 LET K=INT((BO+UP)/2)
490 IF CO$(K)<S$ THEN LET BO=K+1
500 IF CO$(K)>S$ THEN LET UP=K-1
510 LOOP WHILE BO<=UP AND CO$(K)<>S$
520 IF BO<=UP THEN LET FIND=K
530 END DEF
540 DEF CONVERT(REF S$)
550 LET T$=""
560 FOR I=1 TO LEN(S$)
570 IF S$(I)>CHR$(64) AND S$(I)<CHR$(91) THEN
580 LET T$=T$&STR$(ORD(S$(I))-55)
590 ELSE
600 LET T$=T$&S$(I)
610 END IF
620 NEXT
630 LET S$=T$
640 END DEF
650 DATA AD,24,AE,23,AL,28,AO,25,AT,20,AZ,28,BA,20,BE,16,BF,28,BG,22,BH,22,BI,16,BJ,28,BR,29,BY,28,CG,27,CH,21,CI,28,CM,27,CR,22,CV,25,CY,28,CZ,24,DE,22,DK,18,DO,28,DZ,24,EE,20,EG,27,ES,24,FI,18,FO,18,FR,27,GA,27,GB,22,GE,22,GI,23,GL,18
660 DATA GR,27,GT,28,HN,28,HR,21,HU,28,IE,22,IL,23,IR,26,IS,26,IT,27,JO,30,KM,27,KW,30,KZ,20,LB,28,LI,21,LT,20,LU,20,LV,21,MA,28,MC,27,MD,24,ME,22,MG,27,MK,19,ML,28,MR,27,MT,31,MU,30,MZ,25,NE,28,NI,32,NL,18,NO,15,PK,24,PL,28,PS,29,PT,25
670 DATA QA,29,RO,24,RS,22,SA,24,SE,24,SI,19,SK,24,SM,27,SN,28,TD,27,TG,28,TL,23,TN,24,TR,26,UA,29,VG,24,XK,20
J
NB. delete any blank characters
delblk =. #~ ' '&~:
NB. rearrange
rot =. '00' ,~ 2&}. @: (2&|.)
NB. characters -> "digits"
dig =. a. {~ (a.i.'0')+i.10
dig =. dig,a. {~ (a.i.'A')+i.26
todig =. dig&i.
coded =. [: ". 'x' ,~ delblk @: ": @: todig
NB. calculate check sum
cs =: 98 - 97 | coded @: rot @: delblk f.
NB. check sum as text
cstxt =. _2{. '0', [: ": cs
NB. replace first two characters
chgps =. [,2}.]
NB. shift country code
rotlc =. 2&|.
NB. insert check digits (position 3 and 4)
insertps =. chgps &.rotlc
NB. IBAN with newly calculated check digits
ibancd =: (cstxt insertps ]) f.
NB. check / generate check digits
ibancheck =: ] (]`('ok'"_) @. -:) ibancd
NB. groups of four characters
insertblk =. #~ # $ 1 1 1 1j1"_
quads =: insertblk @: delblk f.
NB. IBAN
iban =: quads @: ibancheck
- Output:
iban 'GB82 WEST 1234 5698 7654 32' ok iban 'GB99 WEST 1234 5698 7654 32' GB82 WEST 1234 5698 7654 32 iban 'GB?? WEST 1234 5698 7654 32' GB82 WEST 1234 5698 7654 32 iban 'GB??WEST12345698765432' NB. blank characters don't matter GB82 WEST 1234 5698 7654 32
Java
import java.math.BigInteger;
import java.util.*;
public class IBAN {
private static final String DEFSTRS = ""
+ "AL28 AD24 AT20 AZ28 BE16 BH22 BA20 BR29 BG22 "
+ "HR21 CY28 CZ24 DK18 DO28 EE20 FO18 FI18 FR27 GE22 DE22 GI23 "
+ "GL18 GT28 HU28 IS26 IE22 IL23 IT27 KZ20 KW30 LV21 LB28 LI21 "
+ "LT20 LU20 MK19 MT31 MR27 MU30 MC27 MD24 ME22 NL18 NO15 PK24 "
+ "PS29 PL28 PT25 RO24 SM27 SA24 RS22 SK24 SI19 ES24 SE24 CH21 "
+ "TN24 TR26 AE23 GB22 VG24 GR27 CR21";
private static final Map<String, Integer> DEFINITIONS = new HashMap<>();
static {
for (String definition : DEFSTRS.split(" "))
DEFINITIONS.put(definition.substring(0, 2), Integer.parseInt(definition.substring(2)));
}
public static void main(String[] args) {
String[] ibans = {
"GB82 WEST 1234 5698 7654 32",
"GB82 TEST 1234 5698 7654 32",
"GB81 WEST 1234 5698 7654 32",
"SA03 8000 0000 6080 1016 7519",
"CH93 0076 2011 6238 5295 7",
"XX00 0000",
"",
"DE",
"DE13 äöü_ 1234 1234 1234 12"};
for (String iban : ibans)
System.out.printf("%s is %s.%n", iban, validateIBAN(iban) ? "valid" : "not valid");
}
static boolean validateIBAN(String iban) {
iban = iban.replaceAll("\\s", "").toUpperCase(Locale.ROOT);
int len = iban.length();
if (len < 4 || !iban.matches("[0-9A-Z]+") || DEFINITIONS.getOrDefault(iban.substring(0, 2), 0) != len)
return false;
iban = iban.substring(4) + iban.substring(0, 4);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++)
sb.append(Character.digit(iban.charAt(i), 36));
BigInteger bigInt = new BigInteger(sb.toString());
return bigInt.mod(BigInteger.valueOf(97)).intValue() == 1;
}
}
- Output:
GB82 WEST 1234 5698 7654 32 is valid. GB82 TEST 1234 5698 7654 32 is not valid. GB81 WEST 1234 5698 7654 32 is not valid. SA03 8000 0000 6080 1016 7519 is valid. CH93 0076 2011 6238 5295 7 is valid. XX00 0000 is not valid. is not valid. DE is not valid. DE13 äöü_ 1234 1234 1234 12 is not valid.
JavaScript
var ibanLen = {
NO:15, BE:16, DK:18, FI:18, FO:18, GL:18, NL:18, MK:19,
SI:19, AT:20, BA:20, EE:20, KZ:20, LT:20, LU:20, CR:21,
CH:21, HR:21, LI:21, LV:21, BG:22, BH:22, DE:22, GB:22,
GE:22, IE:22, ME:22, RS:22, AE:23, GI:23, IL:23, AD:24,
CZ:24, ES:24, MD:24, PK:24, RO:24, SA:24, SE:24, SK:24,
VG:24, TN:24, PT:25, IS:26, TR:26, FR:27, GR:27, IT:27,
MC:27, MR:27, SM:27, AL:28, AZ:28, CY:28, DO:28, GT:28,
HU:28, LB:28, PL:28, BR:29, PS:29, KW:30, MU:30, MT:31
}
function isValid(iban) {
iban = iban.replace(/\s/g, '')
if (!iban.match(/^[\dA-Z]+$/)) return false
var len = iban.length
if (len != ibanLen[iban.substr(0,2)]) return false
iban = iban.substr(4) + iban.substr(0,4)
for (var s='', i=0; i<len; i+=1) s+=parseInt(iban.charAt(i),36)
for (var m=s.substr(0,15)%97, s=s.substr(15); s; s=s.substr(13)) m=(m+s.substr(0,13))%97
return m == 1
}
document.write(isValid('GB82 WEST 1234 5698 7654 32'), '<br>') // true
document.write(isValid('GB82 WEST 1.34 5698 7654 32'), '<br>') // false
document.write(isValid('GB82 WEST 1234 5698 7654 325'), '<br>') // false
document.write(isValid('GB82 TEST 1234 5698 7654 32'), '<br>') // false
document.write(isValid('SA03 8000 0000 6080 1016 7519'), '<br>') // true
- Output:
true false false false true
jq
This implementation requires a version of jq with gsub.
The heart of the matter consists of just four lines of straightforward jq code:
# strip the input string of spaces and tabs:
gsub("[ \t]";"")
# check the string is ALPHAnumeric
| test("^[A-Z0-9]+$")
# check its length is as determined by the country code:
and length == $lengths[.[0:2]]
# check the mod 97 criterion:
and ( (.[4:] + .[0:4]) | letters2digits | remainder) == 1
This conciseness is achieved courtesy of the helper functions: letters2digits and remainder. These could be implemented as inner functions of the main function, but for clarity they are shown as top-level functions here.
def letters2digits:
65 as $A | 90 as $Z
| ($A - 10) as $ten
| explode
| map( if $A <= . and . <= $Z
then (. - $ten) | tostring
else [.] | implode
end )
| join("");
# jq currently does not have unlimited-precision integer arithmetic
# and so we define a special-purpose "mod 97" filter:
# input: a string representing a decimal
# output: its remainder modulo 97 as a number
def remainder:
if length < 15 then tonumber % 97
else (.[0:14] | remainder | tostring) as $r1
| ($r1 + .[14:]) | remainder
end;
def is_valid_iban:
{
"AL": 28, "AD": 24, "AT": 20, "AZ": 28, "BE": 16, "BH": 22, "BA": 20, "BR": 29,
"BG": 22, "CR": 21, "HR": 21, "CY": 28, "CZ": 24, "DK": 18, "DO": 28, "EE": 20,
"FO": 18, "FI": 18, "FR": 27, "GE": 22, "DE": 22, "GI": 23, "GR": 27, "GL": 18,
"GT": 28, "HU": 28, "IS": 26, "IE": 22, "IL": 23, "IT": 27, "KZ": 20, "KW": 30,
"LV": 21, "LB": 28, "LI": 21, "LT": 20, "LU": 20, "MK": 19, "MT": 31, "MR": 27,
"MU": 30, "MC": 27, "MD": 24, "ME": 22, "NL": 18, "NO": 15, "PK": 24, "PS": 29,
"PL": 28, "PT": 25, "RO": 24, "SM": 27, "SA": 24, "RS": 22, "SK": 24, "SI": 19,
"ES": 24, "SE": 24, "CH": 21, "TN": 24, "TR": 26, "AE": 23, "GB": 22, "VG": 24
} as $lengths
# Ignore spaces and tabs, and check input is ALPHAnumeric:
| gsub("[ \t]";"")
| test("^[A-Z0-9]+$")
# Validate country code against expected length:
and length == $lengths[.[0:2]]
# Shift and convert:
and ( (.[4:] + .[0:4]) | letters2digits | remainder) == 1 ;
Examples
"GB82 WEST 1234 5698 7654 32" | is_valid_iban #=> true
"GB82 TEST 1234 5698 7654 32" | is_valid_iban #=> false
Jsish
From the Javascript entry.
var ibanLen = {
NO:15, BE:16, DK:18, FI:18, FO:18, GL:18, NL:18, MK:19,
SI:19, AT:20, BA:20, EE:20, KZ:20, LT:20, LU:20, CR:21,
CH:21, HR:21, LI:21, LV:21, BG:22, BH:22, DE:22, GB:22,
GE:22, IE:22, ME:22, RS:22, AE:23, GI:23, IL:23, AD:24,
CZ:24, ES:24, MD:24, PK:24, RO:24, SA:24, SE:24, SK:24,
VG:24, TN:24, PT:25, IS:26, TR:26, FR:27, GR:27, IT:27,
MC:27, MR:27, SM:27, AL:28, AZ:28, CY:28, DO:28, GT:28,
HU:28, LB:28, PL:28, BR:29, PS:29, KW:30, MU:30, MT:31
};
function isIBAN(iban) {
var i,m,s;
iban = iban.replace(/\s/g, '');
if (!iban.match(/^[0-9A-Z]+$/)) return false;
var len = iban.length;
if (len != ibanLen[iban.substr(0,2)]) return false;
iban = iban.substr(4) + iban.substr(0,4);
for (s = '', i = 0; i < len; i += 1) s += parseInt(iban.charAt(i),36);
for (m = s.substr(0, 15) % 97, s = s.substr(15); s; s = s.substr(13)) m = (m + s.substr(0,13)) % 97;
return m == 1;
}
;isIBAN('GB82 WEST 1234 5698 7654 32');
;isIBAN('GB82 WEST 1.34 5698 7654 32');
;isIBAN('GB82 WEST 1234 5698 7654 325');
;isIBAN('GB82 TEST 1234 5698 7654 32');
;isIBAN('SA03 8000 0000 6080 1016 7519');
/*
=!EXPECTSTART!=
isIBAN('GB82 WEST 1234 5698 7654 32') ==> true
isIBAN('GB82 WEST 1.34 5698 7654 32') ==> false
isIBAN('GB82 WEST 1234 5698 7654 325') ==> false
isIBAN('GB82 TEST 1234 5698 7654 32') ==> false
isIBAN('SA03 8000 0000 6080 1016 7519') ==> true
=!EXPECTEND!=
*/
- Output:
prompt$ jsish -u IBAN.jsi [PASS] IBAN.jsi
Julia
function validiban(iban::AbstractString)
country2length = Dict(
"AL" => 28, "AD" => 24, "AT" => 20, "AZ" => 28, "BE" => 16, "BH" => 22, "BA" => 20, "BR" => 29,
"BG" => 22, "CR" => 21, "HR" => 21, "CY" => 28, "CZ" => 24, "DK" => 18, "DO" => 28, "EE" => 20,
"FO" => 18, "FI" => 18, "FR" => 27, "GE" => 22, "DE" => 22, "GI" => 23, "GR" => 27, "GL" => 18,
"GT" => 28, "HU" => 28, "IS" => 26, "IE" => 22, "IL" => 23, "IT" => 27, "KZ" => 20, "KW" => 30,
"LV" => 21, "LB" => 28, "LI" => 21, "LT" => 20, "LU" => 20, "MK" => 19, "MT" => 31, "MR" => 27,
"MU" => 30, "MC" => 27, "MD" => 24, "ME" => 22, "NL" => 18, "NO" => 15, "PK" => 24, "PS" => 29,
"PL" => 28, "PT" => 25, "RO" => 24, "SM" => 27, "SA" => 24, "RS" => 22, "SK" => 24, "SI" => 19,
"ES" => 24, "SE" => 24, "CH" => 21, "TN" => 24, "TR" => 26, "AE" => 23, "GB" => 22, "VG" => 24)
# Ensure upper alphanumeric input.
iban = replace(iban, r"\s", "")
rst = ismatch(r"^[\dA-Z]+$", iban)
# Validate country code against expected length.
rst = rst && length(iban) == country2length[iban[1:2]]
# Shift and convert.
iban = iban[5:end] * iban[1:4]
digs = parse(BigInt, join(parse(Int, ch, 36) for ch in iban))
return rst && digs % 97 == 1
end
- Output:
validiban("GB82 WEST 1234 5698 7654 32") = true validiban("GB82 TEST 1234 5698 7654 32") = false
Kotlin
// version 1.0.6
import java.math.BigInteger
object IBAN {
/* List updated to release 73, January 2017, of IBAN Registry (75 countries) */
private const val countryCodes = "" +
"AD24 AE23 AL28 AT20 AZ28 BA20 BE16 BG22 BH22 BR29 " +
"BY28 CH21 CR22 CY28 CZ24 DE22 DK18 DO28 EE20 ES24 " +
"FI18 FO18 FR27 GB22 GE22 GI23 GL18 GR27 GT28 HR21 " +
"HU28 IE22 IL23 IQ23 IS26 IT27 JO30 KW30 KZ20 LB28 " +
"LC32 LI21 LT20 LU20 LV21 MC27 MD24 ME22 MK19 MR27 " +
"MT31 MU30 NL18 NO15 PK24 PL28 PS29 PT25 QA29 RO24 " +
"RS22 SA24 SC31 SE24 SI19 SK24 SM27 ST25 SV28 TL23 " +
"TN24 TR26 UA29 VG24 XK20"
fun isValid(iban: String): Boolean {
// remove spaces from IBAN
var s = iban.replace(" ", "")
// check country code and length
s.substring(0, 2) + s.length in countryCodes || return false
// move first 4 characters to the end
s = s.substring(4) + s.substring(0, 4)
// replace A to Z with numbers 10 To 35
s = s.replace(Regex("[A-Z]")) { (10 + (it.value[0] - 'A')).toString() }
// check whether mod 97 calculation gives a remainder of 1
return BigInteger(s) % BigInteger.valueOf(97L) == BigInteger.ONE
}
}
fun main() {
val ibans = arrayOf(
"GB82 WEST 1234 5698 7654 32",
"GB82 TEST 1234 5698 7654 32"
)
for (iban in ibans) {
val valid = IBAN.isValid(iban)
println(iban + if (valid) " may be valid" else " is not valid")
}
}
- Output:
GB82 WEST 1234 5698 7654 32 may be valid GB82 TEST 1234 5698 7654 32 is not valid
With IBAN REGISTRY parser
package rosettacode
import rosettacode.Outcome.Fail
import rosettacode.Outcome.Ok
import java.lang.IllegalArgumentException
import java.math.BigInteger
sealed class Outcome {
object Ok : Outcome() {
override fun toString(): String = "Ok"
}
data class Fail(val error: String) : Outcome()
}
fun Boolean.asOutcome(error: String = "") = when (this) {
true -> Ok
false -> Fail(error)
}
/**
* A validator based on a pattern (e.g GB2!n4!a6!n8!n) as defined in the IBAN REGISTRY
* Doesn't handle variable length IBANs - sufficient for Rosetta
*
* IBAN REGISTRY: https://www.swift.com/swift_resource/9606
*/
class Validator(private val pattern: String) {
private val triplet: Regex = """(\d+)!([n,a,c])""".toRegex() // e.g: 10!n
private val rules: List<Regex>
init {
val countTypePairs = triplet
.findAll(pattern)
.map { Pair(it.groups[1]!!.value.toInt(), it.groups[2]!!.value) }
rules = countTypePairs.fold(emptyList()) { acc, p ->
acc + when (p.second) {
"n" -> """[0-9]{${p.first}}""".toRegex()
"a" -> """[A-Z]{${p.first}}""".toRegex()
"c" -> """[A-Z,0-9]{${p.first}}""".toRegex()
else -> throw IllegalArgumentException("Unexpected pattern: $pattern")
}
}
}
fun isValid(ccbban: String): Outcome {
var k = 0
for (rule in rules) {
when (val match = rule.matchAt(ccbban, k)) {
null -> return Fail("failed rule: $rule")
else -> {
k = match.range.last + 1
}
}
}
return (k == ccbban.length).asOutcome("IBAN is too long")
}
}
object Registry : MutableMap<String, Validator> by mutableMapOf()
fun String.split(i: Int) = Pair(this.substring(0, i), this.substring(i))
fun String.toDigits() = this.fold("") { acc, ch -> acc + Character.getNumericValue(ch) }
fun Pair<String, String>.swap() = this.second + this.first
object CDCheck {
private val _97 = BigInteger.valueOf(97)
private val _1 = BigInteger.ONE
operator fun invoke(digits: String) = (BigInteger(digits).mod(_97) == _1).asOutcome("Invalid Check Digits")
}
fun validate(iban: String): Outcome {
val normalized = iban.replace(" ", "").uppercase().also {
if (it.length < 5) {
return Fail("IBAN is too short")
}
}
val (cc, tail) = normalized.split(2)
val validator = Registry[cc] ?: return Fail("Unknown Country Code: $cc")
return when (val outcome = validator.isValid(tail)) {
is Fail -> outcome
is Ok -> {
val allDigits = normalized
.split(4).swap() // (CC + CD) -> end
.toDigits() // letters -> digits
CDCheck(allDigits)
}
}
}
fun main() {
Registry["GB"] = Validator("GB2!n4!a6!n8!n")
Registry["DE"] = Validator("DE2!n8!n10!n")
validate("GB82WEST12345698765432").also { println(it) } // Ok
validate("GB82 WEST 1234 5698 7654 32").also { println(it) } // Ok
validate("DE89370400440532013000").also { println(it) } // Ok
validate("XX82WEST1234569A765432").also { println(it) } // Unknown Country Code: XX
validate("GB82").also { println(it) } // IBAN is too short
validate("GB82WEST1234569A765432").also { println(it) } // failed rule: [0-9]{8}
validate("GB82WE5T1234569A765432").also { println(it) } // failed rule: [A-Z]{4}
validate("GB82WEST123456987654329").also { println(it) } // IBAN is too long
validate("GB80WEST12345698765432").also { println(it) } // Invalid Check Digits
}
- Output:
Ok Ok Ok Fail(error=Unknown Country Code: XX) Fail(error=IBAN is too short) Fail(error=failed rule: [0-9]{8}) Fail(error=failed rule: [A-Z]{4}) Fail(error=IBAN is too long) Fail(error=Invalid Check Digits)
Lobster
let cc = ["AD","AE","AL","AO","AT","AZ","BA","BE","BF","BG","BH","BI","BJ","BR","CG","CH","CI","CM","CR","CV","CY",
"CZ","DE","DK","DO","DZ","EE","EG","ES","FI","FO","FR","GA","GB","GE","GI","GL","GR","GT","HR","HU","IE",
"IL","IR","IS","IT","JO","KW","KZ","LB","LI","LT","LU","LV","MC","MD","ME","MG","MK","ML","MR","MT","MU",
"MZ","NL","NO","PK","PL","PS","PT","QA","RO","RS","SA","SE","SI","SK","SM","SN","TN","TR","UA","VG"]
let ln = [ 24, 23, 28, 25, 20, 28, 20, 16, 27, 22, 22, 16, 28, 29, 27, 21, 28, 27, 21, 25, 28,
24, 22, 18, 28, 24, 20, 27, 24, 18, 18, 27, 27, 22, 22, 23, 18, 27, 28, 21, 28, 22,
23, 26, 26, 27, 30, 30, 20, 28, 21, 20, 20, 21, 27, 24, 22, 27, 19, 28, 27, 31, 30,
25, 18, 15, 24, 28, 29, 25, 29, 24, 22, 24, 24, 19, 24, 27, 28, 24, 26, 29, 24 ]
def ccToLen(s: string) -> int:
let cnt, idx = binary_search(cc, s.substring(0, 2))
if cnt == 1:
return ln[idx]
else:
return -1
def strip(s: string) -> string:
let t = s.tokenize(" ", " ") // splits on spaces and trims spaces from segments
return t.concat_string("") // joins it back together
def rotmod97step(accumu: int, c: int) -> int:
accumu *= 10
if '0' <= c and c <= '9':
accumu += c - '0'
else: if 'A' <= c and c <= 'Z':
accumu *= 10
accumu += c + 10 - 'A'
else:
return -1
while accumu >= 97:
accumu -= 97
return accumu
def rotmod97(s: string) -> int:
// the first four chars come last
// all chars from 'A' to 'Z' => "10" to "35"
let len = s.length
var accumu = 0 // result; negative indicates error
var i = 4 // starting index
while i < len and accumu >= 0:
accumu = rotmod97step(accumu, s[i])
i += 1
i = 0
while i < 4 and accumu >= 0:
accumu = rotmod97step(accumu, s[i])
i += 1
return accumu
def isValidIBAN(iban: string) -> bool:
let s = strip(uppercase(iban))
if s.length == ccToLen(s):
return rotmod97(s) == 1
else:
return false
assert isValidIBAN("GB82 WEST 1234 5698 7654 32")
assert isValidIBAN("GB82 West 1234 5698 7654 32")
assert not isValidIBAN("GB82 TEST 1234 5698 7654 32")
assert not isValidIBAN("GB82 WEST 1243 5698 7654 32")
Logtalk
:- object(iban).
:- info([
version is 0.1,
author is 'Paulo Moura',
date is 2015/10/11,
comment is 'IBAN validation example using DCG rules.'
]).
:- public(valid/1).
valid(IBAN) :-
phrase(iban, IBAN), !.
iban -->
country_code(Code), check_digits(Check), bban(BBAN),
{(BBAN*1000000 + Code*100 + Check) mod 97 =:= 1}.
country_code(Code) -->
letter_digits(L1, D3, D2), letter_digits(L0, D1, D0),
{country_code([L1, L0]), Code is D3*1000 + D2*100 + D1*10 + D0}.
check_digits(Check) -->
digit(D1), digit(D0),
{Check is D1*10 + D0}.
bban(BBAN) -->
bban_codes(Digits),
{digits_to_integer(Digits, BBAN, Count), Count =< 30}.
bban_codes(Ds) -->
" ", bban_codes(Ds).
bban_codes([D| Ds]) -->
digit(D), bban_codes(Ds).
bban_codes([D1, D0| Ds]) -->
letter_digits(_, D1, D0), bban_codes(Ds).
bban_codes([]) -->
[].
digit(D) -->
[C],
{0'0 =< C, C =< 0'9, D is C - 0'0}.
letter_digits(C, D1, D0) -->
[C],
{ ( 0'A =< C, C =< 0'Z ->
D is C - 0'A + 10
; 0'a =< C, C =< 0'z,
D is C - 0'a + 10
),
D1 is D div 10,
D0 is D mod 10
}.
digits_to_integer(Digits, BBAN, Count) :-
digits_to_integer(Digits, 0, BBAN, 0, Count).
digits_to_integer([], BBAN, BBAN, Count, Count).
digits_to_integer([Digit| Digits], BBAN0, BBAN, Count0, Count) :-
BBAN1 is BBAN0 * 10 + Digit,
Count1 is Count0 + 1,
digits_to_integer(Digits, BBAN1, BBAN, Count1, Count).
country_code("AL").
country_code("AD").
country_code("AT").
country_code("AZ").
country_code("BE").
country_code("BH").
country_code("BA").
country_code("BR").
country_code("BG").
country_code("CR").
country_code("HR").
country_code("CY").
country_code("CZ").
country_code("DK").
country_code("DO").
country_code("EE").
country_code("FO").
country_code("FI").
country_code("FR").
country_code("GE").
country_code("DE").
country_code("GI").
country_code("GR").
country_code("GL").
country_code("GT").
country_code("HU").
country_code("IS").
country_code("IE").
country_code("IL").
country_code("IT").
country_code("KZ").
country_code("KW").
country_code("LV").
country_code("LB").
country_code("LI").
country_code("LT").
country_code("LU").
country_code("MK").
country_code("MT").
country_code("MR").
country_code("MU").
country_code("MC").
country_code("MD").
country_code("ME").
country_code("NL").
country_code("NO").
country_code("PK").
country_code("PS").
country_code("PL").
country_code("PT").
country_code("RO").
country_code("SM").
country_code("SA").
country_code("RS").
country_code("SK").
country_code("SI").
country_code("ES").
country_code("SE").
country_code("CH").
country_code("TN").
country_code("TR").
country_code("AE").
country_code("GB").
country_code("VG").
:- end_object.
Testing:
| ?- iban::valid("GB82 WEST 1234 5698 7654 32").
yes
Lua
local length=
{
AL=28, AD=24, AT=20, AZ=28, BH=22, BE=16, BA=20, BR=29, BG=22, CR=21,
HR=21, CY=28, CZ=24, DK=18, DO=28, EE=20, FO=18, FI=18, FR=27, GE=22,
DE=22, GI=23, GR=27, GL=18, GT=28, HU=28, IS=26, IE=22, IL=23, IT=27,
JO=30, KZ=20, KW=30, LV=21, LB=28, LI=21, LT=20, LU=20, MK=19, MT=31,
MR=27, MU=30, MC=27, MD=24, ME=22, NL=18, NO=15, PK=24, PS=29, PL=28,
PT=25, QA=29, RO=24, SM=27, SA=24, RS=22, SK=24, SI=19, ES=24, SE=24,
CH=21, TN=24, TR=26, AE=23, GB=22, VG=24
}
function validate(iban)
iban=iban:gsub("%s","")
local l=length[iban:sub(1,2)]
if not l or l~=#iban or iban:match("[^%d%u]") then
return false -- invalid character, country code or length
end
local mod=0
local rotated=iban:sub(5)..iban:sub(1,4)
for c in rotated:gmatch(".") do
mod=(mod..tonumber(c,36)) % 97
end
return mod==1
end
M2000 Interpreter
We make a lambda function which return a string, with the input IBAN plus (Valid) or (Invalid) at the end.
\\ IBAN checker
Function MakeIBANfun$ {
Inventory countrylength = "AL" := 28, "AD" := 24, "AT" := 20, "AZ" := 28, "BE" := 16, "BH" := 22, "BA" := 20, "BR" := 29
Append countrylength, "BG" := 22, "CR" := 21, "HR" := 21, "CY" := 28, "CZ" := 24, "DK" := 18, "DO" := 28, "EE" := 20
Append countrylength, "FO" := 18, "FI" := 18, "FR" := 27, "GE" := 22, "DE" := 22, "GI" := 23, "GR" := 27, "GL" := 18
Append countrylength, "GT" := 28, "HU" := 28, "IS" := 26, "IE" := 22, "IL" := 23, "IT" := 27, "KZ" := 20, "KW" := 30
Append countrylength, "LV" := 21, "LB" := 28, "LI" := 21, "LT" := 20, "LU" := 20, "MK" := 19, "MT" := 31, "MR" := 27
Append countrylength, "MU" := 30, "MC" := 27, "MD" := 24, "ME" := 22, "NL" := 18, "NO" := 15, "PK" := 24, "PS" := 29
Append countrylength, "PL" := 28, "PT" := 25, "RO" := 24, "SM" := 27, "SA" := 24, "RS" := 22, "SK" := 24, "SI" := 19
Append countrylength, "ES" := 24, "SE" := 24, "CH" := 21, "TN" := 24, "TR" := 26, "AE" := 23, "GB" := 22, "VG" := 24
=Lambda$ countrylength (Iban0$)->{
Iban$=Filter$(Ucase$(Iban0$), " ")
Iban$=Filter$(Iban$, Filter$(Iban$,"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"))
Def Decimal ch, c
{
If Not Exist(countrylength, Left$(Iban$,2)) Then Exit
length=Eval(countrylength)
If Not Len(Iban$)=length Then exit
Buffer ScanChar as Integer*length
Return ScanChar, 0:=Mid$(Iban$,5), length-4:=Mid$(Iban$,1,4)
For i=0 to length-1 {
ch=Eval(ScanChar, i)
if ch>=48 and ch<=57 then {
c = c*10+ch-48
} else.if ch>=65 and ch<=90 then {
c = c*100+ch-55
} else c=-1: exit
}
c = c mod 97
}
=Iban0$ + If$(c=1 ->" (Valid)", " (Invalid)")
}
}
IbanCheck$=MakeIBANfun$()
Print IbanCheck$("GB82 WEST 1234 5698 7654 32") ' valid
Print IbanCheck$("GB82 TEST 1234 5698 7654 32")
Print IbanCheck$("SA03 8000 0000 6080 1016 7519") ' valid
Print IbanCheck$("GR16 0110 1250 0000 0001 2300 695X")
Print IbanCheck$("MK11 2222 3333 4444 555")
Mathematica / Wolfram Language
CountryCodes={{"AL",28},{"AD",24},{"AT",20},{"AZ",28},{"BE",16},{"BH",22},{"BA",20},{"BR",29},{"BG",22},{"CR",21},{"HR",21},{"CY",28},{"CZ",24},{"DK",18},{"DO",28},{"EE",20},{"FO",18},{"FI",18},{"FR",27},{"GE",22},{"DE",22},{"GI",23},{"GR",27},{"GL",18},{"GT",28},{"HU",28},{"IS",26},{"IE",22},{"IL",23},{"IT",27},{"KZ",20},{"KW",30},{"LV",21},{"LB",28},{"LI",21},{"LT",20},{"LU",20},{"MK",19},{"MT",31},{"MR",27},{"MU",30},{"MC",27},{"MD",24},{"ME",22},{"NL",18},{"NO",15},{"PK",24},{"PS",29},{"PL",28},{"PT",25},{"RO",24},{"SM",27},{"SA",24},{"RS",22},{"SK",24},{"SI",19},{"ES",24},{"SE",24},{"CH",21},{"TN",24},{"TR",26},{"AE",23},{"GB",22},{"VG",24}};
ClearAll[IBANVerify]
IBANVerify[input_String]:=Module[{i,cc,rules},
i=StringReplace[StringTrim[input],{" "->"","\t"->""}];
cc=StringTake[i,2];
If[MemberQ[CountryCodes[[All,1]],cc]
,
cc=Select[CountryCodes,First[#]==cc&][[1,2]];
If[cc==StringLength[i]
,
i=StringRotateLeft[i,4];
i=Characters[ToUpperCase[i]];
rules=Rule@@@({CharacterRange["A","Z"],Range[10,35]}\[Transpose]);
i=i/.rules;
i=ToExpression/@i;
i=FromDigits[Flatten[IntegerDigits/@i]];
If[Mod[i,97]===1
,
True
,
False
]
,
False
]
,
False
]
]
Trying out the function:
IBANVerify["GB82 WEST 1234 5698 7654 32"]
IBANVerify["GB82 WEST 1234 5698 7654 323"]
IBANVerify["GB82 WEST 1234 5698 7654 31"]
IBANVerify["XX82 WEST 1234 5698 7654 323"]
- Output:
True False False False
MATLAB
Didn't check country codes, lengths, or consistent checksums to keep this applicable to any new countries.
function valid = validateIBAN(iban)
% Determine if International Bank Account Number is valid IAW ISO 13616
% iban - string containing account number
if length(iban) < 5
valid = false;
else
iban(iban == ' ') = ''; % Remove spaces
iban = lower([iban(5:end) iban(1:4)])+0; % Rearrange and convert
iban(iban > 96 & iban < 123) = iban(iban > 96 & iban < 123)-87; % Letters
iban(iban > 47 & iban < 58) = iban(iban > 47 & iban < 58)-48; % Numbers
valid = piecewiseMod97(iban) == 1;
end
end
function result = piecewiseMod97(x)
% Conduct a piecewise version of mod(x, 97) to support large integers
% x is a vector of integers
x = sprintf('%d', x); % Get to single-digits per index
nDig = length(x);
i1 = 1;
i2 = min(9, nDig);
prefix = '';
while i1 <= nDig
y = str2double([prefix x(i1:i2)]);
result = mod(y, 97);
prefix = sprintf('%d', result);
i1 = i2+1;
i2 = min(i1+8, nDig);
end
end
Usage:
tests = {'GB82 WEST 1234 5698 7654 32' ;
'GB82 TEST 1234 5698 7654 32' ;
'CH93 0076 2011 6238 5295 7' ;
'SA03 8000 0000 6080 1016 7519' ;
'SA03 1234 5678 9101 1121 3141' ;
'GB29 NWBK 6016 1331 9268 19' ;
'GB29' ;
'GR16 0110 1250 0000 0001 2300 695'};
for k = 1:length(tests)
fprintf('%s -> %svalid\n', tests{k}, char(~validateIBAN(tests{k}).*'in'))
end
- Output:
GB82 WEST 1234 5698 7654 32 -> valid GB82 TEST 1234 5698 7654 32 -> invalid CH93 0076 2011 6238 5295 7 -> valid SA03 8000 0000 6080 1016 7519 -> valid SA03 1234 5678 9101 1121 3141 -> invalid GB29 NWBK 6016 1331 9268 19 -> valid GB29 -> invalid GR16 0110 1250 0000 0001 2300 695 -> valid
NewLISP
(setq *iban-code-length* '((15 ("NO"))
(16 ("BE"))
(18 ("DK" "FO" "FI" "GL" "NL"))
(19 ("MK" "SI"))
(20 ("AT" "BA" "EE" "KZ" "LT" "LU"))
(21 ("CR" "HR" "LV" "LI" "CH"))
(22 ("BH" "BG" "GE" "DE" "IE" "ME" "RS" "GB"))
(23 ("GI" "IL" "AE"))
(24 ("AD" "CZ" "MD" "PK" "RO" "SA" "SK" "ES" "SE" "TN" "VG"))
(25 ("PT"))
(26 ("IS" "TR"))
(27 ("FR" "GR" "IT" "MR" "MC" "SM"))
(28 ("AL" "AZ" "CY" "DO" "GT" "HU" "LB" "PL"))
(29 ("BR" "PS"))
(30 ("KW" "MU"))
(31 ("MT"))))
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Remove spaces and set upper case.
(define (sanitize-iban iban)
(upper-case (replace " " iban ""))
)
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Check that only A-Z and 0-9 are used.
(define (valid-chars? iban)
(setq rx (string "[A-Z0-9]{" (length iban) "}" ))
(regex rx iban 1)
)
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Check that the length is correct for the country.
(define (valid-length? iban)
(setq countries-found (lookup (int (length iban)) *iban-code-length*))
(if (not (nil? countries-found))
(member (0 2 iban) countries-found)
)
)
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Convert the IBAN to integer following the rules from Wikipedia.
(define (iban-to-integer iban)
(setq country-code (0 2 iban))
(setq checksum (2 2 iban))
(setq iban (string (4 iban) country-code))
(setq iban (join (map (lambda (x) (if (regex "[0-9]" x) x (string (- (char x) 55)))) (explode iban))))
(bigint (string iban checksum))
)
;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;; Test if IBAN is correct (true) or not (nil):
;; (valid-iban? "GB82 WEST 1234 5698 7654 32") ==> true
;; (valid-iban? "GB82 TEST 1234 5698 7654 32") ==> nil
(define (valid-iban? iban)
(setq iban (sanitize-iban iban))
(and
(valid-chars? iban)
(valid-length? iban)
(= 1L (% (iban-to-integer iban) 97))
)
)
Output:
(valid-iban? "GB82 WEST 1234 5698 7654 32") true (valid-iban? "GB82 TEST 1234 5698 7654 32") nil
Nim
import tables, strutils, re, bigints
let countryLen = toTable({
"AL": 28, "AD": 24, "AT": 20, "AZ": 28, "BE": 16, "BH": 22, "BA": 20, "BR": 29,
"BG": 22, "CR": 21, "HR": 21, "CY": 28, "CZ": 24, "DK": 18, "DO": 28, "EE": 20,
"FO": 18, "FI": 18, "FR": 27, "GE": 22, "DE": 22, "GI": 23, "GR": 27, "GL": 18,
"GT": 28, "HU": 28, "IS": 26, "IE": 22, "IL": 23, "IT": 27, "KZ": 20, "KW": 30,
"LV": 21, "LB": 28, "LI": 21, "LT": 20, "LU": 20, "MK": 19, "MT": 31, "MR": 27,
"MU": 30, "MC": 27, "MD": 24, "ME": 22, "NL": 18, "NO": 15, "PK": 24, "PS": 29,
"PL": 28, "PT": 25, "RO": 24, "SM": 27, "SA": 24, "RS": 22, "SK": 24, "SI": 19,
"ES": 24, "SE": 24, "CH": 21, "TN": 24, "TR": 26, "AE": 23, "GB": 22, "VG": 24})
proc validIban(iban: string): bool =
# Ensure upper alphanumeric input
var iban = iban.replace(" ","").replace("\t","")
if not iban.match(re"^[\dA-Z]+$"):
return false
# Validate country code against expected length
if iban.len != countryLen[iban[0..1]]:
return false
# Shift and convert
iban = iban[4..iban.high] & iban[0..3]
var digits = ""
for ch in iban:
case ch
of '0'..'9': digits.add($(ch.ord - '0'.ord))
of 'A'..'Z': digits.add($(ch.ord - 'A'.ord + 10))
else: discard
result = initBigInt(digits) mod 97 == 1
for account in ["GB82 WEST 1234 5698 7654 32", "GB82 TEST 1234 5698 7654 32"]:
echo account, " validation is: ", validIban account
- Output:
GB82 WEST 1234 5698 7654 32 validation is: true GB82 TEST 1234 5698 7654 32 validation is: false
Oberon-2
Works with oo2c Version 2
MODULE IBAN;
IMPORT
Out,
Err,
ADT:Dictionary,
Object:Boxed,
Object:BigInt,
Object,
Strings,
IntStr;
TYPE
IBANLen = Boxed.LongInt;
VAR
nations: Dictionary.Dictionary(STRING,IBANLen);
PROCEDURE Check*(iban: ARRAY OF CHAR): BOOLEAN;
VAR
country,ibanStr: Object.String;
nLetter: ARRAY 3 OF CHAR;
block: ARRAY 5 OF CHAR;
numIban: ARRAY 256 OF CHAR;
num,den,res: BigInt.BigInt;
ibanLen: IBANLen;
i: LONGINT;
BEGIN
ibanStr := Object.NewLatin1(iban);
country := ibanStr.Substring(0,2);
IF ~nations.HasKey(country) THEN
Err.Object("Country " + country + " has not IBAN codes. ");
RETURN FALSE;
END;
ibanLen := nations.Get(country);
IF SHORT(ibanLen.value) # Strings.Length(iban) THEN
Err.Object("IBAN length incorrect for " + country +". ");
RETURN FALSE
END;
block[0] := 0X;
Strings.Extract(iban,0,4,block);
Strings.Delete(iban,0,4);Strings.Append(block,iban);
numIban[0] := 0X;
FOR i := 0 TO LEN(iban) - 1 DO
nLetter[0] := 0X;
IF (iban[i] >= 'A') & (iban[i] <= 'Z') THEN
IntStr.IntToStr(ORD(iban[i]) - ORD('A') + 10,nLetter);
ELSE
nLetter[0] := iban[i];
nLetter[1] := 0X
END;
Strings.Append(nLetter,numIban);
END;
Strings.Append(0X,numIban);
num := BigInt.New(Object.NewLatin1(numIban),10);
den := BigInt.New("97",10);
res := num.Mod(den);
IF res.Equals(BigInt.one) THEN
RETURN TRUE
ELSE
Err.String("IBAN code check failed. ");
RETURN FALSE
END
END Check;
PROCEDURE CodeLengthFor*(country: ARRAY OF CHAR): LONGINT;
VAR
countryStr: Object.String;
ibanLen: IBANLen;
BEGIN
countryStr := Object.NewLatin1(country);
ibanLen := Boxed.zeroLongInt;
IF nations.HasKey(countryStr) THEN
ibanLen := nations.Get(countryStr)
END;
RETURN ibanLen.value
END CodeLengthFor;
PROCEDURE Test*;
PROCEDURE DoCheck(iban: ARRAY OF CHAR);
BEGIN
Out.String("IBAN[");Out.String(iban);Out.String("]=");Out.Bool(Check(iban));
Out.Ln
END DoCheck;
BEGIN
DoCheck("CH9300762011623852957");
DoCheck("GB82WEST12345698765432");
DoCheck("SA0380000000608010167519");
DoCheck("XX0380000000608010167519");
END Test;
BEGIN
nations := NEW(Dictionary.Dictionary(STRING,Boxed.LongInt));
nations.Set("AL",NEW(IBANLen,28));
nations.Set("AD",NEW(IBANLen,24));
nations.Set("AT",NEW(IBANLen,20));
nations.Set("AZ",NEW(IBANLen,28));
nations.Set("BE",NEW(IBANLen,16));
nations.Set("BH",NEW(IBANLen,22));
nations.Set("BA",NEW(IBANLen,20));
nations.Set("BR",NEW(IBANLen,29));
nations.Set("BG",NEW(IBANLen,22));
nations.Set("CR",NEW(IBANLen,21));
nations.Set("HR",NEW(IBANLen,21));
nations.Set("CY",NEW(IBANLen,28));
nations.Set("CZ",NEW(IBANLen,24));
nations.Set("DK",NEW(IBANLen,18));
nations.Set("DO",NEW(IBANLen,28));
nations.Set("EE",NEW(IBANLen,20));
nations.Set("FO",NEW(IBANLen,18));
nations.Set("FI",NEW(IBANLen,18));
nations.Set("FR",NEW(IBANLen,27));
nations.Set("GE",NEW(IBANLen,22));
nations.Set("DE",NEW(IBANLen,22));
nations.Set("GI",NEW(IBANLen,23));
nations.Set("GR",NEW(IBANLen,27));
nations.Set("GL",NEW(IBANLen,18));
nations.Set("GT",NEW(IBANLen,28));
nations.Set("HU",NEW(IBANLen,28));
nations.Set("IS",NEW(IBANLen,26));
nations.Set("IE",NEW(IBANLen,22));
nations.Set("IL",NEW(IBANLen,23));
nations.Set("IT",NEW(IBANLen,27));
nations.Set("KZ",NEW(IBANLen,20));
nations.Set("KW",NEW(IBANLen,30));
nations.Set("LV",NEW(IBANLen,21));
nations.Set("LB",NEW(IBANLen,28));
nations.Set("LI",NEW(IBANLen,21));
nations.Set("LT",NEW(IBANLen,20));
nations.Set("LU",NEW(IBANLen,20));
nations.Set("MK",NEW(IBANLen,19));
nations.Set("MT",NEW(IBANLen,31));
nations.Set("MR",NEW(IBANLen,27));
nations.Set("MU",NEW(IBANLen,30));
nations.Set("MC",NEW(IBANLen,27));
nations.Set("MD",NEW(IBANLen,24));
nations.Set("ME",NEW(IBANLen,22));
nations.Set("NL",NEW(IBANLen,18));
nations.Set("NO",NEW(IBANLen,15));
nations.Set("PK",NEW(IBANLen,24));
nations.Set("PS",NEW(IBANLen,29));
nations.Set("PL",NEW(IBANLen,28));
nations.Set("PT",NEW(IBANLen,25));
nations.Set("RO",NEW(IBANLen,24));
nations.Set("SM",NEW(IBANLen,27));
nations.Set("SA",NEW(IBANLen,24));
nations.Set("RS",NEW(IBANLen,22));
nations.Set("SK",NEW(IBANLen,24));
nations.Set("SI",NEW(IBANLen,19));
nations.Set("ES",NEW(IBANLen,24));
nations.Set("SE",NEW(IBANLen,24));
nations.Set("CH",NEW(IBANLen,21));
nations.Set("TN",NEW(IBANLen,24));
nations.Set("TR",NEW(IBANLen,26));
nations.Set("AE",NEW(IBANLen,23));
nations.Set("GB",NEW(IBANLen,22));
nations.Set("VG",NEW(IBANLen,24));
Test;
END IBAN.
Output:
IBAN[CH9300762011623852957]=TRUE IBAN[GB82WEST12345698765432]=TRUE IBAN[SA0380000000608010167519]=TRUE IBAN[XX0380000000608010167519]=Country XX not has IBAN codes. FALSE
OCaml
#load "str.cma"
#load "nums.cma" (* for module Big_int *)
(* Countries and length of their IBAN. *)
(* Taken from https://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country *)
let countries = [
("AL", 28); ("AD", 24); ("AT", 20); ("AZ", 28); ("BH", 22); ("BE", 16);
("BA", 20); ("BR", 29); ("BG", 22); ("CR", 21); ("HR", 21); ("CY", 28);
("CZ", 24); ("DK", 18); ("DO", 28); ("TL", 23); ("EE", 20); ("FO", 18);
("FI", 18); ("FR", 27); ("GE", 22); ("DE", 22); ("GI", 23); ("GR", 27);
("GL", 18); ("GT", 28); ("HU", 28); ("IS", 26); ("IE", 22); ("IL", 23);
("IT", 27); ("JO", 30); ("KZ", 20); ("XK", 20); ("KW", 30); ("LV", 21);
("LB", 28); ("LI", 21); ("LT", 20); ("LU", 20); ("MK", 19); ("MT", 31);
("MR", 27); ("MU", 30); ("MC", 27); ("MD", 24); ("ME", 22); ("NL", 18);
("NO", 15); ("PK", 24); ("PS", 29); ("PL", 28); ("PT", 25); ("QA", 29);
("RO", 24); ("SM", 27); ("SA", 24); ("RS", 22); ("SK", 24); ("SI", 19);
("ES", 24); ("SE", 24); ("CH", 21); ("TN", 24); ("TR", 26); ("AE", 23);
("GB", 22); ("VG", 24); ("DZ", 24); ("AO", 25); ("BJ", 28); ("BF", 27);
("BI", 16); ("CM", 27); ("CV", 25); ("IR", 26); ("CI", 28); ("MG", 27);
("ML", 28); ("MZ", 25); ("SN", 28); ("UA", 29)
]
(* Put the countries in a Hashtbl for faster search... *)
let tbl_countries =
let htbl = Hashtbl.create (List.length countries) in
let _ = List.iter (fun (k, v) -> Hashtbl.add htbl k v) countries in
htbl
(* Delete spaces and put all letters in upper case. *)
let clean_iban iban =
Str.global_replace (Str.regexp " +") "" iban
|> String.uppercase_ascii
(* Each country has an IBAN with a specific length. *)
let check_length ib =
let iso_length = List.hd countries |> fst |> String.length in
let country_code = String.sub ib 0 iso_length in
try
Hashtbl.find tbl_countries country_code = String.length ib
with
Not_found -> false
(* Convert a string into a list of chars. *)
let charlist_of_string s =
let l = String.length s in
let rec doloop i =
if i >= l then []
else s.[i] :: doloop (i + 1)
in
doloop 0
(* Letters are associated to values: A=10, B=11, ..., Z=35. *)
let val_of_char c =
match c with
| '0' .. '9' -> int_of_char c - int_of_char '0'
| 'A' .. 'Z' -> int_of_char c - int_of_char 'A' + 10
| _ -> failwith (Printf.sprintf "Character not allowed: %c" c)
(* Compute the mod-97 value and check it is equal to 1. *)
let check_mod97 ib =
let l = String.length ib
and taken = 4 in
let prefix = String.sub ib 0 taken
and rest = String.sub ib taken (l - taken) in
let newval = rest ^ prefix (* move the 4 initial characters to the end of the string *)
|> charlist_of_string (* convert the string into a list of chars *)
|> List.map val_of_char (* convert each char into its integer value *)
|> List.map string_of_int (* convert the integers into strings... *)
|> List.fold_left (^) "" in (* ...and concatenate said strings *)
(* Now compute the mod-97 using the Big Integers provided by OCaml, and
* compare the result to 1. *)
Big_int.eq_big_int
(Big_int.mod_big_int (Big_int.big_int_of_string newval)
(Big_int.big_int_of_int 97))
(Big_int.big_int_of_int 1)
(* Do the validation as described in the Wikipedia article at
* https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN *)
let validate iban =
let ib = clean_iban iban in
check_length ib && check_mod97 ib
let () =
let ibans = [
("GB82 WEST 1234 5698 7654 32", true);
("GB82 TEST 1234 5698 7654 32", false);
("GB81 WEST 1234 5698 7654 32", false);
("GB82 WEST 1234 5698 7654 3", false);
("SA03 8000 0000 6080 1016 7519", true);
("CH93 0076 2011 6238 5295 7", true);
("\"Completely incorrect iban\"", false)
] in
let testit (ib, exp) =
let res = validate ib in
Printf.printf "%s is %svalid. Expected %b [%s]\n"
ib (if res then "" else "not ")
exp (if res = exp then "PASS" else "FAIL")
in
List.iter (fun pair -> testit pair) ibans
- Output:
GB82 WEST 1234 5698 7654 32 is valid. Expected true [PASS] GB82 TEST 1234 5698 7654 32 is not valid. Expected false [PASS] GB81 WEST 1234 5698 7654 32 is not valid. Expected false [PASS] GB82 WEST 1234 5698 7654 3 is not valid. Expected false [PASS] SA03 8000 0000 6080 1016 7519 is valid. Expected true [PASS] CH93 0076 2011 6238 5295 7 is valid. Expected true [PASS] "Completely incorrect iban" is not valid. Expected false [PASS]
PascalABC.NET
var
countryLen := Dict(
('AL', 28), ('AD', 24), ('AT', 20), ('AZ', 28), ('BE', 16), ('BH', 22), ('BA', 20), ('BR', 29),
('BG', 22), ('CR', 21), ('HR', 21), ('CY', 28), ('CZ', 24), ('DK', 18), ('DO', 28), ('EE', 20),
('FO', 18), ('FI', 18), ('FR', 27), ('GE', 22), ('DE', 22), ('GI', 23), ('GR', 27), ('GL', 18),
('GT', 28), ('HU', 28), ('IS', 26), ('IE', 22), ('IL', 23), ('IT', 27), ('KZ', 20), ('KW', 30),
('LV', 21), ('LB', 28), ('LI', 21), ('LT', 20), ('LU', 20), ('MK', 19), ('MT', 31), ('MR', 27),
('MU', 30), ('MC', 27), ('MD', 24), ('ME', 22), ('NL', 18), ('NO', 15), ('PK', 24), ('PS', 29),
('PL', 28), ('PT', 25), ('RO', 24), ('SM', 27), ('SA', 24), ('RS', 22), ('SK', 24), ('SI', 19),
('ES', 24), ('SE', 24), ('CH', 21), ('TN', 24), ('TR', 26), ('AE', 23), ('GB', 22), ('VG', 24));
function validIban(iban: string): boolean;
begin
result := False;
iban := iban.Replace(' ', '').Replace(#9, '');
if not iban.All(c -> c.isupper or c.isdigit) then exit;
if iban.Length <> countryLen.Get(iban[1:3]) then exit;
iban := iban[5:iban.Length + 1] + iban[1:5];
var digits: string := '';
foreach var ch in iban do
case ch of
'0'..'9': digits += ch;
'A'..'Z': digits += (Ord(ch) - Ord('A') + 10).ToString;
end;
result := digits.ToBigInteger mod 97 = 1;
end;
begin
foreach var account in |'GB82 WEST 1234 5698 7654 32', 'GB82 TEST 1234 5698 7654 32'| do
Println(account, 'validation is:', validIban(account));
end.
- Output:
GB82 WEST 1234 5698 7654 32 validation is: True GB82 TEST 1234 5698 7654 32 validation is: False
Perl
#!/usr/bin/perl
use strict ;
use warnings ;
use Math::BigInt ;
my %countrycodelengths = ( "AL" => 28, "AD" => 24, "AT" => 20, "AZ" => 28,
"BE" => 16, "BH" => 22, "BA" => 20, "BR" => 29,
"BG" => 22, "CR" => 21, "HR" => 21, "CY" => 28,
"CZ" => 24, "DK" => 18, "DO" => 28, "EE" => 20,
"FO" => 18, "FI" => 18, "FR" => 27, "GE" => 22,
"DE" => 22, "GI" => 23, "GR" => 27, "GL" => 18,
"GT" => 28, "HU" => 28, "IS" => 26, "IE" => 22,
"IL" => 23, "IT" => 27, "KZ" => 20, "KW" => 30,
"LV" => 21, "LB" => 28, "LI" => 21, "LT" => 20,
"LU" => 20, "MK" => 19, "MT" => 31, "MR" => 27,
"MU" => 30, "MC" => 27, "MD" => 24, "ME" => 22,
"NL" => 18, "NO" => 15, "PK" => 24, "PS" => 29,
"PL" => 28, "PT" => 25, "RO" => 24, "SM" => 27,
"SA" => 24, "RS" => 22, "SK" => 24, "SI" => 19,
"ES" => 24, "SE" => 24, "CH" => 21, "TN" => 24,
"TR" => 26, "AE" => 23, "GB" => 22, "VG" => 24 ) ;
sub validate_iban {
my $ibanstring = shift ;
$ibanstring =~ s/\s+//g ;
return 0 unless $ibanstring =~ /[0-9a-zA-Z]+/ ;
$ibanstring = uc $ibanstring ;
return 0 if ( not exists $countrycodelengths{ substr( $ibanstring , 0 , 2 ) } );
return 0 if length ( $ibanstring ) != $countrycodelengths{ substr( $ibanstring , 0 , 2 ) } ;
$ibanstring =~ s/(.{4})(.+)/$2$1/ ;
$ibanstring =~ s/([A-Z])/ord( $1 ) - 55/eg ;
my $number = Math::BigInt->new( $ibanstring ) ;
if ( $number->bmod( 97 ) == 1 ) {
return 1 ;
}
else {
return 0 ;
}
}
if ( validate_iban( "GB82 WEST 1234 5698 7654 32" ) ) {
print "GB82 WEST 1234 5698 7654 32 is a valid IBAN number!\n" ;
}
else {
print "Sorry! GB82 WEST 1234 5698 7654 32 is not valid!\n" ;
}
if ( validate_iban( "GB82TEST12345698765432" ) ) {
print "GB82TEST12345698765432 is valid!\n" ;
}
- Output:
GB82 WEST 1234 5698 7654 32 is a valid IBAN number! GB82TEST12345698765432 is invalid!
Phix
-- -- demo\rosetta\IBAN.exw -- ===================== -- constant nations = {{"AD",24}, -- Andorra -- (full list in distro) {"CH",21}, -- Switzerland {"GB",22}, -- United Kingdom {"SA",24}, -- Saudi Arabia {"XK",20}} -- Kosovo constant {countries,lengths} = columnize(nations) function iban(string code) -- This routine does and should reject codes containing spaces etc. -- Use iban_s() below for otherwise. integer country = find(code[1..2],countries) if country!=0 and length(code)=lengths[country] then code = code[5..$]&code[1..4] integer c = 0 for i=1 to length(code) do integer ch=code[i] if ch>='0' and ch<='9' then c = c*10+ch-'0' elsif ch>='A' and ch<='Z' then c = c*100+ch-55 else return false end if c = mod(c,97) end for return c=1 end if return false end function function iban_s(string code) -- strips any embedded spaces and hyphens before validating. code = substitute_all(code,{" ","-"},{"",""}) return iban(code) end function procedure test(string code, bool expected) bool valid = iban_s(code) string state if valid=expected then state = iff(valid?"ok":"invalid (as expected)") else state = iff(valid?"OK!!":"INVALID!!")&" (NOT AS EXPECTED)" end if printf(1,"%-34s :%s\n",{code,state}) end procedure test("GB82 WEST 1234 5698 7654 32",true) test("GB82 TEST 1234 5698 7654 32",false) test("GB81 WEST 1234 5698 7654 32",false) test("SA03 8000 0000 6080 1016 7519",true) test("CH93 0076 2011 6238 5295 7",true)
- Output:
GB82 WEST 1234 5698 7654 32 :ok GB82 TEST 1234 5698 7654 32 :invalid (as expected) GB81 WEST 1234 5698 7654 32 :invalid (as expected) SA03 8000 0000 6080 1016 7519 :ok CH93 0076 2011 6238 5295 7 :ok
PHP
<?php
function piece_wise($iban_all_digits) {
$remainder = NULL;
$slice = 9;
for ($i=0; $i<strlen($iban_all_digits); $i=$i+$slice)
{
if ($i>0)
{
$slice = 7;
}
$part = $remainder . substr($iban_all_digits, $i, $slice);
//echo "REMAINDER: " . $remainder . "<br>";
//echo "PART: $part" . "<br>";
$remainder = intval($part) % 97;
}
return $remainder;
}
$iban = "GB82 WEST 1234 5698 7654 32";
//remove space
$iban = str_replace(' ', '', $iban);
//echo $iban; echo '<br>';
$iban_length = strlen($iban);
$country_code = substr($iban, 0, 2);
/*
IBAN lengths are country specific
full list available at
https://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country
*/
$lengths = ['GB' => 22];
if ($lengths[$country_code] != $iban_length)
{
exit ("IBAN length not valid for $country_code");
}
// 2. move first four characters to the end
$iban = substr($iban, 4) . substr($iban, 0, 4);
//3. Replace letters in IBAN with digits
//(A=10, B=11 ... Z=35)
$iban_arr = str_split($iban, 1);
$iban_all_digits = '';
foreach ($iban_arr as $key=>$value)
{
if (ctype_alpha($value))
{
$value = ord($value) - 55;
}
$iban_all_digits = $iban_all_digits . $value;
}
if (piece_wise($iban_all_digits) === 1)
{
echo "VALID IBAN!";
}
else
{
echo "IBAN NOT VALID";
}
Picat
go =>
IBAN = "GB82 WEST 1234 5698 7654 32",
println(IBAN=iban(IBAN)),
nl.
iban(IBAN) = Ret =>
nations(Nations),
Ret = [],
IBAN2 = IBAN.delete_all(' ').to_uppercase(),
Len = IBAN2.length,
Len > 2, % sanity check
if Len != Nations.get(slice(IBAN2,1,2),0).to_integer() then
Ret := ["wrong country length"]
end,
IBAN3 = slice(IBAN2,5,Len) ++ slice(IBAN2,1,4),
if [convert(C) : C in IBAN3].join('').to_integer() mod 97 != 1 then
Ret := Ret ++ ["bad mod"]
end,
if Ret == [] then Ret := "ok" end.
% convert char: 1=1,...9=9, A=10, B=11,Z=35
convert(C) = cond(ord(C)-65 >= 0,ord(C)-55,ord(C)-48).to_string().
% Lengths for different nations.
nations(Nations) =>
Nations = new_map(
["AL"=28, "AD"=24, "AT"=20, "AZ"=28, "BH"=22, "BE"=16, "BA"=20, "BR"=29, "BG"=22, "CR"=21,
"HR"=21, "CY"=28, "CZ"=24, "DK"=18, "DO"=28, "EE"=20, "FO"=18, "FI"=18, "FR"=27, "GE"=22,
"DE"=22, "GI"=23, "GR"=27, "GL"=18, "GT"=28, "HU"=28, "IS"=26, "IE"=22, "IL"=23, "IT"=27,
"JO"=30, "KZ"=20, "KW"=30, "LV"=21, "LB"=28, "LI"=21, "LT"=20, "LU"=20, "MK"=19, "MT"=31,
"MR"=27, "MU"=30, "MC"=27, "MD"=24, "ME"=22, "NL"=18, "NO"=15, "PK"=24, "PS"=29, "PL"=28,
"PT"=25, "QA"=29, "RO"=24, "SM"=27, "SA"=24, "RS"=22, "SK"=24, "SI"=19, "ES"=24, "SE"=24,
"CH"=21, "TN"=24, "TR"=26, "AE"=23, "GB"=22, "VG"=24]).
- Output:
GB82 WEST 1234 5698 7654 32 = ok
Some more (and interesting) tests
go2 =>
Ibans = ["GB82 WEST 1234 5698 7654 32",
"gb82 WEST 1234 5698 7654 32",
"GB82WEST12345698765432",
"GB82 WEST 234 5698 7654 32",
"GB82 WEST 1234 5698 7654 33",
"AE82 WEST 1234 5698 7654 32",
"GB82 TEST 1234 5698 7654 32",
"GR16 0110 1250 0000 0001 2300 695",
"GB29 NWBK 6016 1331 9268 19",
"SA03 8000 0000 6080 1016 7519",
"CH93 0076 2011 6238 5295 7",
"IL62 0108 0000 0009 9999 999"
],
foreach(IBAN in Ibans)
println(IBAN=iban(IBAN))
end,
nl.
- Output:
GB82 WEST 1234 5698 7654 32 = ok gb82 WEST 1234 5698 7654 32 = ok GB82WEST12345698765432 = ok GB82 WEST 234 5698 7654 32 = [wrong country length,bad mod] GB82 WEST 1234 5698 7654 33 = [bad mod] AE82 WEST 1234 5698 7654 32 = [wrong country length,bad mod] GB82 TEST 1234 5698 7654 32 = [bad mod] GR16 0110 1250 0000 0001 2300 695 = ok GB29 NWBK 6016 1331 9268 19 = ok SA03 8000 0000 6080 1016 7519 = ok CH93 0076 2011 6238 5295 7 = ok IL62 0108 0000 0009 9999 999 = ok
PicoLisp
(setq *Sizes '((GB . 22) (CH . 21) (SA . 24)))
(de iban (Str)
(let Lst
(filter
'((X) (not (sp? X)))
(chop (uppc Str)) )
(when
(=
(cdr (assoc (pack (head 2 Lst)) *Sizes))
(length Lst) )
(%
(format
(mapcar
'((X)
(if (upp? X)
(- (char X) 55)
X ) )
(append (nth Lst 5) (head 4 Lst)) ) )
97 ) ) ) )
(for I '("sa03 8000 0000 6080 1016 7519"
"CH9300762011623852957"
"gb82west1234 56987654 32"
"GB82WEST000")
(if (= 1 (iban I))
(println 'Valid)
(println 'Invalid) ) )
(bye)
PowerShell
function verifIBAN ([string]$ibanS)
{
if ($ibanS.Length -ne 27) {return $false} else
{
$ibanI="$($ibanS.Substring(4,23))$($ibanS.Substring(0,4))".ToUpper()
[int]$comptIBAN=0
$NumIBAN=""
while ($comptIBAN -lt 27)
{
if ([byte]$ibanI[$comptIBAN] -ge 65 -and [byte]$ibanI[$comptIBAN] -le 90)
{
$NumIban+=([byte]$ibanI[$comptIBAN]-55)
} #pour transformer les lettres en chiffres (A=10, B=11...)
else
{
$NumIban+=$ibanI[$comptIBAN]
}
$comptIBAN++
}
#cela fait un nombre de 30 chiffres : trop pour powershell, je découpe donc les 9 premiers caractères :
if ("$($NumIBAN.Substring(0,9)%97)$($NumIBAN.Substring(9,$NumIBAN.Length-9))"%97 -eq 1)
{return $true}
else
{return $false}
}
} #fin fonction vérification IBAN / Stéphane RABANY 2018
Ancien texte qui ne sert à rien selon moi : I have heard that Regex should not be used with IBAN codes. Regex does seem to work, however.
@'
"Country","Length","Example"
"Albania",28,"AL47212110090000000235698741"
"Andorra",24,"AD1200012030200359100100"
"Austria",20,"AT611904300235473201"
"Belgium",16,"BE68539007547034"
"Bosnia and Herzegovina",20,"BA391290079401028494"
"Bulgaria",22,"BG80BNBG96611020345678"
"Croatia",21,"HR1210010051863000160"
"Cyprus",28,"CY17002001280000001200527600"
"Czech Republic",24,"CZ6508000000192000145399"
"Denmark",18,"DK5000400440116243"
"Estonia",20,"EE382200221020145685"
"Faroe Islands",18,"FO1464600009692713"
"Finland",18,"FI2112345600000785"
"France",27,"FR1420041010050500013M02606"
"Georgia",22,"GE29NB0000000101904917"
"Germany",22,"DE89370400440532013000"
"Gibraltar",23,"GI75NWBK000000007099453"
"Greece",27,"GR1601101250000000012300695"
"Greenland",18,"GL8964710001000206"
"Hungary",28,"HU42117730161111101800000000"
"Iceland",26,"IS140159260076545510730339"
"Ireland",22,"IE29AIBK93115212345678"
"Italy",27,"IT60X0542811101000000123456"
"Kosovo",20,"XK051212012345678906"
"Latvia",21,"LV80BANK0000435195001"
"Liechtenstein",21,"LI21088100002324013AA"
"Lithuania",20,"LT121000011101001000"
"Luxembourg",20,"LU280019400644750000"
"Macedonia",19,"MK07300000000042425"
"Malta",31,"MT84MALT011000012345MTLCAST001S"
"Moldova",24,"MD24AG000225100013104168"
"Monaco",27,"MC5813488000010051108001292"
"Montenegro",22,"ME25505000012345678951"
"Netherlands",18,"NL91ABNA0417164300"
"Norway",15,"NO9386011117947"
"Poland",28,"PL27114020040000300201355387"
"Portugal",25,"PT50000201231234567890154"
"Romania",24,"RO49AAAA1B31007593840000"
"San Marino",27,"SM86U0322509800000000270100"
"Serbia",22,"RS35260005601001611379"
"Slovakia",24,"SK3112000000198742637541"
"Slovenia",19,"SI56191000000123438"
"Spain",24,"ES9121000418450200051332"
"Sweden",24,"SE3550000000054910000003"
"Switzerland",21,"CH9300762011623852957"
"Ukraine",29,"UA573543470006762462054925026"
"United Kingdom",22,"GB29NWBK60161331926819"
'@ -split "`r`n" | Set-Content -Path .\IBAN.csv -Force
$ibans = foreach ($iban in Import-Csv -Path .\IBAN.csv)
{
$iban | Select-Object -Property Country,
@{Name='Code' ; Expression={$iban.Example.Substring(0,2)}},
@{Name='Length'; Expression={[int]$iban.Length}},
Example
}
$ibans
- Output:
Country Code Length Example ------- ---- ------ ------- Albania AL 28 AL47212110090000000235698741 Andorra AD 24 AD1200012030200359100100 Austria AT 20 AT611904300235473201 Belgium BE 16 BE68539007547034 Bosnia and Herzegovina BA 20 BA391290079401028494 Bulgaria BG 22 BG80BNBG96611020345678 Croatia HR 21 HR1210010051863000160 Cyprus CY 28 CY17002001280000001200527600 Czech Republic CZ 24 CZ6508000000192000145399 Denmark DK 18 DK5000400440116243 Estonia EE 20 EE382200221020145685 Faroe Islands FO 18 FO1464600009692713 Finland FI 18 FI2112345600000785 France FR 27 FR1420041010050500013M02606 Georgia GE 22 GE29NB0000000101904917 Germany DE 22 DE89370400440532013000 Gibraltar GI 23 GI75NWBK000000007099453 Greece GR 27 GR1601101250000000012300695 Greenland GL 18 GL8964710001000206 Hungary HU 28 HU42117730161111101800000000 Iceland IS 26 IS140159260076545510730339 Ireland IE 22 IE29AIBK93115212345678 Italy IT 27 IT60X0542811101000000123456 Kosovo XK 20 XK051212012345678906 Latvia LV 21 LV80BANK0000435195001 Liechtenstein LI 21 LI21088100002324013AA Lithuania LT 20 LT121000011101001000 Luxembourg LU 20 LU280019400644750000 Macedonia MK 19 MK07300000000042425 Malta MT 31 MT84MALT011000012345MTLCAST001S Moldova MD 24 MD24AG000225100013104168 Monaco MC 27 MC5813488000010051108001292 Montenegro ME 22 ME25505000012345678951 Netherlands NL 18 NL91ABNA0417164300 Norway NO 15 NO9386011117947 Poland PL 28 PL27114020040000300201355387 Portugal PT 25 PT50000201231234567890154 Romania RO 24 RO49AAAA1B31007593840000 San Marino SM 27 SM86U0322509800000000270100 Serbia RS 22 RS35260005601001611379 Slovakia SK 24 SK3112000000198742637541 Slovenia SI 19 SI56191000000123438 Spain ES 24 ES9121000418450200051332 Sweden SE 24 SE3550000000054910000003 Switzerland CH 21 CH9300762011623852957 Ukraine UA 29 UA573543470006762462054925026 United Kingdom GB 22 GB29NWBK60161331926819
$regex = [regex]'[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{6}[0-9]{5}([a-zA-Z0-9]?){0,16}'
foreach ($iban in $ibans)
{
[PSCustomObject]@{
Country = $iban.Country
Example = $iban.Example
IsValid = $regex.IsMatch($iban.Example)
}
}
- Output:
Country Example IsValid ------- ------- ------- Albania AL47212110090000000235698741 True Andorra AD1200012030200359100100 True Austria AT611904300235473201 True Belgium BE68539007547034 True Bosnia and Herzegovina BA391290079401028494 True Bulgaria BG80BNBG96611020345678 True Croatia HR1210010051863000160 True Cyprus CY17002001280000001200527600 True Czech Republic CZ6508000000192000145399 True Denmark DK5000400440116243 True Estonia EE382200221020145685 True Faroe Islands FO1464600009692713 True Finland FI2112345600000785 True France FR1420041010050500013M02606 True Georgia GE29NB0000000101904917 True Germany DE89370400440532013000 True Gibraltar GI75NWBK000000007099453 True Greece GR1601101250000000012300695 True Greenland GL8964710001000206 True Hungary HU42117730161111101800000000 True Iceland IS140159260076545510730339 True Ireland IE29AIBK93115212345678 True Italy IT60X0542811101000000123456 True Kosovo XK051212012345678906 True Latvia LV80BANK0000435195001 True Liechtenstein LI21088100002324013AA True Lithuania LT121000011101001000 True Luxembourg LU280019400644750000 True Macedonia MK07300000000042425 True Malta MT84MALT011000012345MTLCAST001S True Moldova MD24AG000225100013104168 True Monaco MC5813488000010051108001292 True Montenegro ME25505000012345678951 True Netherlands NL91ABNA0417164300 True Norway NO9386011117947 True Poland PL27114020040000300201355387 True Portugal PT50000201231234567890154 True Romania RO49AAAA1B31007593840000 True San Marino SM86U0322509800000000270100 True Serbia RS35260005601001611379 True Slovakia SK3112000000198742637541 True Slovenia SI56191000000123438 True Spain ES9121000418450200051332 True Sweden SE3550000000054910000003 True Switzerland CH9300762011623852957 True Ukraine UA573543470006762462054925026 True United Kingdom GB29NWBK60161331926819 True
PureBasic
EnableExplicit
Enumeration IBAN
#IBAN_VAL
#IBAN_SUM
#IBAN_NOSPACE
#IBAN_VAL_FORM
#IBAN_SUM_FORM
EndEnumeration
NewMap CData.i()
Macro CCD(SIGN,LENGTH)
CData(SIGN)=LENGTH
EndMacro
Procedure.s IBANForm(iban.s,form.i)
Define fn.s, c.i
fn=RemoveString(UCase(iban),Chr(32))
If form=#IBAN_NOSPACE : ProcedureReturn fn : EndIf
fn=Mid(fn,5)+Mid(fn,1,4)
For c=65 To 90
fn=ReplaceString(fn,Chr(c),Str(c-55))
Next c
If form=#IBAN_VAL_FORM : ProcedureReturn fn : EndIf
fn=Left(fn,Len(fn)-2)+"00"
If form=#IBAN_SUM_FORM : ProcedureReturn fn : EndIf
EndProcedure
Procedure.s m97iban(iban.s,calculate.i)
Define i.i, part.s, rest.s
Select calculate
Case #IBAN_VAL : iban=IBANForm(iban,#IBAN_VAL_FORM)
Case #IBAN_SUM : iban=IBANForm(iban,#IBAN_SUM_FORM)
EndSelect
For i=1 To Len(iban) ; Validierung der Prüfsumme
part+Mid(iban,i,1)
If Val(rest+part)<97 : Continue : EndIf
rest=Str((Val(rest+part)) %97) : part=""
Next
Select calculate
Case #IBAN_VAL : ProcedureReturn rest
Case #IBAN_SUM : ProcedureReturn RSet(Str(98-Val(rest+part)),2,"0")
EndSelect
EndProcedure
CCD("AL",28) : CCD("AD",24) : CCD("AT",20) : CCD("AZ",28) : CCD("BE",16) : CCD("BH",22) : CCD("BA",20)
CCD("BR",29) : CCD("BG",22) : CCD("CR",21) : CCD("HR",21) : CCD("CY",28) : CCD("CZ",24) : CCD("DK",18)
CCD("DO",28) : CCD("EE",20) : CCD("FO",18) : CCD("FI",18) : CCD("FR",27) : CCD("GE",22) : CCD("DE",22)
CCD("GI",23) : CCD("GR",27) : CCD("GL",18) : CCD("GT",28) : CCD("HU",28) : CCD("IS",26) : CCD("IE",22)
CCD("IL",23) : CCD("IT",27) : CCD("KZ",20) : CCD("KW",30) : CCD("LV",21) : CCD("LB",28) : CCD("LI",21)
CCD("LT",20) : CCD("LU",20) : CCD("MK",19) : CCD("MT",31) : CCD("MR",27) : CCD("MU",30) : CCD("MC",27)
CCD("MD",24) : CCD("ME",22) : CCD("NL",18) : CCD("NO",15) : CCD("PK",24) : CCD("PS",29) : CCD("PL",28)
CCD("PT",25) : CCD("RO",24) : CCD("SM",27) : CCD("SA",24) : CCD("RS",22) : CCD("SK",24) : CCD("SI",19)
CCD("ES",24) : CCD("SE",24) : CCD("CH",21) : CCD("TN",24) : CCD("TR",26) : CCD("AE",23) : CCD("GB",22)
CCD("VG",24)
DataSection
IBANData:
Data.s "GB82 WEST 1234 5698 7654 32"
Data.s "GB82WEST12345698765432"
Data.s "gb82 west 1234 5698 7654 32"
Data.s "GB82 TEST 1234 5698 7654 32"
Data.s "GR16 0110 1250 0000 0001 2300 695"
Data.s "GB29 NWBK 6016 1331 9268 19"
Data.s "SA03 8000 0000 6080 1016 7519"
Data.s "CH93 0076 2011 6238 5295 7"
Data.s "IL62 0108 0000 0009 9999 999"
Data.s "IL62-0108-0000-0009-9999-999"
Data.s "US12 3456 7890 0987 6543 210"
Data.s "GR16 0110 1250 0000 0001 2300 695X"
Data.s Chr(0)
EndDataSection
Define iban.s, cc.s
OpenConsole()
Restore IBANData
Repeat
Read.s iban : If iban=Chr(0) : Break : EndIf
Print("IBAN"+#TAB$+": "+LSet(iban,35,Chr(32))+#TAB$)
cc=Left(IBANForm(iban,#IBAN_NOSPACE),2)
If CData(cc)
If Not CData()=Len(IBANForm(iban,#IBAN_NOSPACE)) : PrintN("[INCORRECT: LENGTH]") : Continue : EndIf
Else
PrintN("[INCORRECT: COUNTRY]") : Continue
EndIf
If Not Val(m97iban(iban,#IBAN_VAL))=1 : PrintN("[INCORRECT: MOD97]") : Continue : EndIf
If Not Right(IBANForm(iban,#IBAN_VAL_FORM),2)=m97iban(iban,#IBAN_SUM)
PrintN("[INCORRECT: CHECKSUM]") : Continue
EndIf
PrintN("[CORRECT]")
ForEver
Input()
End
- Output:
IBAN : GB82 WEST 1234 5698 7654 32 [CORRECT] IBAN : GB82WEST12345698765432 [CORRECT] IBAN : gb82 west 1234 5698 7654 32 [CORRECT] IBAN : GB82 TEST 1234 5698 7654 32 [INCORRECT: MOD97] IBAN : GR16 0110 1250 0000 0001 2300 695 [CORRECT] IBAN : GB29 NWBK 6016 1331 9268 19 [CORRECT] IBAN : SA03 8000 0000 6080 1016 7519 [CORRECT] IBAN : CH93 0076 2011 6238 5295 7 [CORRECT] IBAN : IL62 0108 0000 0009 9999 999 [CORRECT] IBAN : IL62-0108-0000-0009-9999-999 [INCORRECT: LENGTH] IBAN : US12 3456 7890 0987 6543 210 [INCORRECT: COUNTRY] IBAN : GR16 0110 1250 0000 0001 2300 695X [INCORRECT: LENGTH]
Python
import re
_country2length = dict(
AL=28, AD=24, AT=20, AZ=28, BE=16, BH=22, BA=20, BR=29,
BG=22, CR=21, HR=21, CY=28, CZ=24, DK=18, DO=28, EE=20,
FO=18, FI=18, FR=27, GE=22, DE=22, GI=23, GR=27, GL=18,
GT=28, HU=28, IS=26, IE=22, IL=23, IT=27, KZ=20, KW=30,
LV=21, LB=28, LI=21, LT=20, LU=20, MK=19, MT=31, MR=27,
MU=30, MC=27, MD=24, ME=22, NL=18, NO=15, PK=24, PS=29,
PL=28, PT=25, RO=24, SM=27, SA=24, RS=22, SK=24, SI=19,
ES=24, SE=24, CH=21, TN=24, TR=26, AE=23, GB=22, VG=24 )
def valid_iban(iban):
# Ensure upper alphanumeric input.
iban = iban.replace(' ','').replace('\t','')
if not re.match(r'^[\dA-Z]+$', iban):
return False
# Validate country code against expected length.
if len(iban) != _country2length[iban[:2]]:
return False
# Shift and convert.
iban = iban[4:] + iban[:4]
digits = int(''.join(str(int(ch, 36)) for ch in iban)) #BASE 36: 0..9,A..Z -> 0..35
return digits % 97 == 1
if __name__ == '__main__':
for account in ["GB82 WEST 1234 5698 7654 32", "GB82 TEST 1234 5698 7654 32"]:
print('%s validation is: %s' % (account, valid_iban(account)))
- Output:
GB82 WEST 1234 5698 7654 32 validation is: True GB82 TEST 1234 5698 7654 32 validation is: False
Racket
#lang racket
(define lens
'([AL 28] [AD 24] [AT 20] [AZ 28] [BH 22] [BE 16] [BA 20] [BR 29] [BG 22]
[CR 21] [HR 21] [CY 28] [CZ 24] [DK 18] [DO 28] [EE 20] [FO 18] [FI 18]
[FR 27] [GE 22] [DE 22] [GI 23] [GR 27] [GL 18] [GT 28] [HU 28] [IS 26]
[IE 22] [IL 23] [IT 27] [KZ 20] [KW 30] [LV 21] [LB 28] [LI 21] [LT 20]
[LU 20] [MK 19] [MT 31] [MR 27] [MU 30] [MC 27] [MD 24] [ME 22] [NL 18]
[NO 15] [PK 24] [PS 29] [PL 28] [PT 25] [RO 24] [SM 27] [SA 24] [RS 22]
[SK 24] [SI 19] [ES 24] [SE 24] [CH 21] [TN 24] [TR 26] [AE 23] [GB 22]
[VG 24]))
(define (valid-iban? str)
(define str* (regexp-replace* #px"\\s+" str ""))
(define c (cond [(regexp-match #rx"^[A-Z][A-Z]" str*)
=> (λ(x) (assq (string->symbol (car x)) lens))]
[else #f]))
(define (letter c)
(number->string (+ (char->integer (string-ref c 0)) -65 10)))
(and c (= (cadr c) (string-length str*))
(regexp-match? #rx"[A-Z0-9]" str*)
(let* ([x (string-append (substring str* 4) (substring str* 0 4))]
[x (string->number (regexp-replace* #rx"[A-Z]" x letter))])
(= 1 (modulo x 97)))))
(valid-iban? "GB82 WEST 1234 5698 7654 32") ; => #t
(valid-iban? "GB82 TEST 1234 5698 7654 32") ; => #f
Raku
(formerly Perl 6)
subset IBAN of Str where sub ($_ is copy) {
s:g/\s//;
return False if m/<-alnum>/ or .chars != <
AD 24 AE 23 AL 28 AT 20 AZ 28 BA 20 BE 16 BG 22 BH 22 BR 29 CH 21
CR 21 CY 28 CZ 24 DE 22 DK 18 DO 28 EE 20 ES 24 FI 18 FO 18 FR 27
GB 22 GE 22 GI 23 GL 18 GR 27 GT 28 HR 21 HU 28 IE 22 IL 23 IS 26
IT 27 KW 30 KZ 20 LB 28 LI 21 LT 20 LU 20 LV 21 MC 27 MD 24 ME 22
MK 19 MR 27 MT 31 MU 30 NL 18 NO 15 PK 24 PL 28 PS 29 PT 25 RO 24
RS 22 SA 24 SE 24 SI 19 SK 24 SM 27 TN 24 TR 26 VG 24
>.hash{.substr(0,2).uc};
s/(.**4)(.+)/$1$0/;
return .subst(:g, /\D/, { :36(~$_) }) % 97 == 1;
}
say "$_ is {$_ ~~ IBAN ?? 'valid' !! 'invalid' }" for
'GB82 WEST 1234 5698 7654 32',
'gb82 west 1234 5698 7654 32',
'GB82 TEST 1234 5698 7654 32';
- Output:
GB82 WEST 1234 5698 7654 32 is valid. gb82 west 1234 5698 7654 32 is valid. GB82 TEST 1234 5698 7654 32 is invalid.
REXX
These REXX programs can validate an IBAN specified on the command line or from an internal list.
basic checking
/*REXX program validates an IBAN (International Bank Account Number). */
@.=; @.1 = 'GB82 WEST 1234 5698 7654 32 '
@.2 = 'Gb82 West 1234 5698 7654 32 '
@.3 = 'GB82 TEST 1234 5698 7654 32 '
@.4 = 'GR16 0110 1250 0000 0001 2300 695 '
@.5 = 'GB29 NWBK 6016 1331 9268 19 '
@.6 = 'SA03 8000 0000 6080 1016 7519 '
@.7 = 'CH93 0076 2011 6238 5295 7 '
@.8 = 'IL62 0108 0000 0009 9999 999 '
@.9 = 'IL62-0108-0000-0009-9999-999 '
@.10 = 'US12 3456 7890 0987 6543 210 '
@.11 = 'GR16 0110 1250 0000 0001 2300 695X '
parse arg @.0 /*get optional 1st arg from CL*/
do k=0 + (arg()==0) while @.k\=='' /*either: 0 or 1 ──► n */
r = val_IBAN(@.k)
if r==0 then say ' valid IBAN:' @.k
else say 'invalid IBAN:' @.k " " r
if k==0 then leave /*User specified IBAN? Then we're done*/
end /*k*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
val_IBAN: procedure; arg x; numeric digits 200 /*allow for big numbers in the IBAN's. */
x=space(x,0); L=length(x) /*elide blanks; determine the length. */
cc= 'AD 24 AE 23 AL 28 AT 20 AZ 28 BA 20 BE 16 BG 22 BH 22 BR 29 CH 21 CR 21 CY 28 CZ 24',
'DE 22 DK 18 DO 28 EE 20 ES 24 FI 18 FO 18 FR 27 GB 22 GE 22 GI 23 GL 18 GR 27 GT 28',
'HR 21 HU 28 IE 22 IL 23 IS 26 IT 27 KW 30 KZ 20 LB 28 LI 21 LT 20 LU 20 LV 21 MC 27',
'MD 24 ME 22 MK 19 MR 27 MT 31 MU 30 NL 18 NO 15 PK 24 PL 28 PS 29 PT 25 RO 24 RS 22',
'SA 24 SE 24 SI 19 SK 24 SM 27 TN 24 TR 26 VG 24' /*a list of valid countries. */
@ABC# = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' /*the Latin alphabet and decimal digits*/
cc_=left(x, 2); kk=substr(x, 3, 2) /*get IBAN country code and checkDigits*/
c#=wordpos(cc_, cc) /*find the country code index. */
cL=word(cc, c# + 1) /*get the length of the country's IBAN.*/
e= '***error*** invalid IBAN' /*literal used when displaying an error*/
if c#==0 then return e 'country code:' cc_
if \datatype(x, 'A') then return e 'character:' substr(x, verify(x, @ABC#), 1)
if cL\==L then return e 'length:' L ' (should be' cL")"
y=substr(x, 5)left(x, 4) /*put four digs in front ───► the back.*/
z= /* [↓] translate characters ──► digits*/
do j=1 for L; _=substr(y, j, 1)
if datatype(_, 'U') then z=z || pos(_, @ABC#) + 9 /*if uppercase, then ··· */
else z=z || _
end /*j*/
if z//97==1 then return 0 /*check if correct remainder (modulus).*/
return e 'check digits.'
- output when using the default input:
valid IBAN: GB82 WEST 1234 5698 7654 32 valid IBAN: Gb82 West 1234 5698 7654 32 invalid IBAN: GB82 TEST 1234 5698 7654 32 ***error*** invalid check digits. valid IBAN: GR16 0110 1250 0000 0001 2300 695 valid IBAN: GB29 NWBK 6016 1331 9268 19 valid IBAN: SA03 8000 0000 6080 1016 7519 valid IBAN: CH93 0076 2011 6238 5295 7 valid IBAN: IL62 0108 0000 0009 9999 999 invalid IBAN: IL62-0108-0000-0009-9999-999 ***error*** invalid IBAN character: - invalid IBAN: US12 3456 7890 0987 6543 210 ***error*** invalid IBAN country code: US invalid IBAN: GR16 0110 1250 0000 0001 2300 695X ***error*** invalid IBAN length: 28 (should be 27)
more checking
This version of the REXX program has more error checking:
- checks for two countries that may not be valid (as per their entry date into the IBAN system)
- checks some countries to make sure their check digits match a specific value
/*REXX pgm validates an IBAN (International Bank Account Number), including date ranges.*/
@.=; @.1 = 'GB82 WEST 1234 5698 7654 32 '
@.2 = 'Gb82 West 1234 5698 7654 32 '
@.3 = 'GB82 TEST 1234 5698 7654 32 '
@.4 = 'GR16 0110 1250 0000 0001 2300 695 '
@.5 = 'GB29 NWBK 6016 1331 9268 19 '
@.6 = 'SA03 8000 0000 6080 1016 7519 '
@.7 = 'CH93 0076 2011 6238 5295 7 '
@.8 = 'IL62 0108 0000 0009 9999 999 '
@.9 = 'IL62-0108-0000-0009-9999-999 '
@.10 = 'US12 3456 7890 0987 6543 210 '
@.11 = 'GR16 0110 1250 0000 0001 2300 695X '
@.12 = 'GT11 2222 3333 4444 5555 6666 7777 '
@.13 = 'MK11 2222 3333 4444 555 '
parse arg @.0 /*get optional 1st arg from CL*/
do k=0 + (arg()==0) while @.k\=='' /*either: 0 or 1 ──► n */
r = val_IBAN(@.k)
if r==0 then say ' valid IBAN:' @.k
else say 'invalid IBAN:' @.k " " r
if k==0 then leave /*User specified IBAN? Then we're done*/
end /*k*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
val_IBAN: procedure; arg x; numeric digits 200 /*allow for big numbers in the IBAN's. */
x=space(x,0); L=length(x) /*elide blanks; determine the length. */
cc= 'AD 24 AE 23 AL 28 AT 20 AZ 28 BA 20 BE 16 BG 22 BH 22 BR 29 CH 21 CR 21 CY 28 CZ 24',
'DE 22 DK 18 DO 28 EE 20 ES 24 FI 18 FO 18 FR 27 GB 22 GE 22 GI 23 GL 18 GR 27 GT 28',
'HR 21 HU 28 IE 22 IL 23 IS 26 IT 27 KW 30 KZ 20 LB 28 LI 21 LT 20 LU 20 LV 21 MC 27',
'MD 24 ME 22 MK 19 MR 27 MT 31 MU 30 NL 18 NO 15 PK 24 PL 28 PS 29 PT 25 RO 24 RS 22',
'SA 24 SE 24 SI 19 SK 24 SM 27 TN 24 TR 26 VG 24' /*a list of valid countries. */
@ABC# = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' /*the Latin alphabet and decimal digits*/
cc_=left(x, 2); kk=substr(x, 3, 2) /*get IBAN country code and checkDigits*/
c#=wordpos(cc_, cc) /*find the country code index. */
cL=word(cc, c# + 1) /*get the length of the country's IBAN.*/
e= '***error*** invalid IBAN' /*literal used when displaying an error*/
if c#==0 then return e 'country code:' cc_
if \datatype(x, 'A') then return e 'character:' substr(x, verify(x, @ABC#), 1)
if cL\==L then return e 'length:' L ' (should be' cL")"
if cc_=='BR' & date("S")<20130701 then return e "country, Brazil isn't valid until 1-July-2013."
if cc_=='GT' & date("S")<20140701 then return e "country, Guatemala isn't valid until 1-July-2014."
if cc_=='BA' & kk\==39 then return e "check digits for Bosnia and Herzegovina:" kk
if cc_=='MK' & kk\==07 then return e "check digits for Macedonia:" kk
if cc_=='ME' & kk\==25 then return e "check digits for Montenegro:" kk
if cc_=='PT' & kk\==50 then return e "check digits for Portugal:" kk
if cc_=='SI' & kk\==56 then return e "check digits for Slovenia:" kk
y=substr(x, 5)left(x, 4) /*put four digs in front ───► the back.*/
z= /* [↓] translate characters ──► digits*/
do j=1 for L; _=substr(y, j, 1)
if datatype(_, 'U') then z=z || pos(_, @ABC#) + 9 /*if uppercase, then ··· */
else z=z || _
end /*j*/
if z//97==1 then return 0 /*check if correct remainder (modulus).*/
return e 'check digits.'
- output when using the default input, (the run date of this program is 29-April-2013):
valid IBAN: GB82 WEST 1234 5698 7654 32 valid IBAN: Gb82 West 1234 5698 7654 32 invalid IBAN: GB82 TEST 1234 5698 7654 32 ***error*** invalid IBAN check digits. valid IBAN: GR16 0110 1250 0000 0001 2300 695 valid IBAN: GB29 NWBK 6016 1331 9268 19 valid IBAN: SA03 8000 0000 6080 1016 7519 valid IBAN: CH93 0076 2011 6238 5295 7 valid IBAN: IL62 0108 0000 0009 9999 999 invalid IBAN: IL62-0108-0000-0009-9999-999 ***error*** invalid IBAN character: - invalid IBAN: US12 3456 7890 0987 6543 210 ***error*** invalid IBAN country code: US invalid IBAN: GR16 0110 1250 0000 0001 2300 695X ***error*** invalid IBAN length: 28 (should be 27) invalid IBAN: GT11 2222 3333 4444 5555 6666 7777 ***error*** invalid IBAN country, Guatemala isn't valid until 1-July-2014. invalid IBAN: MK11 2222 3333 4444 555 ***error*** invalid IBAN check digits for Macedonia: 11
Ring
# Project : IBAN
codes = list(5)
codes[1] = "GB82 WEST 1234 5698 7654 32"
codes[2] = "GB82 TEST 1234 5698 7654 32"
codes[3] = "GB81 WEST 1234 5698 7654 32"
codes[4] = "SA03 8000 0000 6080 1016 7519"
codes[5] = "CH93 0076 2011 6238 5295 7"
for y = 1 to len(codes)
see codes[y]
flag = 1
codes[y] = substr(codes[y], " ", "")
checkcode(codes[y])
check = checkiban(codes[y])
if check = 1
see " is valid" + nl
else
see " is invalid" + nl
ok
next
func checkcode(code)
for n = 1 to 2
if ascii(code[n]) < 65 or ascii(code[n]) > 90
flag = 0
ok
next
for m = 3 to len(code)
if (ascii(code[m]) > 64 and ascii(code[m]) < 91) or (ascii(code[m]) > 47 and ascii(code[m]) < 58)
else
flag = 0
ok
next
func checkiban(code)
code= substr(code, 5, len(code) - 4) + left(code, 4)
for x = 1 to len(code)
if ascii(code[x]) > 64 and ascii(code[x]) < 91
code = left(code, x-1) + string(ascii(code[x]) - 55) + right(code, len(code) - x)
ok
next
modold = left(code,9) % 97
for p = 1 to floor((len(code)-9)/7)
modnew = string(modold) + substr(code, 10 + (p-1) * 7, 7)
modnew = number(modnew) % 97
modold = modnew
next
modrest = right(code, len(code) - ((p-1)*7 + 9))
modnew = string(modold) + modrest
modnew = number(modnew) % 97
return modnew
Output:
GB82 WEST 1234 5698 7654 32 is valid GB82 TEST 1234 5698 7654 32 is invalid GB81 WEST 1234 5698 7654 32 is invalid SA03 8000 0000 6080 1016 7519 is valid CH93 0076 2011 6238 5295 7 is valid
Ruby
def valid_iban? iban
len = {
AL: 28, AD: 24, AT: 20, AZ: 28, BE: 16, BH: 22, BA: 20, BR: 29,
BG: 22, CR: 21, HR: 21, CY: 28, CZ: 24, DK: 18, DO: 28, EE: 20,
FO: 18, FI: 18, FR: 27, GE: 22, DE: 22, GI: 23, GR: 27, GL: 18,
GT: 28, HU: 28, IS: 26, IE: 22, IL: 23, IT: 27, KZ: 20, KW: 30,
LV: 21, LB: 28, LI: 21, LT: 20, LU: 20, MK: 19, MT: 31, MR: 27,
MU: 30, MC: 27, MD: 24, ME: 22, NL: 18, NO: 15, PK: 24, PS: 29,
PL: 28, PT: 25, RO: 24, SM: 27, SA: 24, RS: 22, SK: 24, SI: 19,
ES: 24, SE: 24, CH: 21, TN: 24, TR: 26, AE: 23, GB: 22, VG: 24
}
# Ensure upper alphanumeric input.
iban.delete! " \t"
return false unless iban =~ /^[\dA-Z]+$/
# Validate country code against expected length.
cc = iban[0, 2].to_sym
return false unless iban.size == len[cc]
# Shift and convert.
iban = iban[4..-1] + iban[0, 4]
iban.gsub!(/./) { |c| c.to_i(36) }
iban.to_i % 97 == 1
end
p valid_iban? "GB82 WEST 1234 5698 7654 32" #=> true
p valid_iban? "GB82 TEST 1234 5698 7654 32" #=> false
Rust
fn main() {
for iban in [
"",
"x",
"QQ82",
"QQ82W",
"GB82 TEST 1234 5698 7654 322",
"gb82 WEST 1234 5698 7654 32",
"GB82 WEST 1234 5698 7654 32",
"GB82 TEST 1234 5698 7654 32",
"GB81 WEST 1234 5698 7654 32",
"SA03 8000 0000 6080 1016 7519",
"CH93 0076 2011 6238 5295 7",
].iter()
{
println!(
"'{}' is {}valid",
iban,
if validate_iban(iban) { "" } else { "NOT " }
);
}
}
fn validate_iban(iban: &str) -> bool {
let iso_len = [
("AL", 28), ("AD", 24), ("AT", 20), ("AZ", 28), ("BE", 16), ("BH", 22),
("BA", 20), ("BR", 29), ("BG", 22), ("HR", 21), ("CY", 28), ("CZ", 24),
("DK", 18), ("DO", 28), ("EE", 20), ("FO", 18), ("FI", 18), ("FR", 27),
("GE", 22), ("DE", 22), ("GI", 23), ("GL", 18), ("GT", 28), ("HU", 28),
("IS", 26), ("IE", 22), ("IL", 23), ("IT", 27), ("KZ", 20), ("KW", 30),
("LV", 21), ("LB", 28), ("LI", 21), ("LT", 20), ("LU", 20), ("MK", 19),
("MT", 31), ("MR", 27), ("MU", 30), ("MC", 27), ("MD", 24), ("ME", 22),
("NL", 18), ("NO", 15), ("PK", 24), ("PS", 29), ("PL", 28), ("PT", 25),
("RO", 24), ("SM", 27), ("SA", 24), ("RS", 22), ("SK", 24), ("SI", 19),
("ES", 24), ("SE", 24), ("CH", 21), ("TN", 24), ("TR", 26), ("AE", 23),
("GB", 22), ("VG", 24), ("GR", 27), ("CR", 21),
];
let trimmed_iban = iban.chars()
.filter(|&ch| ch != ' ')
.collect::<String>()
.to_uppercase();
if trimmed_iban.len() < 4 {
return false;
}
let prefix = &trimmed_iban[0..2];
if let Some(pair) = iso_len.iter().find(|&&(code, _)| code == prefix) {
if pair.1 != trimmed_iban.len() {
return false;
}
} else {
return false;
}
let reversed_iban = format!("{}{}", &trimmed_iban[4..], &trimmed_iban[0..4]);
let mut expanded_iban = String::new();
for ch in reversed_iban.chars() {
expanded_iban.push_str(&if ch.is_numeric() {
format!("{}", ch)
} else {
format!("{}", ch as u8 - 'A' as u8 + 10u8)
});
}
expanded_iban.bytes().fold(0, |acc, ch| {
(acc * 10 + ch as u32 - '0' as u32) % 97
}) == 1
}
Scala
import scala.collection.immutable.SortedMap
class Iban(val iban: String) {
// Isolated tests
def isAllUpperCase = iban.toUpperCase == iban
def isValidpattern = (Iban.pattern findFirstIn iban).nonEmpty
def isNationalSize = {
Iban.ccVsLength.getOrElse(iban.take(2), 0) == iban.size
}
def isCheckNumberOK = {
def rearrange = (iban.drop(4) + iban.take(4)). // Move left country code part to end
// continue with each char converted to Int
map(ch => if (ch.isDigit) ch.toInt - '0' else ch - 'A' + 10).mkString
(BigInt(rearrange) mod 97) == 1
}
def isValidIban = {
isAllUpperCase &&
isValidpattern &&
isNationalSize &&
isCheckNumberOK
}
}
object Iban {
// IBAN length database
lazy val ccVsLength: SortedMap[String, Int] = SortedMap[String, Int]() ++
"""AD24 AE23 AL28 AO25 AT20 AZ28 BA20 BE16 BF27 BG22 BH22 BI16
|BJ28 BR29 CG27 CH21 CI28 CM27 CR21 CV25 CY28 CZ24 DE22 DK18
|DO28 DZ24 EE20 EG27 ES24 FI18 FO18 FR27 GA27 GB22 GE22 GI23
|GL18 GR27 GT28 HR21 HU28 IE22 IL23 IR26 IS26 IT27 JO30 KW30
|KZ20 LB28 LI21 LT20 LU20 LV21 MC27 MD24 ME22 MG27 MK19 ML28
|MR27 MT31 MU30 MZ25 NL18 NO15 PK24 PL28 PS29 PT25 QA29 RO24
|RS22 SA24 SE24 SI19 SK24 SM27 SN28 TN24 TR26 UA29 VG24""".
stripMargin.replaceAll( """\s""", " ").split(' ').
map(v => (v.take(2), if (v.isEmpty) 0 else v.slice(2, 4).toInt))
lazy val pattern = "([A-Z]{2})([0-9]{2})([A-Z0-9]{0,30})".r
def apply(s: String) = new Iban(s.replaceAll( """\s""", ""))
}
The test program:
object IbanTest extends App {
def blackCases = """AT611904300235473201
|GB82TEST12345698765432
|GB81WEST12345698765432""".stripMargin
def whiteCases = """AD1200012030200359100100
|AE26 0211 0000 0023 0064 016
|AL47 2121 1009 0000 0002 3569 8741
|AO06000600000100037131174
|AZ21NABZ00000000137010001944
|BA391290079401028494
|BE68539007547034
|BF1030134020015400945000643
|BG80BNBG96611020345678
|BH29BMAG1299123456BH00
|BI43201011067444
|BJ11B00610100400271101192591
|BR9700360305000010009795493P1
|CG5230011000202151234567890
|CH9300762011623852957
|CI05A00060174100178530011852
|CM2110003001000500000605306
|CR0515202001026284066
|CV64000300004547069110176
|CY17002001280000001200527600
|CZ6508000000192000145399
|DE89370400440532013000
|DK5000400440116243
|DO28BAGR00000001212453611324
|DZ4000400174401001050486
|EE382200221020145685
|EG1100006001880800100014553
|ES9121000418450200051332
|FI2112345600000785
|FO1464600009692713
|FR1420041010050500013M02606
|FR7630007000110009970004942
|GA2140002000055602673300064
|GB29NWBK60161331926819
|GE29NB0000000101904917
|GI75NWBK000000007099453
|GL8964710001000206
|GR1601101250000000012300695
|GT82TRAJ01020000001210029690
|HR1210010051863000160
|HU42117730161111101800000000
|IE29AIBK93115212345678
|IL620108000000099999999
|IR580540105180021273113007
|IS140159260076545510730339
|IT60X0542811101000000123456
|JO94CBJO0010000000000131000302
|KW74NBOK0000000000001000372151
|KZ176010251000042993
|LB30099900000001001925579115
|LI21088100002324013AA
|LT121000011101001000
|LU280019400644750000
|LV80BANK0000435195001
|MC5813488000010051108001292
|MD24AG000225100013104168
|ME25505000012345678951
|MG4600005030010101914016056
|MK07300000000042425
|ML03D00890170001002120000447
|MR1300012000010000002037372
|MT84MALT011000012345MTLCAST001S
|MU17BOMM0101101030300200000MUR
|MZ59000100000011834194157
|NL91ABNA0417164300
|NL81 TRIO 0212 4710 66
|NO9386011117947
|PK24SCBL0000001171495101
|PL27114020040000300201355387
|PS92PALS000000000400123456702
|PT50000200000163099310355
|PT50000201231234567890154
|QA58 DOHB 0000 1234 5678 90AB CDEF G
|RO49 AAAA 1B31 0075 9384 0000
|RS35260005601001611379
|SA0380000000608010167519
|SE3550000000054910000003
|SI56191000000123438
|SK3112000000198742637541
|SM86U0322509800000000270100
|SN12K00100152000025690007542
|TN5914207207100707129648
|TR330006100519786457841326
|UA57 3543 4700 0676 2462 0549 2502 6
|VG96 VPVG 0000 0123 4567 8901
|GB82 WEST 1234 5698 7654 32
|SA03 8000 0000 6080 1016 7519
|CH93 0076 2011 6238 5295 7""".stripMargin
whiteCases.lines.foreach(l => assert(Iban(l).isValidIban))
blackCases.lines.foreach(l => assert(!Iban(l).isValidIban))
println(s"Successfully completed; ${whiteCases.lines.size + blackCases.lines.size} cases tested, no errors.")
}
- Output:
Successfully completed; 91 cases tested, no errors.
Seed7
$ include "seed7_05.s7i";
include "bigint.s7i";
const type: countryHash is hash [string] integer;
const func countryHash: initCountryCode is func
result
var countryHash: cc is countryHash.value;
begin
cc @:= ["AL"] 28; cc @:= ["AD"] 24; cc @:= ["AT"] 20; cc @:= ["AZ"] 28; cc @:= ["BE"] 16; cc @:= ["BH"] 22;
cc @:= ["BA"] 20; cc @:= ["BR"] 29; cc @:= ["BG"] 22; cc @:= ["CR"] 21; cc @:= ["HR"] 21; cc @:= ["CY"] 28;
cc @:= ["CZ"] 24; cc @:= ["DK"] 18; cc @:= ["DO"] 28; cc @:= ["EE"] 20; cc @:= ["FO"] 18; cc @:= ["FI"] 18;
cc @:= ["FR"] 27; cc @:= ["GE"] 22; cc @:= ["DE"] 22; cc @:= ["GI"] 23; cc @:= ["GR"] 27; cc @:= ["GL"] 18;
cc @:= ["GT"] 28; cc @:= ["HU"] 28; cc @:= ["IS"] 26; cc @:= ["IE"] 22; cc @:= ["IL"] 23; cc @:= ["IT"] 27;
cc @:= ["KZ"] 20; cc @:= ["KW"] 30; cc @:= ["LV"] 21; cc @:= ["LB"] 28; cc @:= ["LI"] 21; cc @:= ["LT"] 20;
cc @:= ["LU"] 20; cc @:= ["MK"] 19; cc @:= ["MT"] 31; cc @:= ["MR"] 27; cc @:= ["MU"] 30; cc @:= ["MC"] 27;
cc @:= ["MD"] 24; cc @:= ["ME"] 22; cc @:= ["NL"] 18; cc @:= ["NO"] 15; cc @:= ["PK"] 24; cc @:= ["PS"] 29;
cc @:= ["PL"] 28; cc @:= ["PT"] 25; cc @:= ["RO"] 24; cc @:= ["SM"] 27; cc @:= ["SA"] 24; cc @:= ["RS"] 22;
cc @:= ["SK"] 24; cc @:= ["SI"] 19; cc @:= ["ES"] 24; cc @:= ["SE"] 24; cc @:= ["CH"] 21; cc @:= ["TN"] 24;
cc @:= ["TR"] 26; cc @:= ["AE"] 23; cc @:= ["GB"] 22; cc @:= ["VG"] 24;
end func;
const countryHash: countryCode is initCountryCode;
const func boolean: isLegal (in var string: iban) is func
result
var boolean: legal is FALSE;
local
var char: ch is ' ';
var string: converted is "";
begin
iban := upper(replace(iban, " ", ""));
legal := iban[.. 2] in countryCode and countryCode[iban[.. 2]] = length(iban);
iban := iban[5 ..] & iban[.. 4];
for ch range iban do
case ch of
when {'0' .. '9'}: converted &:= ch;
when {'A' .. 'Z'}: converted &:= str(ord(ch) - ord('A') + 10);
otherwise: legal := FALSE;
end case;
end for;
legal := legal and (bigInteger parse converted) rem 97_ = 1_;
end func;
const proc: check (in string: iban) is func
begin
writeln("Valid " <& iban <& ": " <& isLegal(iban));
end func;
const proc: main is func
begin
check("GB82 WEST 1234 5698 7654 32");
check("GB82WEST12345698765432");
check("gb82 west 1234 5698 7654 32");
check("GB82 TEST 1234 5698 7654 32");
check("GB82 WEST 1243 5698 7654 32");
end func;
- Output:
Valid GB82 WEST 1234 5698 7654 32: TRUE Valid GB82WEST12345698765432: TRUE Valid gb82 west 1234 5698 7654 32: TRUE Valid GB82 TEST 1234 5698 7654 32: FALSE Valid GB82 WEST 1243 5698 7654 32: FALSE
Sidef
func valid_iban(iban) {
static len = Hash(
AD=>24, AE=>23, AL=>28, AO=>25, AT=>20, AZ=>28, BA=>20, BE=>16, BF=>27,
BG=>22, BH=>22, BI=>16, BJ=>28, BR=>29, CG=>27, CH=>21, CI=>28, CM=>27,
CR=>21, CV=>25, CY=>28, CZ=>24, DE=>22, DK=>18, DO=>28, DZ=>24, EE=>20,
EG=>27, ES=>24, FI=>18, FO=>18, FR=>27, GA=>27, GB=>22, GE=>22, GI=>23,
GL=>18, GR=>27, GT=>28, HR=>21, HU=>28, IE=>22, IL=>23, IR=>26, IS=>26,
IT=>27, JO=>30, KW=>30, KZ=>20, LB=>28, LI=>21, LT=>20, LU=>20, LV=>21,
MC=>27, MD=>24, ME=>22, MG=>27, MK=>19, ML=>28, MR=>27, MT=>31, MU=>30,
MZ=>25, NL=>18, NO=>15, PK=>24, PL=>28, PS=>29, PT=>25, QA=>29, RO=>24,
RS=>22, SA=>24, SE=>24, SI=>19, SK=>24, SM=>27, SN=>28, TN=>24,