Skip to content

Commit b46ef33

Browse files
author
Akibuzzaman Akib
committed
maths: add Luhn algorithm for credit card number validation
- Implements luhn_check(card_number) using the mod-10 checksum - Handles double-and-subtract-9 step for values > 9 - Type hints on all parameters and return values - Doctests for valid Visa number, invalid numbers, edge cases - Raises ValueError for non-digit input - Wikipedia reference included
1 parent 791deb4 commit b46ef33

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

maths/luhn_algorithm.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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

Comments
 (0)