|
| 1 | +""" |
| 2 | +Luhn Algorithm — credit card number validation. |
| 3 | +
|
| 4 | +The Luhn algorithm (also known as the "modulus 10" or "mod 10" algorithm) is a |
| 5 | +simple checksum formula used to validate identification numbers such as credit |
| 6 | +card numbers, IMEI numbers, and Canadian Social Insurance Numbers. |
| 7 | +
|
| 8 | +Algorithm: |
| 9 | + 1. From the rightmost digit (the check digit) and moving left, double the |
| 10 | + value of every second digit. |
| 11 | + 2. If the result of doubling is greater than 9, subtract 9. |
| 12 | + 3. Sum all the digits (the undoubled ones and the doubled/adjusted ones). |
| 13 | + 4. If the total modulo 10 is 0, then the number is valid. |
| 14 | +
|
| 15 | +References: |
| 16 | + - https://en.wikipedia.org/wiki/Luhn_algorithm |
| 17 | +""" |
| 18 | + |
| 19 | + |
| 20 | +def luhn_check(card_number: str) -> bool: |
| 21 | + """Validate a credit card number (given as a digit string) using the Luhn |
| 22 | + algorithm. |
| 23 | +
|
| 24 | + :param card_number: A string containing only digit characters representing |
| 25 | + the card number to validate. |
| 26 | + :raises ValueError: If *card_number* contains any non-digit characters. |
| 27 | + :return: ``True`` if the number is valid according to the Luhn algorithm, |
| 28 | + ``False`` otherwise. |
| 29 | +
|
| 30 | + Examples: |
| 31 | + >>> luhn_check("4532015112830366") # valid Visa |
| 32 | + True |
| 33 | + >>> luhn_check("4532015112830367") # invalid — check digit off by one |
| 34 | + False |
| 35 | + >>> luhn_check("1234567890123456") # invalid |
| 36 | + False |
| 37 | + >>> luhn_check("79927398713") # canonical Luhn-valid test number |
| 38 | + True |
| 39 | + >>> luhn_check("79927398714") # invalid |
| 40 | + False |
| 41 | + >>> luhn_check("0") # single zero is valid (trivially) |
| 42 | + True |
| 43 | + >>> luhn_check("abc123") |
| 44 | + Traceback (most recent call last): |
| 45 | + ... |
| 46 | + ValueError: luhn_check() only accepts strings of digits |
| 47 | + >>> luhn_check("4111 1111 1111 1111") |
| 48 | + Traceback (most recent call last): |
| 49 | + ... |
| 50 | + ValueError: luhn_check() only accepts strings of digits |
| 51 | + """ |
| 52 | + if not card_number.isdigit(): |
| 53 | + raise ValueError("luhn_check() only accepts strings of digits") |
| 54 | + |
| 55 | + digits = [int(d) for d in card_number] |
| 56 | + # Double every second digit from the right (index from end: 1, 3, 5, …) |
| 57 | + for i in range(len(digits) - 2, -1, -2): |
| 58 | + digits[i] *= 2 |
| 59 | + if digits[i] > 9: |
| 60 | + digits[i] -= 9 |
| 61 | + |
| 62 | + return sum(digits) % 10 == 0 |
| 63 | + |
| 64 | + |
| 65 | +if __name__ == "__main__": |
| 66 | + import doctest |
| 67 | + |
| 68 | + doctest.testmod() |
| 69 | + |
| 70 | + test_numbers = [ |
| 71 | + ("4532015112830366", True), |
| 72 | + ("4532015112830367", False), |
| 73 | + ("79927398713", True), |
| 74 | + ("1234567890123456", False), |
| 75 | + ] |
| 76 | + for number, expected in test_numbers: |
| 77 | + result = luhn_check(number) |
| 78 | + status = "✓" if result == expected else "✗" |
| 79 | + print(f"{status} luhn_check({number!r}) = {result}") |
0 commit comments