{
  "id": "P101",
  "slug": "iban-checker",
  "key": "P101-iban-checker",
  "title": "IBAN checker",
  "summary": "International bank account numbers checked the ISO 13616 way, step by step: letters as numbers and the remainder mod 97 taken nine digits at a time; check digits made from an account part, leading zeros kept; and every IBAN one typing mistake away that would pass.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P101 - IBAN checker\n\nInternational bank account numbers, checked the ISO 13616 way and shown\nstep by step. You can also make an IBAN from a country and an account\npart, and see what a single typing mistake does to one.\n\n- `main.eml` - the menu, the step-by-step check, making an IBAN, the typo\n  search and the country list\n- `iban.eml` - cleaning the input, the country lengths, letters as\n  numbers, the remainder piece by piece, the check digits and the\n  one-change neighbours\n\nHow each part works:\n\n- The check: move the first four characters (country and check digits)\n  to the end and turn each letter into two digits (A is 10, ..., Z is 35).\n  The resulting number must leave 1 when divided by 97.\n- That number is long: 28 digits for the British example. As in the\n  corpus case `iban-mod97-validator`, it is taken mod 97 a piece at a\n  time. The remainder so far goes in front of the next digits, and no\n  piece is longer than 9 digits. Each piece is shown, and the whole\n  number's remainder is printed beside it as a second route.\n- Making an IBAN: the check digits are 98 minus the remainder of the\n  account part, the country and 00.\n- An IBAN is text, never a number. As in the corpus case\n  `leading-zero-identifiers`, an account part that starts with zeros keeps\n  them. The program says how many characters would be left if they were\n  read as a number.\n- Only each country's length is checked, not the layout of its account\n  part.\n- The typo search: for an IBAN that fails, every IBAN one change away that\n  passes. A change is a digit replaced by a digit, a letter by a letter, or\n  two neighbours swapped. A replaced character, or a swap of two digits or\n  two letters, always changes the remainder. A letter swapped with a digit\n  can keep it: in the account part that happens exactly for B and 1, M and\n  2, or X and 3, because B is 11, M is 22 and X is 33.\n\nWhat is checked: menu choices 1 to 5; letters, digits and spaces only,\nwith lower case raised; two letters for a country in the list; check\ndigits that are digits; the country's length; an account part of the\nright length. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- a valid British IBAN, step by step;\n- the same with its last digit changed, typed in lower case;\n- a German one a character short;\n- the German and Danish check digits made from their account parts (the\n  Danish one keeps its two leading zeros);\n- a made-up British IBAN with B and 1 side by side, then the same with the\n  two swapped, which passes too;\n- the typo search on an IBAN with its last two digits swapped: 7 IBANs\n  one change away pass, the right one among them;\n- the typo search on a valid IBAN, and the country list.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- an IBAN with a hyphen, a cancel, one too short, one without a country,\n  an unknown country, and check digits that are letters;\n- the country Germany, an account part of 3 characters, two cancels;\n- the typo search on an IBAN of the wrong length, and on one with a stray\n  symbol; a cancel.\n\nBuilt on the verified corpus cases `iban-mod97-validator` (mod 97 on a\nnumber too big to hold, two ways) and `leading-zero-identifiers`\n(identifiers made of digits are not numbers).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P101 IBAN checker: check an IBAN step by step, make one from a country\n# and an account part, and see what one typing mistake does.\nimport iban\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef ask_iban(prompt):\n    # a cleaned IBAN, \"\" to cancel, or \"!\" after a complaint\n    plain(input(prompt + \"> \")) => t\n    if t == \"\":\n        return \"\"\n    iban.clean(t) => s\n    if s == \"\":\n        \"Type letters and digits only; spaces are fine.\" ^0\n        return \"!\"\n    return s\n\ndef show_check(s):\n    iban.check(s) => r\n    (iban.grouped(s)) ^0\n    iban.countries() => lengths\n    if len(s) >= 2 and s[0:2] in lengths:\n        (\"  country \" + s[0:2] + \": \" + str(lengths[s[0:2]]) + \" characters; this one has \" + str(len(s)) + \".\") ^0\n    if r[0] or r[1] == \"mod 97 is not 1, so a character is wrong\":\n        iban.rearranged(s) => moved\n        iban.expand(moved) => digits\n        (\"  first four to the end: \" + moved) ^0\n        (\"  letters as numbers:    \" + digits + \" (\" + str(len(digits)) + \" digits)\") ^0\n        iban.mod97_steps(digits) => m\n        \"  mod 97, 9 digits at a time:\" ^0\n        for st in m[1]:\n            (\"    \" + st[0] + \" mod 97 = \" + str(st[1])) ^0\n        (\"  the whole number mod 97 = \" + str(int(digits) % 97) + \", the same.\") ^0\n    if r[0]:\n        \"Valid: mod 97 is 1.\" ^0\n    else:\n        (\"Not valid: \" + r[1] + \".\") ^0\n\ndef make():\n    plain(input(\"country (two letters)> \")) => c\n    if c == \"\":\n        \"Cancelled.\" ^0\n        return\n    iban.clean(c) => c\n    iban.countries() => lengths\n    if not (c in lengths):\n        \"Type a country from the list, such as DE or GB.\" ^0\n        return\n    lengths[c] - 4 => need\n    plain(input(\"account part (\" + str(need) + \" letters and digits)> \")) => b\n    if b == \"\":\n        \"Cancelled.\" ^0\n        return\n    iban.clean(b) => b\n    if len(b) != need:\n        (\"A \" + c + \" account part has \" + str(need) + \" characters, letters and digits, spaces left out.\") ^0\n        return\n    iban.check_digits(c, b) => d\n    c + d + b => s\n    (\"Check digits: 98 - (\" + b + c + \"00, letters as numbers, mod 97) = \" + d + \".\") ^0\n    show_check(s)\n    0 => zeros\n    while zeros < len(b) and b[zeros] == \"0\":\n        zeros + 1 => zeros\n    if zeros > 0:\n        b[zeros:len(b)] => short\n        (\"The account part starts with \" + str(zeros) + \" zero\" + plural(zeros) + \". Kept as text they stay; read as a number they would be lost, leaving \" + str(len(short)) + \" characters where \" + c + \" needs \" + str(need) + \".\") ^0\n\ndef plural(n):\n    if n == 1:\n        return \"\"\n    return \"s\"\n\ndef look_for_typos():\n    ask_iban(\"an IBAN that fails\") => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return\n    if s == \"!\":\n        return\n    iban.check(s) => r\n    if r[0]:\n        \"That one is valid. A wrong digit in place of a digit, or a wrong letter in place of a letter, always changes the remainder; so does swapping two different digits, or two different letters, next to each other.\" ^0\n        \"A letter swapped with a digit next to it in the account part slips through when the two are B and 1, M and 2, or X and 3: B is 11, M is 22 and X is 33.\" ^0\n        return\n    if r[1] != \"mod 97 is not 1, so a character is wrong\":\n        (\"Not that kind of failure: \" + r[1] + \".\") ^0\n        return\n    iban.typos(s) => found\n    0 => n_rep\n    0 => n_swap\n    for f in found:\n        if f[0] == \"replace\":\n            n_rep + 1 => n_rep\n        else:\n            n_swap + 1 => n_swap\n    (\"One character changed: \" + str(n_rep) + \" IBAN\" + plural(n_rep) + \" pass. Two neighbours swapped: \" + str(n_swap) + \" pass\" + verb(n_swap) + \".\") ^0\n    for f in found:\n        \"swap   \" => kind\n        if f[0] == \"replace\":\n            \"replace\" => kind\n        (\"  \" + kind + \"  \" + iban.grouped(f[1])) ^0\n    \"Mod 97 catches the mistake but cannot say which one it was: each of these is a valid IBAN.\" ^0\n\ndef verb(n):\n    if n == 1:\n        return \"es\"\n    return \"\"\n\ndef show_countries():\n    iban.countries() => lengths\n    \"\" => line\n    for c in [\"AT\", \"BE\", \"CH\", \"DE\", \"DK\", \"ES\", \"FI\", \"FR\", \"GB\", \"IE\", \"IT\", \"LU\", \"NL\", \"NO\", \"PL\", \"PT\", \"SE\"]:\n        if line != \"\":\n            line + \", \" => line\n        line + c + \" \" + str(lengths[c]) => line\n    (\"Countries and IBAN lengths: \" + line + \".\") ^0\n    \"Only the length is checked for each country, not the layout of its account part.\" ^0\n\n\"== IBAN checker ==\" ^0\n\"An IBAN is valid when, with its first four characters moved to the end and its letters turned into numbers, it leaves 1 when divided by 97.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        ask_iban(\"IBAN\") => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n        elif s != \"!\":\n            show_check(s)\n    elif choice == \"2\":\n        make()\n    elif choice == \"3\":\n        look_for_typos()\n    elif choice == \"4\":\n        show_countries()\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import iban\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef ask_iban(prompt):\n    t = plain(input(prompt + \"> \"))\n    if t == \"\":\n        return \"\"\n    s = iban.clean(t)\n    if s == \"\":\n        print(\"Type letters and digits only; spaces are fine.\")\n        return \"!\"\n    return s\n\ndef show_check(s):\n    r = iban.check(s)\n    print(iban.grouped(s))\n    lengths = iban.countries()\n    if len(s) >= 2 and s[0:2] in lengths:\n        print(\"  country \" + s[0:2] + \": \" + str(lengths[s[0:2]]) + \" characters; this one has \" + str(len(s)) + \".\")\n    if r[0] or r[1] == \"mod 97 is not 1, so a character is wrong\":\n        moved = iban.rearranged(s)\n        digits = iban.expand(moved)\n        print(\"  first four to the end: \" + moved)\n        print(\"  letters as numbers:    \" + digits + \" (\" + str(len(digits)) + \" digits)\")\n        m = iban.mod97_steps(digits)\n        print(\"  mod 97, 9 digits at a time:\")\n        for st in m[1]:\n            print(\"    \" + st[0] + \" mod 97 = \" + str(st[1]))\n        print(\"  the whole number mod 97 = \" + str(int(digits) % 97) + \", the same.\")\n    if r[0]:\n        print(\"Valid: mod 97 is 1.\")\n    else:\n        print(\"Not valid: \" + r[1] + \".\")\n\ndef make():\n    c = plain(input(\"country (two letters)> \"))\n    if c == \"\":\n        print(\"Cancelled.\")\n        return\n    c = iban.clean(c)\n    lengths = iban.countries()\n    if not c in lengths:\n        print(\"Type a country from the list, such as DE or GB.\")\n        return\n    need = lengths[c] - 4\n    b = plain(input(\"account part (\" + str(need) + \" letters and digits)> \"))\n    if b == \"\":\n        print(\"Cancelled.\")\n        return\n    b = iban.clean(b)\n    if len(b) != need:\n        print(\"A \" + c + \" account part has \" + str(need) + \" characters, letters and digits, spaces left out.\")\n        return\n    d = iban.check_digits(c, b)\n    s = c + d + b\n    print(\"Check digits: 98 - (\" + b + c + \"00, letters as numbers, mod 97) = \" + d + \".\")\n    show_check(s)\n    zeros = 0\n    while zeros < len(b) and b[zeros] == \"0\":\n        zeros = zeros + 1\n    if zeros > 0:\n        short = b[zeros:len(b)]\n        print(\"The account part starts with \" + str(zeros) + \" zero\" + plural(zeros) + \". Kept as text they stay; read as a number they would be lost, leaving \" + str(len(short)) + \" characters where \" + c + \" needs \" + str(need) + \".\")\n\ndef plural(n):\n    if n == 1:\n        return \"\"\n    return \"s\"\n\ndef look_for_typos():\n    s = ask_iban(\"an IBAN that fails\")\n    if s == \"\":\n        print(\"Cancelled.\")\n        return\n    if s == \"!\":\n        return\n    r = iban.check(s)\n    if r[0]:\n        print(\"That one is valid. A wrong digit in place of a digit, or a wrong letter in place of a letter, always changes the remainder; so does swapping two different digits, or two different letters, next to each other.\")\n        print(\"A letter swapped with a digit next to it in the account part slips through when the two are B and 1, M and 2, or X and 3: B is 11, M is 22 and X is 33.\")\n        return\n    if r[1] != \"mod 97 is not 1, so a character is wrong\":\n        print(\"Not that kind of failure: \" + r[1] + \".\")\n        return\n    found = iban.typos(s)\n    n_rep = 0\n    n_swap = 0\n    for f in found:\n        if f[0] == \"replace\":\n            n_rep = n_rep + 1\n        else:\n            n_swap = n_swap + 1\n    print(\"One character changed: \" + str(n_rep) + \" IBAN\" + plural(n_rep) + \" pass. Two neighbours swapped: \" + str(n_swap) + \" pass\" + verb(n_swap) + \".\")\n    for f in found:\n        kind = \"swap   \"\n        if f[0] == \"replace\":\n            kind = \"replace\"\n        print(\"  \" + kind + \"  \" + iban.grouped(f[1]))\n    print(\"Mod 97 catches the mistake but cannot say which one it was: each of these is a valid IBAN.\")\n\ndef verb(n):\n    if n == 1:\n        return \"es\"\n    return \"\"\n\ndef show_countries():\n    lengths = iban.countries()\n    line = \"\"\n    for c in [\"AT\", \"BE\", \"CH\", \"DE\", \"DK\", \"ES\", \"FI\", \"FR\", \"GB\", \"IE\", \"IT\", \"LU\", \"NL\", \"NO\", \"PL\", \"PT\", \"SE\"]:\n        if line != \"\":\n            line = line + \", \"\n        line = line + c + \" \" + str(lengths[c])\n    print(\"Countries and IBAN lengths: \" + line + \".\")\n    print(\"Only the length is checked for each country, not the layout of its account part.\")\n\nprint(\"== IBAN checker ==\")\nprint(\"An IBAN is valid when, with its first four characters moved to the end and its letters turned into numbers, it leaves 1 when divided by 97.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        s = ask_iban(\"IBAN\")\n        if s == \"\":\n            print(\"Cancelled.\")\n        elif s != \"!\":\n            show_check(s)\n    elif choice == \"2\":\n        make()\n    elif choice == \"3\":\n        look_for_typos()\n    elif choice == \"4\":\n        show_countries()\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "iban.eml",
      "eml": "# P101 IBAN checker - the ISO 13616 rule: move the first four characters\n# to the end, turn letters into numbers (A = 10 ... Z = 35), and the whole\n# number mod 97 must be 1. The number has 30 or more digits; as in the\n# corpus case iban-mod97-validator it is taken mod 97 a piece at a time,\n# never holding more than 9 digits, and checked against the whole number.\n# An IBAN is text, never a number: as in the corpus case\n# leading-zero-identifiers, its leading zeros are part of it.\n\ndef countries():\n    # length of a full IBAN, by country (only the length is checked, not\n    # each country's own layout of the account part)\n    return {\"AT\": 20, \"BE\": 16, \"CH\": 21, \"DE\": 22, \"DK\": 18, \"ES\": 24, \"FI\": 18, \"FR\": 27, \"GB\": 22, \"IE\": 22, \"IT\": 27, \"LU\": 20, \"NL\": 18, \"NO\": 15, \"PL\": 28, \"PT\": 25, \"SE\": 24}\n\ndef letter_values():\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => abc\n    {} => out\n    for i in [0:25]:\n        10 + i => out[abc[i]]\n    return out\n\ndef is_digit(c):\n    return c >= \"0\" and c <= \"9\"\n\ndef is_letter(c):\n    return c >= \"A\" and c <= \"Z\"\n\ndef clean(text):\n    # spaces dropped, lower case raised; \"\" if any other character is left\n    \"abcdefghijklmnopqrstuvwxyz\" => low\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => up\n    {} => raise_\n    for i in [0:25]:\n        up[i] => raise_[low[i]]\n    \"\" => out\n    for c in text:\n        if c != \" \":\n            if c in raise_:\n                out + raise_[c] => out\n            elif is_digit(c) or is_letter(c):\n                out + c => out\n            else:\n                return \"\"\n    return out\n\ndef grouped(s):\n    \"\" => out\n    for i in [0:len(s) - 1]:\n        if i > 0 and i % 4 == 0:\n            out + \" \" => out\n        out + s[i] => out\n    return out\n\ndef expand(s):\n    # every letter as its two digits\n    letter_values() => v\n    \"\" => out\n    for c in s:\n        if is_letter(c):\n            out + str(v[c]) => out\n        else:\n            out + c => out\n    return out\n\ndef mod97_steps(digits):\n    # [remainder, steps]: up to 9 digits at a time, the remainder so far\n    # written in front of the next piece; each step is [piece, remainder]\n    [] => steps\n    \"\" => carry\n    0 => i\n    0 => r\n    while i < len(digits):\n        9 - len(carry) => take\n        carry + digits[i:i + take] => piece\n        int(piece) % 97 => r\n        steps + [[piece, r]] => steps\n        str(r) => carry\n        i + take => i\n    return [r, steps]\n\ndef rearranged(s):\n    return s[4:len(s)] + s[0:4]\n\ndef check(s):\n    # [valid, reason]; s is already clean\n    countries() => lengths\n    if len(s) < 5:\n        return [False, \"too short to be an IBAN\"]\n    if not (is_letter(s[0]) and is_letter(s[1])):\n        return [False, \"an IBAN starts with two letters, the country\"]\n    if not (s[0:2] in lengths):\n        return [False, s[0:2] + \" is not a country in this list\"]\n    if not (is_digit(s[2]) and is_digit(s[3])):\n        return [False, \"the third and fourth characters, the check digits, must be digits\"]\n    if len(s) != lengths[s[0:2]]:\n        return [False, s[0:2] + \" IBANs have \" + str(lengths[s[0:2]]) + \" characters, this has \" + str(len(s))]\n    if mod97_steps(expand(rearranged(s)))[0] != 1:\n        return [False, \"mod 97 is not 1, so a character is wrong\"]\n    return [True, \"mod 97 is 1\"]\n\ndef check_digits(country, bban):\n    # the two digits that make country + digits + bban come out at 1\n    mod97_steps(expand(bban + country + \"00\"))[0] => r\n    98 - r => d\n    if d < 10:\n        return \"0\" + str(d)\n    return str(d)\n\ndef typos(s):\n    # [[kind, IBAN], ...]: every IBAN one change away from s that passes -\n    # one character replaced by another of its kind, or two neighbours\n    # swapped\n    \"0123456789\" => digits\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\" => letters\n    [] => out\n    for i in [0:len(s) - 1]:\n        digits => pool\n        if is_letter(s[i]):\n            letters => pool\n        for c in pool:\n            if c != s[i]:\n                s[0:i] + c + s[i + 1:len(s)] => t\n                if check(t)[0]:\n                    out + [[\"replace\", t]] => out\n    for i in [0:len(s) - 2]:\n        if s[i] != s[i + 1]:\n            s[0:i] + s[i + 1] + s[i] + s[i + 2:len(s)] => t\n            if check(t)[0]:\n                out + [[\"swap\", t]] => out\n    return out\n",
      "python": "def countries():\n    return {\"AT\": 20, \"BE\": 16, \"CH\": 21, \"DE\": 22, \"DK\": 18, \"ES\": 24, \"FI\": 18, \"FR\": 27, \"GB\": 22, \"IE\": 22, \"IT\": 27, \"LU\": 20, \"NL\": 18, \"NO\": 15, \"PL\": 28, \"PT\": 25, \"SE\": 24}\n\ndef letter_values():\n    abc = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    out = {}\n    for i in range(0, 26):\n        out[abc[i]] = 10 + i\n    return out\n\ndef is_digit(c):\n    return c >= \"0\" and c <= \"9\"\n\ndef is_letter(c):\n    return c >= \"A\" and c <= \"Z\"\n\ndef clean(text):\n    low = \"abcdefghijklmnopqrstuvwxyz\"\n    up = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    raise_ = {}\n    for i in range(0, 26):\n        raise_[low[i]] = up[i]\n    out = \"\"\n    for c in text:\n        if c != \" \":\n            if c in raise_:\n                out = out + raise_[c]\n            elif is_digit(c) or is_letter(c):\n                out = out + c\n            else:\n                return \"\"\n    return out\n\ndef grouped(s):\n    out = \"\"\n    for i in range(0, len(s)):\n        if i > 0 and i % 4 == 0:\n            out = out + \" \"\n        out = out + s[i]\n    return out\n\ndef expand(s):\n    v = letter_values()\n    out = \"\"\n    for c in s:\n        if is_letter(c):\n            out = out + str(v[c])\n        else:\n            out = out + c\n    return out\n\ndef mod97_steps(digits):\n    steps = []\n    carry = \"\"\n    i = 0\n    r = 0\n    while i < len(digits):\n        take = 9 - len(carry)\n        piece = carry + digits[i:i + take]\n        r = int(piece) % 97\n        steps = steps + [[piece, r]]\n        carry = str(r)\n        i = i + take\n    return [r, steps]\n\ndef rearranged(s):\n    return s[4:len(s)] + s[0:4]\n\ndef check(s):\n    lengths = countries()\n    if len(s) < 5:\n        return [False, \"too short to be an IBAN\"]\n    if not (is_letter(s[0]) and is_letter(s[1])):\n        return [False, \"an IBAN starts with two letters, the country\"]\n    if not s[0:2] in lengths:\n        return [False, s[0:2] + \" is not a country in this list\"]\n    if not (is_digit(s[2]) and is_digit(s[3])):\n        return [False, \"the third and fourth characters, the check digits, must be digits\"]\n    if len(s) != lengths[s[0:2]]:\n        return [False, s[0:2] + \" IBANs have \" + str(lengths[s[0:2]]) + \" characters, this has \" + str(len(s))]\n    if mod97_steps(expand(rearranged(s)))[0] != 1:\n        return [False, \"mod 97 is not 1, so a character is wrong\"]\n    return [True, \"mod 97 is 1\"]\n\ndef check_digits(country, bban):\n    r = mod97_steps(expand(bban + country + \"00\"))[0]\n    d = 98 - r\n    if d < 10:\n        return \"0\" + str(d)\n    return str(d)\n\ndef typos(s):\n    digits = \"0123456789\"\n    letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    out = []\n    for i in range(0, len(s)):\n        pool = digits\n        if is_letter(s[i]):\n            pool = letters\n        for c in pool:\n            if c != s[i]:\n                t = s[0:i] + c + s[i + 1:len(s)]\n                if check(t)[0]:\n                    out = out + [[\"replace\", t]]\n    for i in range(0, len(s) - 2+1):\n        if s[i] != s[i + 1]:\n            t = s[0:i] + s[i + 1] + s[i] + s[i + 2:len(s)]\n            if check(t)[0]:\n                out = out + [[\"swap\", t]]\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\nGB82-WEST\n1\n\n1\n1234\n1\n12AB 3456 7890\n1\nXX12 3456 7890\n1\nGBAB WEST 1234 5698 7654 32\n2\nGermany\n2\nDE\n123\n2\n\n2\nDE\n\n3\nDE89 3704\n3\nx!\n3\n\n5\n",
      "screen": "== IBAN checker ==\nAn IBAN is valid when, with its first four characters moved to the end and its letters turned into numbers, it leaves 1 when divided by 97.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> GB82-WEST\nType letters and digits only; spaces are fine.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> \nCancelled.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> 1234\n1234\nNot valid: too short to be an IBAN.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> 12AB 3456 7890\n12AB 3456 7890\nNot valid: an IBAN starts with two letters, the country.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> XX12 3456 7890\nXX12 3456 7890\nNot valid: XX is not a country in this list.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> GBAB WEST 1234 5698 7654 32\nGBAB WEST 1234 5698 7654 32\n  country GB: 22 characters; this one has 22.\nNot valid: the third and fourth characters, the check digits, must be digits.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> Germany\nType a country from the list, such as DE or GB.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> DE\naccount part (18 letters and digits)> 123\nA DE account part has 18 characters, letters and digits, spaces left out.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> \nCancelled.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> DE\naccount part (18 letters and digits)> \nCancelled.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 3\nan IBAN that fails> DE89 3704\nNot that kind of failure: DE IBANs have 22 characters, this has 8.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 3\nan IBAN that fails> x!\nType letters and digits only; spaces are fine.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 3\nan IBAN that fails> \nCancelled.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\nGB82 WEST 1234 5698 7654 32\n1\ngb82 west 1234 5698 7654 33\n1\nDE89 3704 0044 0532 0130 0\n2\nDE\n370400440532013000\n2\ndk\n0040 0440 1162 43\n2\nGB\nABCB 1234 5678 9012 34\n1\nGB60 ABC1 B234 5678 9012 34\n3\nGB82 WEST 1234 5698 7654 23\n3\nGB60 ABCB 1234 5678 9012 34\n4\n5\n",
      "screen": "== IBAN checker ==\nAn IBAN is valid when, with its first four characters moved to the end and its letters turned into numbers, it leaves 1 when divided by 97.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> GB82 WEST 1234 5698 7654 32\nGB82 WEST 1234 5698 7654 32\n  country GB: 22 characters; this one has 22.\n  first four to the end: WEST12345698765432GB82\n  letters as numbers:    3214282912345698765432161182 (28 digits)\n  mod 97, 9 digits at a time:\n    321428291 mod 97 = 70\n    702345698 mod 97 = 29\n    297654321 mod 97 = 24\n    2461182 mod 97 = 1\n  the whole number mod 97 = 1, the same.\nValid: mod 97 is 1.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> gb82 west 1234 5698 7654 33\nGB82 WEST 1234 5698 7654 33\n  country GB: 22 characters; this one has 22.\n  first four to the end: WEST12345698765433GB82\n  letters as numbers:    3214282912345698765433161182 (28 digits)\n  mod 97, 9 digits at a time:\n    321428291 mod 97 = 70\n    702345698 mod 97 = 29\n    297654331 mod 97 = 34\n    3461182 mod 97 = 28\n  the whole number mod 97 = 28, the same.\nNot valid: mod 97 is not 1, so a character is wrong.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> DE89 3704 0044 0532 0130 0\nDE89 3704 0044 0532 0130 0\n  country DE: 22 characters; this one has 21.\nNot valid: DE IBANs have 22 characters, this has 21.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> DE\naccount part (18 letters and digits)> 370400440532013000\nCheck digits: 98 - (370400440532013000DE00, letters as numbers, mod 97) = 89.\nDE89 3704 0044 0532 0130 00\n  country DE: 22 characters; this one has 22.\n  first four to the end: 370400440532013000DE89\n  letters as numbers:    370400440532013000131489 (24 digits)\n  mod 97, 9 digits at a time:\n    370400440 mod 97 = 23\n    235320130 mod 97 = 70\n    700013148 mod 97 = 38\n    389 mod 97 = 1\n  the whole number mod 97 = 1, the same.\nValid: mod 97 is 1.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> dk\naccount part (14 letters and digits)> 0040 0440 1162 43\nCheck digits: 98 - (00400440116243DK00, letters as numbers, mod 97) = 50.\nDK50 0040 0440 1162 43\n  country DK: 18 characters; this one has 18.\n  first four to the end: 00400440116243DK50\n  letters as numbers:    00400440116243132050 (20 digits)\n  mod 97, 9 digits at a time:\n    004004401 mod 97 = 47\n    471624313 mod 97 = 31\n    312050 mod 97 = 1\n  the whole number mod 97 = 1, the same.\nValid: mod 97 is 1.\nThe account part starts with 2 zeros. Kept as text they stay; read as a number they would be lost, leaving 12 characters where DK needs 14.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 2\ncountry (two letters)> GB\naccount part (18 letters and digits)> ABCB 1234 5678 9012 34\nCheck digits: 98 - (ABCB12345678901234GB00, letters as numbers, mod 97) = 60.\nGB60 ABCB 1234 5678 9012 34\n  country GB: 22 characters; this one has 22.\n  first four to the end: ABCB12345678901234GB60\n  letters as numbers:    1011121112345678901234161160 (28 digits)\n  mod 97, 9 digits at a time:\n    101112111 mod 97 = 87\n    872345678 mod 97 = 40\n    409012341 mod 97 = 7\n    761160 mod 97 = 1\n  the whole number mod 97 = 1, the same.\nValid: mod 97 is 1.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 1\nIBAN> GB60 ABC1 B234 5678 9012 34\nGB60 ABC1 B234 5678 9012 34\n  country GB: 22 characters; this one has 22.\n  first four to the end: ABC1B2345678901234GB60\n  letters as numbers:    1011121112345678901234161160 (28 digits)\n  mod 97, 9 digits at a time:\n    101112111 mod 97 = 87\n    872345678 mod 97 = 40\n    409012341 mod 97 = 7\n    761160 mod 97 = 1\n  the whole number mod 97 = 1, the same.\nValid: mod 97 is 1.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 3\nan IBAN that fails> GB82 WEST 1234 5698 7654 23\nOne character changed: 5 IBANs pass. Two neighbours swapped: 2 pass.\n  replace  GB82 WPST 1234 5698 7654 23\n  replace  GB82 WESV 1234 5698 7654 23\n  replace  GB82 WEST 1834 5698 7654 23\n  replace  GB82 WEST 1234 5698 7754 23\n  replace  GB82 WEST 1234 5698 7657 23\n  swap     GB82 WETS 1234 5698 7654 23\n  swap     GB82 WEST 1234 5698 7654 32\nMod 97 catches the mistake but cannot say which one it was: each of these is a valid IBAN.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 3\nan IBAN that fails> GB60 ABCB 1234 5678 9012 34\nThat one is valid. A wrong digit in place of a digit, or a wrong letter in place of a letter, always changes the remainder; so does swapping two different digits, or two different letters, next to each other.\nA letter swapped with a digit next to it in the account part slips through when the two are B and 1, M and 2, or X and 3: B is 11, M is 22 and X is 33.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 4\nCountries and IBAN lengths: AT 20, BE 16, CH 21, DE 22, DK 18, ES 24, FI 18, FR 27, GB 22, IE 22, IT 27, LU 20, NL 18, NO 15, PL 28, PT 25, SE 24.\nOnly the length is checked for each country, not the layout of its account part.\n\n1) check an IBAN  2) make one  3) look for a typo  4) countries  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "iban-mod97-validator",
      "caseId": "204-iban-mod97-validator",
      "title": "Mod 97 on a number too big to hold"
    },
    {
      "slug": "leading-zero-identifiers",
      "caseId": "262-leading-zero-identifiers",
      "title": "Leading-zero identifiers — two customers, one key"
    }
  ],
  "updated": "2026-10-16"
}
