package com.thealgorithms.others; import java.util.Arrays; import java.util.Objects; /** * The Luhn algorithm or Luhn formula, also known as the "modulus 10" or "mod * 10" algorithm, named after its creator, IBM scientist Hans Peter Luhn, is a * simple checksum formula used to validate a variety of identification numbers. * *

* The algorithm is in the public domain and is in wide use today. It is * specified in ISO/IEC 7812-1. It is not intended to be a cryptographically * secure hash function; it was designed to protect against accidental errors, * not malicious attacks. Most credit cards and many government identification * numbers use the algorithm as a simple method of distinguishing valid numbers * from mistyped or otherwise incorrect numbers.

* *

* The Luhn algorithm will detect any single-digit error, as well as almost all * transpositions of adjacent digits. It will not, however, detect transposition * of the two-digit sequence 09 to 90 (or vice versa). It will detect most of * the possible twin errors (it will not detect 22 ↔ 55, 33 ↔ 66 or 44 ↔ * 77).

* *

* The check digit is computed as follows:

*
    *
  1. Take the original number and starting from the rightmost digit moving * left, double the value of every second digit (including the rightmost * digit).
  2. *
  3. Replace the resulting value at each position with the sum of the digits * of this position's value or just subtract 9 from all numbers more or equal * then 10.
  4. *
  5. Sum up the resulting values from all positions (s).
  6. *
  7. The calculated check digit is equal to {@code 10 - s % 10}.
  8. *
* * @see Wiki */ public class Luhn { /** * Check input digits array by Luhn algorithm. Initial array doesn't change * while processing. * * @param digits array of digits from 0 to 9 * @return true if check was successful, false otherwise */ public static boolean luhnCheck(int[] digits) { int[] numbers = Arrays.copyOf(digits, digits.length); int sum = 0; for (int i = numbers.length - 1; i >= 0; i--) { if (i % 2 == 0) { int temp = numbers[i] * 2; if (temp > 9) { temp = temp - 9; } numbers[i] = temp; } sum += numbers[i]; } return sum % 10 == 0; } public static void main(String[] args) { System.out.println("Luhn algorithm usage examples:"); int[] validInput = { 4, 5, 6, 1, 2, 6, 1, 2, 1, 2, 3, 4, 5, 4, 6, 7 }; int[] invalidInput = { 4, 5, 6, 1, 2, 6, 1, 2, 1, 2, 3, 4, 5, 4, 6, 4 }; //typo in last symbol checkAndPrint(validInput); checkAndPrint(invalidInput); System.out.println("\nBusiness examples:"); String validCardNumber = "5265 9251 6151 1412"; String invalidCardNumber = "4929 3231 3088 1896"; String illegalCardNumber = "4F15 BC06 3A88 76D5"; businessExample(validCardNumber); businessExample(invalidCardNumber); businessExample(illegalCardNumber); } private static void checkAndPrint(int[] input) { String validationResult = Luhn.luhnCheck(input) ? "valid" : "not valid"; System.out.println( "Input " + Arrays.toString(input) + " is " + validationResult ); } /* ======================== Business usage example ======================== */ /** * Object representation of credit card. */ private record CreditCard(int[] digits) { private static final int DIGITS_COUNT = 16; /** * @param cardNumber string representation of credit card number - 16 * digits. Can have spaces for digits separation * @return credit card object * @throws IllegalArgumentException if input string is not 16 digits or * if Luhn check was failed */ public static CreditCard fromString(String cardNumber) { Objects.requireNonNull(cardNumber); String trimmedCardNumber = cardNumber.replaceAll(" ", ""); if ( trimmedCardNumber.length() != DIGITS_COUNT || !trimmedCardNumber.matches("\\d+") ) { throw new IllegalArgumentException( "{" + cardNumber + "} - is not a card number" ); } int[] cardNumbers = toIntArray(trimmedCardNumber); boolean isValid = luhnCheck(cardNumbers); if (!isValid) { throw new IllegalArgumentException( "Credit card number {" + cardNumber + "} - have a typo" ); } return new CreditCard(cardNumbers); } /** * @return string representation separated by space every 4 digits. * Example: "5265 9251 6151 1412" */ public String number() { StringBuilder result = new StringBuilder(); for (int i = 0; i < DIGITS_COUNT; i++) { if (i % 4 == 0 && i != 0) { result.append(" "); } result.append(digits[i]); } return result.toString(); } @Override public String toString() { return String.format( "%s {%s}", CreditCard.class.getSimpleName(), number() ); } private static int[] toIntArray(String string) { return string.chars().map(i -> Character.digit(i, 10)).toArray(); } } private static void businessExample(String cardNumber) { try { System.out.println( "Trying to create CreditCard object from valid card number: " + cardNumber ); CreditCard creditCard = CreditCard.fromString(cardNumber); System.out.println( "And business object is successfully created: " + creditCard + "\n" ); } catch (IllegalArgumentException e) { System.out.println( "And fail with exception message: " + e.getMessage() + "\n" ); } } }