Algorithm::LUHN_XS - Very Fast XS Version of the original Algorithm::LUHN


  use Algorithm::LUHN_XS qw/check_digit is_valid/;

  my $c;
  $c = check_digit("43881234567");
  print "It works\n" if is_valid("43881234567$c");

  $c = check_digit("A2C4E6G8"); # this will return undef
  if (!defined($c)) {
      # couldn't create a check digit

  print "Valid LUHN characters are:\n";
  my %vc = Algorithm::LUHN_XS::valid_chars();
  for (sort keys %vc) {
    print "$_ => $vc{$_}\n";

  Algorithm::LUHN_XS::valid_chars(map {$_ => ord($_)-ord('A')+10} A..Z);
  $c = check_digit("A2C4E6G8");
  print "It worked again\n" if is_valid("A2C4E6G8$c");


This module is an XS version of the original Perl Module Algorithm::LUHN, which was written by Tim Ayers. It should work exactly the same, only substantially faster. The supplied check_digit() routine is 100% compatible with the pure Perl Algorithm::LUHN module, while the faster check_digit_fast() and really fast check_digit_rff() are not.

How much faster? Here's a benchmark, running on a 3.4GHz i7-2600:

Benchmark: timing 100 iterations

Algorithm::LUHN: 69 secs (69.37 usr 0.00 sys) 1.44/s

check_digit: 2 secs ( 1.98 usr 0.00 sys) 50.51/s

check_digit_fast: 2 secs ( 1.68 usr 0.00 sys) 59.52/s

check_digit_rff: 1 secs ( 1.29 usr 0.00 sys) 77.52/s

So, it's 35x to 53x faster than the original pure Perl module, depending on how much compatibility with the original module you need.

The rest of the documentation is mostly a copy of the original docs, with some additions for functions that are new.

This module calculates the Modulus 10 Double Add Double checksum, also known as the LUHN Formula. This algorithm is used to verify credit card numbers and Standard & Poor's security identifiers such as CUSIP's and CSIN's.

You can find plenty of information about the algorithm by searching the web for "modulus 10 double add double".



This function takes a credit-card number and returns true if the number passes the LUHN check.

Ie it returns true if the final character of CHECKSUMMED_NUM is the correct checksum for the rest of the number and false if not. Obviously the final character does not factor into the checksum calculation. False will also be returned if NUM contains in an invalid character as defined by valid_chars(). If NUM is not valid, $Algorithm::LUHN_XS::ERROR will contain the reason.

This function is equivalent to

  substr $N,length($N)-1 eq check_digit(substr $N,0,length($N)-1)

For example, 4242 4242 4242 4242 is a valid Visa card number, that is provided for test purposes. The final digit is '2', which is the right check digit. If you change it to a '3', it's not a valid card number. Ie:

    is_valid('4242424242424242');   # true
    is_valid('4242424242424243');   # false
is_valid_fast CHECKSUMMED_NUM
is_valid_rff CHECKSUMMED_NUM

As with check_digit(), we have 3 versions of is_valid(), each one progressively faster than the check_digit() that comes in the original pure Perl Algorithm::LUHN module. Here's a benchmark of 1M total calls to is_valid():

Benchmark: timing 100 iterations

Algorithm::LUHN: 100 secs (100.29 usr 0.01 sys) 1.00/s

is_valid: 3 secs ( 2.46 usr 0.11 sys) 38.91/s

is_valid_fast: 2 secs ( 2.38 usr 0.05 sys) 41.15/s

is_valid_rff: 2 secs ( 1.97 usr 0.08 sys) 48.78/s

Algorithm::LUHN_XS varies from 38x to 48x times faster than the original pure perl Algorithm::LUHN module. The is_valid() routine is 100% compatible with the original, returning either '1' for success or the empty string '' for failure. The is_valid_fast() routine returns 1 for success and 0 for failure. Finally, the is_valid_rff() function also returns 1 for success and 0 for failure, but only works with numeric input. If you supply any alpha characters, it will return 0.

check_digit NUM

This function returns the checksum of the given number. If it cannot calculate the check_digit it will return undef and set $Algorithm::LUHN_XS::ERROR to contain the reason why. This is much faster than the check_digit routine in the pure perl Algorithm::LUHN module, but only about half as fast as the check_digit_fast() function in this module, due to the need to return both integers and undef, which isn't fast with XS.

check_digit_fast NUM

This function returns the checksum of the given number. If it cannot calculate the check digit it will return -1 and set $Algorithm::LUHN_XS::ERROR to contain the reason why. It's about 20% faster than check_digit() because the XS code in this case only has to return integers.

check_digit_rff NUM

This function returns the checksum of the given number.

It's about 50% faster than check_digit() because it doesn't support the valid_chars() function, and only produces a valid output for numeric input. If you pass it input with alpha characters, it will return -1. Works great for Credit Cards, but not for things like CUSIP identifiers.

valid_chars LIST

By default this module only recognizes 0..9 as valid characters, but sometimes you want to consider other characters as valid, e.g. Standard & Poor's identifers may contain 0..9, A..Z, @, #, *. This function allows you to add additional characters to the accepted list.

LIST is a mapping of character => value. For example, Standard & Poor's maps A..Z to 10..35 so the LIST to add these valid characters would be (A, 10, B, 11, C, 12, ...)

Please note that this adds or re-maps characters, so any characters already considered valid but not in LIST will remain valid.

If you do not provide LIST, this function returns the current valid character map.

Note that the check_digit_rff() and is_valid_rff() functions do not support the valid_chars() function. Both only support numeric inputs, and map them to their literal values.


This module, because of how valid_chars() stores data in the XS portion, is NOT thread safe.

The _fast and _rff versions of is_valid() and check_digit() don't have the same return values for failure as the original Algorithm::LUHN module. Specifically:

  • is_valid_fast() and is_valid_rff() return 0 on failure, but is_valid() returns the empty string.

  • check_digit_fast() and check_digit_rff() return -1 on failure, but check_digit() returns undef.

Also, be careful with passing long numbers around. Perl will, depending on the context, convert things like 12345678912345 to 1.2345678912345e+1. Try to keep things in "string context".


Algorithm::LUHN is the original pure perl module this is based on.

Algorithm::CheckDigits provides a front-end to a large collection of modules for working with check digits.

Business::CreditCard provides three functions for checking credit card numbers. Business::CreditCard::Object provides an OO interface to those functions.

Business::CardInfo provides a class for holding credit card details, and has a type constraint on the card number, to ensure it passes the LUHN check.

Business::CCCheck provides a number of functions for checking credit card numbers.

Regexp::Common supports combined LUHN and issuer checking against a card number.

Algorithm::Damm implements a different kind of check digit algorithm, the Damm algorithm (Damm, not Damn).

Math::CheckDigits implements yet another approach to check digits.

Neil Bowers has also written a review of LUHN modules, which covers them in more detail than this section.



This module was written by Kerry Schwab (


Copyright (c) 2018 Kerry Schwab. All rights reserved. Derived from Algorithm::LUHN, which is (c) 2001 by Tim Ayers.


This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.


Tim Ayers, for the original pure perl version of Algorithm::LUHN.

Neil Bowers, the current maintainer of Algorithm::LUHN.

The inspiration for this module was a PerlMonks post I made here:, and I received help from several PerlMonks members: