Generate a check digit and validate a Universal Loan Identifier in SAS

This article shows how to use SAS to implement the ISO algorithm to generate (and validate) a Universal Loan Identifier (ULI). A ULI is a long string of numbers and letters that serves as a unique identifier that is used for certain financial transactions. The ISO standard ensures that banks and other financial entities have a uniform way to identify accounts. The details are provided by a financial regulation that shows how to generate and validate a check digit for an account. One step that might challenge SAS programmers is computing the modulo operations on a long string of digits. A previous article explains why you should use a special function for that step, rather than converting the string to a numeric value and using the built-in MOD function in SAS.

What is a check digit?

Have you ever mistyped your credit card into a web site and been told that the credit card number is invalid? Did you wonder how the web site knew that you mistyped the number? When a long number or string is used to identify a financial account, it is common to embed a check digit into the number, which can help ensure that the number is typed and transmitted correctly. The credit card validation algorithm takes a number like 1789372997 and computes a new number (called the check digit) that is based on a modulo operation performed a transformation of the number (such as the sum of the digits). Chris Hemedinger has blogged about credit cards and the National Provider IDs. He shows how to use SAS to generate check digits and to validate that numbers are valid.

Generate a check digit for a ULI

Each financial entity is assigned 20-character alphanumeric Legal Entity Identifier (LEI). For example, the Wikipedia article about LEIs states that Jaguar Land Rover Ltd has the LEI 213800WSGIIZCXF1P572. Similarly, every account has an identifier such as 123456X. If you concatenate these strings together, the combined string 213800WSGIIZCXF1P572123456X uniquely identifies the account within the company. The documentation for the ISO standard tells you exactly how to generate a check digit for this long string:

