IBAN

From Rosetta Code
IBAN is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
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.

The task here is to validate the following fictitious IBAN: GB82 WEST 1234 5698 7654 32. Details of the algorithm can be found on the Wikipedia page.

C

<lang C>#include <alloca.h>

  1. include <ctype.h>
  2. include <stdio.h>
  3. include <stdlib.h>
  4. include <string.h>
  1. define V(cc, exp) if (!strncmp(iban, cc, 2)) return len == exp

/* Validate country code against expected length. */ int valid_cc(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(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);
   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;

}</lang>

Output:
iban 'GB82 WEST 1234 5698 7654 32' GB82TEST12345698765432
GB82WEST12345698765432 is valid.
GB82TEST12345698765432 is invalid.

Caché ObjectScript

<lang cache>Class Utils.Validate [ Abstract ] {

ClassMethod IBAN(pIBAN As %String = "") As %Boolean { If pIBAN=..IBANGenerate(pIBAN) Quit 1 Quit 0 }

ClassMethod IBANGenerate(pIBAN As %String = "") As %String { Set pIBAN=$ZConvert(pIBAN, "U") Set cc=$Extract(pIBAN, 1, 2) Set cd=$Extract(pIBAN, 3, 4) Set bban=$Extract(pIBAN, 5, *) Set str="", alt=$ZStrip(bban_cc_"00", "*E'U'N") For i=1:1:$Length(alt) { Set chr=$Extract(alt, i) If chr?1U Set chr=$ASCII(chr)-55 Set str=str_chr } Set cd=98-..Mod(str, 97) Set cd=$Translate($Justify(cd, 2), " ", 0) Quit cc_cd_bban }

ClassMethod Mod(pNum As %Integer, pDiv As %Integer) As %Integer [ Internal, Private ] { If $Length(pNum)<9 Quit pNum#pDiv Set res=0 For i=1:1:$Length(pNum)\7+1 { Set res=(res_$Extract(pNum, 1, 7))#pDiv Set pNum=$Extract(pNum, 8, *) } Quit res }

}</lang>

Examples:
USER>Write ##class(Utils.Validate).IBAN("GB82 WEST 1234 5698 7654 32")
1
USER>Write ##class(Utils.Validate).IBAN("GB00 WEST 1234 5698 7654 32")
0

Ruby

Works with: Ruby version 1.9+

<lang 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</lang>