{
  "id": "P063",
  "slug": "check-digits",
  "key": "P063-check-digits",
  "title": "Check digits",
  "summary": "Luhn, ISBN-10 and ISBN-13 / EAN-13 check digits: check a number, add the check digit to one without, and test a valid number against every single wrong digit and every swap of neighbouring digits to see which slips each scheme catches - and which it lets through.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P063 - Check digits\n\nThree check-digit schemes: Luhn, used on card and phone-equipment numbers,\nISBN-10, and ISBN-13 / EAN-13, the barcode number on books and goods. You\ncan check a number, add the check digit to a number without one, or test\na valid number against every single wrong digit and every swap of two\nneighbouring digits, to see which slips the scheme catches and which it\nlets through. A check digit guards against slips of the hand, not against\nanyone faking a number.\n\n- `main.eml` - the menu, choosing a scheme, reading digits, and the\n  messages\n- `checks.eml` - the three weighted sums, the check digit, and the error\n  test\n\nHow each part works:\n\n- Luhn: as in the corpus case `luhn-algorithm`, start from the right and\n  double every second digit, taking 9 off a result above 9; the number is\n  valid when the total is a multiple of 10.\n- ISBN-10: the digits weigh 10, 9, ... down to 1 from the left, and the\n  total must be a multiple of 11. The check digit can need the value 10,\n  which is written X.\n- ISBN-13 / EAN-13: the digits weigh 1, 3, 1, 3, ... from the left, and the\n  total must be a multiple of 10.\n- The check digit is the one last digit that makes the number valid;\n  since the last digit weighs 1, exactly one does.\n- The error test changes the number every possible way that a typing slip\n  would: each digit to each other digit, and each pair of different\n  neighbours swapped. It counts what the check catches.\n  - All three catch every single wrong digit.\n  - Only ISBN-10 also catches every swap, because 11 is prime.\n  - Luhn lets a 09 / 90 swap through: doubled, 0 stays 0 and 9 becomes\n    18, which counts as 9, so both orders add up to 9.\n  - EAN-13 lets a swap of two digits 5 apart through: with weights 1 and 3,\n    a swap changes the total by twice the difference of the two digits,\n    and twice 5 is 10.\n\nWhat is checked: menu choices 1 to 4; schemes 1 to 3; digits, with spaces\nand hyphens skipped. An X is allowed only as an ISBN-10's last digit. The\nlengths are 2 to 19 digits for Luhn, 10 for ISBN-10 and 13 for EAN-13,\neach one fewer before the check digit is added. An error test starts from\na valid number. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the published examples 79927398713 (Luhn), 0-306-40615-2 (ISBN-10) and\n  978-0-306-40615-7 (ISBN-13), each checked with its sum;\n- check digits added: X for 155404295, 1 for 400638133393, and 7 for\n  40903;\n- error tests:\n  - 409037 under Luhn misses its 09 and 90 swaps;\n  - 4006381333931 under EAN-13 misses the 38 swap;\n  - 0306406152 under ISBN-10 catches every swap.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x, and scheme 4;\n- ISBN-10 input 12345, abc, and 15540429X5 with an X that is not last;\n- 79927398710, which is not valid (its check digit should be 3), checked\n  and then error-tested;\n- a 5-digit EAN-13 body.\n\nBuilt on the verified corpus case `luhn-algorithm` (doubling every second\ndigit from the right, the total a multiple of 10).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P063 check digits: check a number against Luhn, ISBN-10 or ISBN-13 /\n# EAN-13, add the check digit to a number without one, and test which\n# typing errors each scheme would catch on a given number. A check digit\n# guards against slips of the hand, not against anyone faking a number.\nimport checks\n\ndef trim(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_scheme():\n    \"  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\" ^0\n    while True:\n        trim(input(\"scheme> \")) => answer\n        if answer == \"\":\n            return -1\n        if answer == \"1\" or answer == \"2\" or answer == \"3\":\n            return int(answer) - 1\n        \"Type 1, 2 or 3.\" ^0\n\ndef ask_digits(scheme, with_check):\n    # The number's digits, or [] when the answer is empty.\n    checks.lengths(scheme) => span\n    span[0] => low\n    span[1] => high\n    if not with_check:\n        low - 1 => low\n        high - 1 => high\n    while True:\n        trim(input(\"digits (spaces and hyphens are fine)> \")) => answer\n        if answer == \"\":\n            return []\n        checks.digits_of(scheme, answer, with_check) => ds\n        if len(ds) == 0:\n            if scheme == 1 and with_check:\n                \"Use digits only, with an X allowed as the last one.\" ^0\n            else:\n                \"Use digits only.\" ^0\n        elif len(ds) < low or len(ds) > high:\n            if low == high and with_check:\n                (\"An \" + checks.names()[scheme] + \" number has \" + str(low) + \" digits; that one has \" + str(len(ds)) + \".\") ^0\n            elif low == high:\n                (\"An \" + checks.names()[scheme] + \" number has \" + str(low) + \" digits before its check digit; that one has \" + str(len(ds)) + \".\") ^0\n            else:\n                (\"Type \" + str(low) + \" to \" + str(high) + \" digits; that one has \" + str(len(ds)) + \".\") ^0\n        else:\n            return ds\n\ndef check():\n    ask_scheme() => scheme\n    if scheme == -1:\n        \"Cancelled.\" ^0\n        return\n    ask_digits(scheme, True) => ds\n    if len(ds) == 0:\n        \"Cancelled.\" ^0\n        return\n    checks.weighted_total(scheme, ds) => total\n    checks.modulus(scheme) => m\n    if checks.valid(scheme, ds):\n        (checks.as_text(ds) + \" is valid: its \" + checks.names()[scheme] + \" sum is \" + str(total) + \", a multiple of \" + str(m) + \".\") ^0\n    else:\n        len(ds) => n\n        checks.check_digit(scheme, ds[0:n - 1]) => want\n        (checks.as_text(ds) + \" is not valid: its sum is \" + str(total) + \", which leaves \" + str(total % m) + \" over \" + str(m) + \". With these first digits the check digit would be \" + checks.digit_text(want) + \".\") ^0\n\ndef complete():\n    ask_scheme() => scheme\n    if scheme == -1:\n        \"Cancelled.\" ^0\n        return\n    ask_digits(scheme, False) => body\n    if len(body) == 0:\n        \"Cancelled.\" ^0\n        return\n    checks.check_digit(scheme, body) => d\n    (\"The check digit is \" + checks.digit_text(d) + \": \" + checks.as_text(body + [d]) + \".\") ^0\n\ndef test_errors():\n    ask_scheme() => scheme\n    if scheme == -1:\n        \"Cancelled.\" ^0\n        return\n    ask_digits(scheme, True) => ds\n    if len(ds) == 0:\n        \"Cancelled.\" ^0\n        return\n    if not checks.valid(scheme, ds):\n        \"Start from a valid number - use 2) to add its check digit.\" ^0\n        return\n    checks.error_test(scheme, ds) => r\n    (\"One wrong digit: \" + str(r[0]) + \" of the \" + str(r[1]) + \" ways are caught.\") ^0\n    (\"Two neighbouring digits swapped: \" + str(r[2]) + \" of the \" + str(r[3]) + \" swaps are caught.\") ^0\n    for miss in r[4]:\n        (\"  not caught: \" + checks.digit_text(miss[1]) + checks.digit_text(miss[2]) + \" -> \" + checks.digit_text(miss[2]) + checks.digit_text(miss[1]) + \" at digits \" + str(miss[0] + 1) + \" and \" + str(miss[0] + 2)) ^0\n\n\"== Check digits ==\" ^0\n\"Luhn, ISBN-10 and ISBN-13 / EAN-13: the last digit catches most slips of the hand.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) check a number  2) add the check digit  3) error test  4) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        check()\n    elif choice == \"2\":\n        complete()\n    elif choice == \"3\":\n        test_errors()\n    elif choice == \"4\":\n        False => running\n    else:\n        \"Pick a number from 1 to 4.\" ^0\n\"Bye.\" ^0\n",
      "python": "import checks\n\ndef trim(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_scheme():\n    print(\"  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\")\n    while True:\n        answer = trim(input(\"scheme> \"))\n        if answer == \"\":\n            return -1\n        if answer == \"1\" or answer == \"2\" or answer == \"3\":\n            return int(answer) - 1\n        print(\"Type 1, 2 or 3.\")\n\ndef ask_digits(scheme, with_check):\n    span = checks.lengths(scheme)\n    low = span[0]\n    high = span[1]\n    if not with_check:\n        low = low - 1\n        high = high - 1\n    while True:\n        answer = trim(input(\"digits (spaces and hyphens are fine)> \"))\n        if answer == \"\":\n            return []\n        ds = checks.digits_of(scheme, answer, with_check)\n        if len(ds) == 0:\n            if scheme == 1 and with_check:\n                print(\"Use digits only, with an X allowed as the last one.\")\n            else:\n                print(\"Use digits only.\")\n        elif len(ds) < low or len(ds) > high:\n            if low == high and with_check:\n                print(\"An \" + checks.names()[scheme] + \" number has \" + str(low) + \" digits; that one has \" + str(len(ds)) + \".\")\n            elif low == high:\n                print(\"An \" + checks.names()[scheme] + \" number has \" + str(low) + \" digits before its check digit; that one has \" + str(len(ds)) + \".\")\n            else:\n                print(\"Type \" + str(low) + \" to \" + str(high) + \" digits; that one has \" + str(len(ds)) + \".\")\n        else:\n            return ds\n\ndef check():\n    scheme = ask_scheme()\n    if scheme == -1:\n        print(\"Cancelled.\")\n        return\n    ds = ask_digits(scheme, True)\n    if len(ds) == 0:\n        print(\"Cancelled.\")\n        return\n    total = checks.weighted_total(scheme, ds)\n    m = checks.modulus(scheme)\n    if checks.valid(scheme, ds):\n        print(checks.as_text(ds) + \" is valid: its \" + checks.names()[scheme] + \" sum is \" + str(total) + \", a multiple of \" + str(m) + \".\")\n    else:\n        n = len(ds)\n        want = checks.check_digit(scheme, ds[0:n - 1])\n        print(checks.as_text(ds) + \" is not valid: its sum is \" + str(total) + \", which leaves \" + str(total % m) + \" over \" + str(m) + \". With these first digits the check digit would be \" + checks.digit_text(want) + \".\")\n\ndef complete():\n    scheme = ask_scheme()\n    if scheme == -1:\n        print(\"Cancelled.\")\n        return\n    body = ask_digits(scheme, False)\n    if len(body) == 0:\n        print(\"Cancelled.\")\n        return\n    d = checks.check_digit(scheme, body)\n    print(\"The check digit is \" + checks.digit_text(d) + \": \" + checks.as_text(body + [d]) + \".\")\n\ndef test_errors():\n    scheme = ask_scheme()\n    if scheme == -1:\n        print(\"Cancelled.\")\n        return\n    ds = ask_digits(scheme, True)\n    if len(ds) == 0:\n        print(\"Cancelled.\")\n        return\n    if not checks.valid(scheme, ds):\n        print(\"Start from a valid number - use 2) to add its check digit.\")\n        return\n    r = checks.error_test(scheme, ds)\n    print(\"One wrong digit: \" + str(r[0]) + \" of the \" + str(r[1]) + \" ways are caught.\")\n    print(\"Two neighbouring digits swapped: \" + str(r[2]) + \" of the \" + str(r[3]) + \" swaps are caught.\")\n    for miss in r[4]:\n        print(\"  not caught: \" + checks.digit_text(miss[1]) + checks.digit_text(miss[2]) + \" -> \" + checks.digit_text(miss[2]) + checks.digit_text(miss[1]) + \" at digits \" + str(miss[0] + 1) + \" and \" + str(miss[0] + 2))\n\nprint(\"== Check digits ==\")\nprint(\"Luhn, ISBN-10 and ISBN-13 / EAN-13: the last digit catches most slips of the hand.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) check a number  2) add the check digit  3) error test  4) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        check()\n    elif choice == \"2\":\n        complete()\n    elif choice == \"3\":\n        test_errors()\n    elif choice == \"4\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 4.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "checks.eml",
      "eml": "# P063 check digits - three schemes. A number is a list of digits 0 to 9,\n# with 10 standing for the X an ISBN-10 may end in. Each scheme adds a last\n# digit so that a weighted sum comes out a multiple of 10 (or 11), and a\n# typing error usually breaks that.\n\ndef names():\n    return [\"Luhn\", \"ISBN-10\", \"ISBN-13 / EAN-13\"]\n\ndef lengths(scheme):\n    # [shortest, longest] number, check digit included.\n    if scheme == 0:\n        return [2, 19]\n    if scheme == 1:\n        return [10, 10]\n    return [13, 13]\n\ndef luhn_total(ds):\n    # The corpus case luhn-algorithm: from the right, every second digit is\n    # doubled, 9 taken off when that passes 9, and everything added up.\n    0 => total\n    len(ds) => n\n    for i in [0:n - 1]:\n        ds[n - 1 - i] => d\n        if i % 2 == 1:\n            d * 2 => d\n            if d > 9:\n                d - 9 => d\n        total + d => total\n    return total\n\ndef weighted_total(scheme, ds):\n    if scheme == 0:\n        return luhn_total(ds)\n    0 => total\n    if scheme == 1:\n        # ISBN-10: weights 10, 9, ..., 1 from the left.\n        for i in [0:9]:\n            total + (10 - i) * ds[i] => total\n        return total\n    # EAN-13: weights 1, 3, 1, 3, ... from the left.\n    for i in [0:12]:\n        1 => w\n        if i % 2 == 1:\n            3 => w\n        total + w * ds[i] => total\n    return total\n\ndef modulus(scheme):\n    if scheme == 1:\n        return 11\n    return 10\n\ndef valid(scheme, ds):\n    return weighted_total(scheme, ds) % modulus(scheme) == 0\n\ndef check_digit(scheme, body):\n    # The last digit that makes body valid: try each, 0 to 9 (and X, 10,\n    # for ISBN-10). Exactly one works, since the last digit's weight is 1.\n    10 => top\n    if scheme == 1:\n        11 => top\n    for d in [0:top - 1]:\n        if valid(scheme, body + [d]):\n            return d\n    return -1\n\ndef digit_text(d):\n    if d == 10:\n        return \"X\"\n    return str(d)\n\ndef as_text(ds):\n    \"\" => s\n    for d in ds:\n        s + digit_text(d) => s\n    return s\n\ndef digits_of(scheme, s, with_check):\n    # The digits of s, spaces and hyphens skipped, or [] when anything else\n    # is there. An X (either case) is allowed only as an ISBN-10's check\n    # digit.\n    [] => out\n    \"\" => kept\n    for ch in s:\n        if ch != \" \" and ch != \"-\":\n            kept + ch => kept\n    for i in [0:len(kept) - 1]:\n        kept[i] => ch\n        if ch >= \"0\" and ch <= \"9\":\n            out + [int(ch)] => out\n        elif (ch == \"X\" or ch == \"x\") and scheme == 1 and with_check and i == len(kept) - 1:\n            out + [10] => out\n        else:\n            return []\n    return out\n\ndef error_test(scheme, ds):\n    # Every single wrong digit and every swap of two neighbouring different\n    # digits, and how many of each the check catches. Returns [singles\n    # caught, singles, swaps caught, swaps, the swaps that slip through].\n    0 => s_caught\n    0 => s_all\n    len(ds) => n\n    for i in [0:n - 1]:\n        9 => top\n        if scheme == 1 and i == n - 1:\n            10 => top\n        for d in [0:top]:\n            if d != ds[i]:\n                ds[0:n] => e\n                d => e[i]\n                s_all + 1 => s_all\n                if not valid(scheme, e):\n                    s_caught + 1 => s_caught\n    0 => t_caught\n    0 => t_all\n    [] => missed\n    for i in [0:n - 2]:\n        if ds[i] != ds[i + 1]:\n            ds[0:n] => e\n            ds[i + 1] => e[i]\n            ds[i] => e[i + 1]\n            # an X can only stand last\n            if not (scheme == 1 and e[n - 2] == 10):\n                t_all + 1 => t_all\n                if valid(scheme, e):\n                    missed + [[i, ds[i], ds[i + 1]]] => missed\n                else:\n                    t_caught + 1 => t_caught\n    return [s_caught, s_all, t_caught, t_all, missed]\n",
      "python": "def names():\n    return [\"Luhn\", \"ISBN-10\", \"ISBN-13 / EAN-13\"]\n\ndef lengths(scheme):\n    if scheme == 0:\n        return [2, 19]\n    if scheme == 1:\n        return [10, 10]\n    return [13, 13]\n\ndef luhn_total(ds):\n    total = 0\n    n = len(ds)\n    for i in range(0, n):\n        d = ds[n - 1 - i]\n        if i % 2 == 1:\n            d = d * 2\n            if d > 9:\n                d = d - 9\n        total = total + d\n    return total\n\ndef weighted_total(scheme, ds):\n    if scheme == 0:\n        return luhn_total(ds)\n    total = 0\n    if scheme == 1:\n        for i in range(0, 10):\n            total = total + (10 - i) * ds[i]\n        return total\n    for i in range(0, 13):\n        w = 1\n        if i % 2 == 1:\n            w = 3\n        total = total + w * ds[i]\n    return total\n\ndef modulus(scheme):\n    if scheme == 1:\n        return 11\n    return 10\n\ndef valid(scheme, ds):\n    return weighted_total(scheme, ds) % modulus(scheme) == 0\n\ndef check_digit(scheme, body):\n    top = 10\n    if scheme == 1:\n        top = 11\n    for d in range(0, top):\n        if valid(scheme, body + [d]):\n            return d\n    return -1\n\ndef digit_text(d):\n    if d == 10:\n        return \"X\"\n    return str(d)\n\ndef as_text(ds):\n    s = \"\"\n    for d in ds:\n        s = s + digit_text(d)\n    return s\n\ndef digits_of(scheme, s, with_check):\n    out = []\n    kept = \"\"\n    for ch in s:\n        if ch != \" \" and ch != \"-\":\n            kept = kept + ch\n    for i in range(0, len(kept)):\n        ch = kept[i]\n        if ch >= \"0\" and ch <= \"9\":\n            out = out + [int(ch)]\n        elif (ch == \"X\" or ch == \"x\") and scheme == 1 and with_check and i == len(kept) - 1:\n            out = out + [10]\n        else:\n            return []\n    return out\n\ndef error_test(scheme, ds):\n    s_caught = 0\n    s_all = 0\n    n = len(ds)\n    for i in range(0, n):\n        top = 9\n        if scheme == 1 and i == n - 1:\n            top = 10\n        for d in range(0, top+1):\n            if d != ds[i]:\n                e = ds[0:n]\n                e[i] = d\n                s_all = s_all + 1\n                if not valid(scheme, e):\n                    s_caught = s_caught + 1\n    t_caught = 0\n    t_all = 0\n    missed = []\n    for i in range(0, n - 2+1):\n        if ds[i] != ds[i + 1]:\n            e = ds[0:n]\n            e[i] = ds[i + 1]\n            e[i + 1] = ds[i]\n            if not (scheme == 1 and e[n - 2] == 10):\n                t_all = t_all + 1\n                if valid(scheme, e):\n                    missed = missed + [[i, ds[i], ds[i + 1]]]\n                else:\n                    t_caught = t_caught + 1\n    return [s_caught, s_all, t_caught, t_all, missed]\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\n4\n\n1\n2\n12345\nabc\n15540429X5\n\n1\n1\n79927398710\n3\n1\n79927398710\n2\n3\n12345\n\n4\n",
      "screen": "== Check digits ==\nLuhn, ISBN-10 and ISBN-13 / EAN-13: the last digit catches most slips of the hand.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 0\nPick a number from 1 to 4.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> x\nPick a number from 1 to 4.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 1\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 4\nType 1, 2 or 3.\nscheme> \nCancelled.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 1\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 2\ndigits (spaces and hyphens are fine)> 12345\nAn ISBN-10 number has 10 digits; that one has 5.\ndigits (spaces and hyphens are fine)> abc\nUse digits only, with an X allowed as the last one.\ndigits (spaces and hyphens are fine)> 15540429X5\nUse digits only, with an X allowed as the last one.\ndigits (spaces and hyphens are fine)> \nCancelled.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 1\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 1\ndigits (spaces and hyphens are fine)> 79927398710\n79927398710 is not valid: its sum is 67, which leaves 7 over 10. With these first digits the check digit would be 3.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 3\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 1\ndigits (spaces and hyphens are fine)> 79927398710\nStart from a valid number - use 2) to add its check digit.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 2\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 3\ndigits (spaces and hyphens are fine)> 12345\nAn ISBN-13 / EAN-13 number has 12 digits before its check digit; that one has 5.\ndigits (spaces and hyphens are fine)> \nCancelled.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n1\n79927398713\n1\n2\n0-306-40615-2\n1\n3\n978-0-306-40615-7\n2\n2\n155404295\n2\n3\n400638133393\n2\n1\n40903\n3\n1\n409037\n3\n3\n4006381333931\n3\n2\n0306406152\n4\n",
      "screen": "== Check digits ==\nLuhn, ISBN-10 and ISBN-13 / EAN-13: the last digit catches most slips of the hand.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 1\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 1\ndigits (spaces and hyphens are fine)> 79927398713\n79927398713 is valid: its Luhn sum is 70, a multiple of 10.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 1\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 2\ndigits (spaces and hyphens are fine)> 0-306-40615-2\n0306406152 is valid: its ISBN-10 sum is 132, a multiple of 11.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 1\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 3\ndigits (spaces and hyphens are fine)> 978-0-306-40615-7\n9780306406157 is valid: its ISBN-13 / EAN-13 sum is 100, a multiple of 10.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 2\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 2\ndigits (spaces and hyphens are fine)> 155404295\nThe check digit is X: 155404295X.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 2\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 3\ndigits (spaces and hyphens are fine)> 400638133393\nThe check digit is 1: 4006381333931.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 2\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 1\ndigits (spaces and hyphens are fine)> 40903\nThe check digit is 7: 409037.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 3\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 1\ndigits (spaces and hyphens are fine)> 409037\nOne wrong digit: 54 of the 54 ways are caught.\nTwo neighbouring digits swapped: 3 of the 5 swaps are caught.\n  not caught: 09 -> 90 at digits 2 and 3\n  not caught: 90 -> 09 at digits 3 and 4\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 3\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 3\ndigits (spaces and hyphens are fine)> 4006381333931\nOne wrong digit: 117 of the 117 ways are caught.\nTwo neighbouring digits swapped: 8 of the 9 swaps are caught.\n  not caught: 38 -> 83 at digits 5 and 6\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 3\n  1) Luhn  2) ISBN-10  3) ISBN-13 / EAN-13\nscheme> 2\ndigits (spaces and hyphens are fine)> 0306406152\nOne wrong digit: 91 of the 91 ways are caught.\nTwo neighbouring digits swapped: 9 of the 9 swaps are caught.\n\n1) check a number  2) add the check digit  3) error test  4) quit\nchoice> 4\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "luhn-algorithm",
      "caseId": "089-luhn-algorithm",
      "title": "Luhn algorithm"
    }
  ],
  "updated": "2026-10-14"
}