Replace every letter (the case doesn't matter) with a digit according to the mapping 'A'=10, 'B'=11, ..., 'Z'=35. You now have a long string that consists entirely of digits.

Append the string '00' (two zeros) to the end of the string. Call the string N.

Apply the mathematical modulo operation r = mod(N,97) to find the remainder of dividing N by the prime number 97. You can perform this operation in SAS by stepping through the characters of the string. You should not convert the string to a double-precision number.

The following call to PROC FCMP defines a function that replaces the letters with two-digit numbers. The function from the previous article (which computes the modulo for a string of digits) is also defined.

proc fcmp outlib=work.banking.ULI;
/* Given an input string that contains numbers and letters,
upcase the string and transform it by replacing letters according to
'A'->'10', 'B'->'11', 'C'->'12', ..., 'Z'->'35',
The output is a 64-byte string, so make sure the input arg isn't too long
*/
function replace_alphabetic(ULI_alpha $) $64; /* return a 64-byte string */
length ULI $64;
array Letters[26] $1; /* target characters */
array Numbers[26] $2; /* replacement characters */
do i = 1 to dim(Letters);
Letters[i] = byte(64 + i); /* the ASCII seq: 'A'=65, ..., 'Z'=90 */
Numbers[i] = put(9 + i, 2.); /* '10', '11', '12', ..., '35' */
end;
ULI = upcase(ULI_alpha);
do i = 1 to dim(Letters);
ULI = tranwrd(ULI, Letters[i], Numbers[i]); /* replaces each letter with a 2-digit number */
end;
return ( ULI );
endsub;

/* Compute the remainder of a large integer (represented as a string)
when divided by d. This implements the standard long-division algorithm,
but at the i_th step the i_th character is converted to an integer.
*/
function mod_from_string(s $, d);
r = 0; /* initialize remainder */
do i = 1 to length(s);
c = substr(s, i, 1); /* i_th digit as character */
digit = input(c, 1.); /* i_th digit as integer */
r = mod(10*r + digit, d); /* running remainder */
end;
return( r ); /* return remainder as an integer */
endsub;
quit;

Create a ULI from LEI and loan ID

After you compute the check digit, you can use it to create a unique string is called a Universal Loan Identifier (ULI). Let's apply the algorithm to two financial entities (G.E. Financing GmbH and Jaguar Land Rover Ltd) that appear in the Wikipedia article about LEIs. The first entity has three loans. The second entity has two loans. Here are the example data:

data Loans;
length LEI $20 LoanID $8;
input LEI LoanID;
datalines;
54930084UKLVMY22DS16 999143X
54930084UKLVMY22DS16 999103Z
54930084UKLVMY22DS16 999104Z
213800WSGIIZCXF1P572 123456X
213800WSGIIZCXF1P572 789012Y
;

The ISO standard specifies how to create a check digit and form the ULI:

Concatenate the LEI and Loan ID strings. Replace letters with numbers.

Append '00' to end of string.

Compute the remainder r = mod(N,97), where N is the string of numbers in the previous step. You can do this without converting the string to a number.

Compute the check digit as 98 – r. This is a number between 2-98. Convert it to a two-character string '02'-'98'

The ULI is the string concatenation of the LEI, the loan ID, and the check digit.

For example, the following SAS DATA step creates a ULI for the LEIs and fictitious loan IDs in the LOANS data set:

option cmplib=work.banking; /* set search path for the FCMP function(s) */

/* Generate a check digit for a LEI and LoanId pair, then create a ULI.
The procedure for generating a check digit is documented at
https://www.consumerfinance.gov/rules-policy/regulations/1003/c/
*/
data Create_ULI;
set Loans; /* vars are LEI and LoanID */ /* Steps */
sA = cats(LEI, LoanID); /* 0: concat LEI and LoanID */
sN = replace_alphabetic(sA); /* 1: Replace letters with numbers */
L = length(sN);
substr(sN, L+1, 2) = '00'; /* 2: append '00' to end of string */
r = mod_from_string(sN, 97); /* 3: remainder mod 97 (as a number) */
checkdigit = 98 - r; /* 4: compute the check digit (as a number) */
ULI = cats(LEI, LoanID, put(checkdigit,Z2.)); /* 5: Append check digits (as a two-character string) */
keep LEI LoanID sN checkdigit ULI;
run;

proc print data=Create_ULI;
var checkdigit ULI;
run;

You can visually inspect the ULI column to verify that the ULI is the concatenation of the LEI, the loan ID, and the check digit.

Validate a ULI

If someone receives the ULI, they can easily extract the original LEI and the loan ID. However, they should first validate that the ULI does not contain any errors. To do that, you perform the following steps:

Replace the letters in the ULI with numbers.

Compute the remainder r = mod(N,97), where N is the string of numbers in the previous step. You can do this without converting the string to a number.

If the remainder is 1, conclude that the ULI is valid. Otherwise, reject the ULI.

Every ULI in the Create_ULI data set and valid. To demonstrate that the validation algorithm can detect invalid ULIs, the following DATA step changes a digit in the last ULI from a '5' to a '6':

/* Create an "error": to test whether the validation can detect errors, change a digit of the last ULI */
data ULI;
set Create_ULI(keep=ULI) end=EOF;
if EOF then
substr(ULI, 1, 1) = '6'; /* create an "error" in this ULI */
run;

For the new ULIs, the following SAS DATA step reads the ULIs and creates the remainders modulo 97:

/* Validate a ULI. The validation procedure is documented at
https://www.consumerfinance.gov/rules-policy/regulations/1003/c/
*/
data Validate_ULI;
set ULI; /* var is named ULI */
sN = replace_alphabetic(ULI); /* 1: Replace letters with numbers */
r = mod_from_string(sN, 97); /* 2: remainder mod 97 */
isValid = (r=1); /* 3: check if remainder is 1 */
run;

proc print data=Validate_ULI;
var ULI r isValid;
run;

The validation algorithm reports that the first four ULIs are valid. However, the last ULI, which we intentionally modified, is invalid.

Summary

This article shows how to use SAS to generate and validate a Universal Loan Identifier (ULI). A ULI is a long string of numbers and letters that serves as a unique identifier that is used for certain financial transactions. To ensure that the string is transmitted correctly, you must be able to compute the remainder modulo 97 of a long string of numbers. This article shows how to implement to helper functions in PROC FCMP that help you encode and decode ULIs.

Although the helper functions in this article are specific to a Universal Loan Identifier, there are many other strings of numbers and letters that serve as identifiers and for which you need to compute a check digit. You can modify the helper functions in this article to handle similar situations.