At Theo's subforum I found the Powerbasic source code for an IBAN checker. I also found some example code with Rosetta.org and adopted the task a little bit for Oxygenbasic. The challenge was to move the first 4 digits to the end of the code, change existing letters A - Z to numbers 10 - 35 and apply a mod 97 calculation with the resulting number. If the result is 1, then the code may be a valid IBAN.
I used a more current list for the country codes. The program can be executed just in time or compiled to a 32-bit or to a 64-bit exe file.
IBANcheck.o2bas:
'Identify an IBAN
'see: https://rosettacode.org/wiki/IBAN
'see: https://en.wikipedia.org/wiki/International_Bank_Account_Number
$ filename "IBANcheck.exe"
'uses rtl32
'uses rtl64
uses console
function replace(string t,w,r) as string 'ParseUtil.inc
========================================
/**
substitutes each w with r in t
@param t is the main string where substitutions are made
@param w is the key string to be replaced
@param r is the replacement string
@return the main string with replacements
**/
int a,b,lw,lr
string s=t
'
lw=len(w)
lr=len(r)
a=1
do
a=instr(a,s,w)
if a=0 then exit do
s=left(s,a-1)+r+mid(s,a+lw)
a+=lr
loop
return s
end function
function mod97(string s) as uint
--------------------------------
'Special division of a big number, a bit like in school
'with paper and pencil, determine the remainder
uint start = 10
uint r = mod(val(left(s, 9)), 97)
while start <= len(s)
r = mod(val(str(r) & mid(s, start, 7)), 97)
start += 7
wend
if r != 1 then print "Remainder = " r ": "
return r
end function
' List updated to release 96, February 2024, of IBAN Registry for ISO 13616 (87 countries)
string countryCodes =
"AD24001 AE23002 AL28003 AT20004 AZ28005 BA20006 BE16007 BG22008 BH22009 BI27010 " +
"BR29011 BY28012 CH21013 CR22014 CY28015 CZ24016 DE22017 DJ27018 DK18019 DO28020 " +
"EE20021 EG29022 ES24023 FI18024 FK18025 FO18026 FR27027 GB22028 GE22029 GI23030 " +
"GL18031 GR27032 GT28033 HR21034 HU28035 IE22036 IL23037 IQ23038 IS26039 IT27040 " +
"JO30041 KW30042 KZ20043 LB28044 LC32045 LI21046 LT20047 LU20048 LV21049 LY25050 " +
"MC27051 MD24052 ME22053 MK19054 MN20055 MR27056 MT31057 MU30058 NI28059 NL18060 " +
"NO15061 OM23062 PK24063 PL28064 PS29065 PT25066 QA29067 RO24068 RS22069 RU33070 " +
"SA24071 SC31072 SD18073 SE24074 SI19075 SK24076 SM27077 SO23078 ST25079 SV28080 " +
"TL23081 TN24082 TR26083 UA29084 VA22085 VG24086 XK20087"
dim string countries(87)
countries = {
"Andorra","United Arab Emirates","Albania","Austria","Azerbaijan","Bosnia and Herzegovina","Belgium","Bulgaria","Bahrein","Burundi",
"Brazil","Republic of Belarus","Switzerland","Costa Rica","Cyprus","Czech Republic","Germany","Djibouti","Denmark","Dominican Republic",
"Estonia","Egypt","Spain","Finnland","Falkland Islands","Faroe Islands","France","United Kingdom","Georgia","Gibraltar",
"Greenland","Greece","Guatemala","Croatia","Hungary","Ireland","Israel","Iraq","Iceland","Italy",
"Jordan","Kuwait","Kazakhstan","Lebanon","Saint Lucia","Liechtenstein","Lithuania","Luxembourg","Latvia","Libya",
"Monaco","Moldova","Montenegro","Macedonia","Mongolia","Mauritania","Malta","Mauritius","Nicaragua","Netherlands",
"Norway","Oman","Pakistan","Poland","Palestine, State of","Portugal","Qatar","Romania","Serbia","Russia",
"Saudi Arabia","Seychelles","Sudan","Sweden","Slovenia","Slovakia","San Marino","Somalia","Sao Tome and Principe","El Salvador",
"Timor-Leste","Tunisia","Turkey","Ukraine","Vatican City State","Virgin Islands","Kosovo"
}
function findCountryCode(string cc) as int
return instr(countryCodes, ucase(cc))
end function
string country
function validateIBAN(string IBAN) as bool
int pos, idx
string s_IBAN = ucase(IBAN)
string n
'remove spaces and hyphen
s_IBAN = replace (s_IBAN, " ","")
s_IBAN = replace (s_IBAN, "-","")
pos = findCountryCode(left(s_IBAN,2) + str(len(s_IBAN)))
if pos = 0 then
printl "Either Country code does not exist or length of code not correct!"
printl "IBAN is not valid!" : printl
return false
else
idx=val(mid(countryCodes,pos+4,3))
country = countries(idx)
end if
' move first 4 characters to end
s_IBAN = mid(s_IBAN, 5) + left(s_IBAN, 4)
' replace A to Z with numbers 10 To 35
n = ""
int i
for i = 1 to len(s_IBAN)
if mid(s_IBAN,i,1) >= "0" and mid(s_IBAN,i,1) <= "9" then
'is number
n &= mid(s_IBAN,i,1)
elseif mid(s_IBAN,i,1) >= "A" and mid(s_IBAN,i,1) <= "Z" then
'convert to number 10..35
n &= str(asc(mid(s_IBAN,i,1))-55)
else
printl "something wrong with IBAN number"
printl "IBAN is not valid!" : printl
return false
end If
next
'printl "IBAN converted to number :"
'printl n
if mod97(n) != 1 then
print "Remainder not 1 - "
printl "IBAN is not valid!" : printl
return false
end if
print "Country: " Country " - "
printl "IBAN may be valid." : printl
return true
end function
--------------------------------------------------------------
SetConsoleTitle "IBAN Checker"
bool iv
string IBAN
printl "This little program can be helpful for checking an IBAN "
printl "(International Bank Account Number) for errors e.g.:"
printl
IBAN = "GB82 WEST 1234 5698 7654 32" : printl IBAN
iv = validateIBAN(IBAN)
IBAN = "GB82 TEST 1234 5698 7654 32" : printl IBAN
iv = validateIBAN(IBAN)
IBAN = "GB81 WEST 1234 5698 7654 3" : printl IBAN
iv = validateIBAN(IBAN)
IBAN = "SA03 8000 0000 6080 1016 7519" : printl IBAN
iv = validateIBAN(IBAN)
IBAN ="CH93 0076 2011 6238 5295 7" : printl IBAN
iv = validateIBAN(IBAN)
--------------------------------------------------------------
string a
start:
printl : print "Would you like to check another IBAN? (Y/N) "
a = ucase(input())
if left(a,1) != "Y" then goto fin
cls
printl "Please enter an IBAN:"
IBAN = rtrim ltrim input()
if len(IBAN) > 3 then
iv = validateIBAN(IBAN)
end if
goto start
fin:
Thanks Roland,
It works with my current account. I'll include it as demos\DataProcessing\IBANchecker.o2bas.
The provided O2-Basic code seems to be well-structured and should work as intended for validating IBAN numbers. However, there are a few improvements that can be suggested:
In the replace function, you can use the replace$ intrinsic function available in O2-Basic, which simplifies the implementation:
function replace(string t, w, r) as string
return replace$(t, w, r)
end function
In the mod97 function, you can optimize the loop by processing the string in chunks of 9 characters instead of 7:
function mod97(string s) as uint
uint start = 10
uint r = mod(val(left(s, 9)), 97)
while start <= len(s)
r = mod(val(str(r) & mid(s, start, 9)), 97)
start += 9
wend
if r != 1 then print "Remainder = " r ": "
return r
end function
Instead of using a single string variable countryCodes to store country codes and their corresponding check digits, you can use a dictionary (if available in O2-Basic) or an array to make the code cleaner and easier to maintain:
dim uint checkDigits(87)
checkDigits = [24, 23, 28, ..., 20, 23, 26] ' Add the rest of the check digits here
function findCountryCode(string cc) as int
int idx
for idx = 0 to 86
if ucase(left(cc, 2)) = ucase(left(countries(idx), 2)) then
return checkDigits(idx)
end if
next idx
return 0
end function
In the validateIBAN function, you can remove the country variable since it is not used anywhere else in the code.
Consider adding error handling for unexpected input formats or invalid country codes.
Finally, it's a good practice to add comments to your code to make it easier for others (and yourself) to understand what each part of the code does.
PS: Says "MISTRAL LARGE" I can not comment on that.
Hi Theo,
thank you for your comments. This was my first little project in a while, I am glad I did not forget everything about O2. I still have to mention the following:
replace$ does not exist as a built-in function in Oxygenbasic (I believe). I derived the mod97 function from Wikipedia, I found similar constructions in Rosetta Code. But piece-wise calculation D mod 97 can be done in many ways. Although not required for the task I added the names of the countries, because I wanted to show the country of the code in the result.
The execution speed could be increased of course (just by using strptr and byte array), but for this small app with a specific purpose, that does not make sense to me.
You code is fine, i was just testing MISTRAL on it.