9

CUSIPs are a 9-digit alphanumeric code for uniquely identifying a financial security.

https://en.wikipedia.org/wiki/CUSIP

They were invented in the 1964, and given the reliability of data transmission in the 60's, the 9th digit is actually a check digit used to confirm the validity of the first 8 characters. Sometimes, even today, you might find reason to want to validate a CUSIP, or perhaps a company or service obnoxiously decides to only transmit the 8-character CUSIP, even though this defeats the purpose of a check digit.

The procedure to generate the check digit is:

  1. Convert non-numeric digits to values according to their ordinal position in the alphabet plus 9 (A=10, B=11,...Z=35) and converting the characters *=36, @=37, #=38.

  2. Multiply every even digit by 2

  3. If the result of the multiplication is a two-digit number, add the digits together. (12 = 1 + 2 = 3)

  4. Get the sum of all values.

  5. Get the floored value of this operation: (10 - (sum modulo 10)) modulo 10.

What is the best/simplest way to get this value in C#?

Michu93
  • 5,058
  • 7
  • 47
  • 80
friggle
  • 3,362
  • 3
  • 35
  • 47

3 Answers3

13
public string GenerateCheckDigit(string cusip)
{        
    int sum = 0;
    char[] digits = cusip.ToUpper().ToCharArray();
    string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#";

    for (int i = 0; i < digits.Length; i++)
    {
        int val;
        if (!int.TryParse(digits[i].ToString(), out val))
            val = alphabet.IndexOf(digits[i]) + 10;
        
        if ((i % 2) != 0)
            val *= 2;

        val = (val % 10) + (val / 10);
        
        sum += val;
    }
    
    int check = (10 - (sum % 10)) % 10;

    return check.ToString();
}

Edit:

.NET Fiddle demonstrating this: https://dotnetfiddle.net/kspQWl

casperOne
  • 73,706
  • 19
  • 184
  • 253
friggle
  • 3,362
  • 3
  • 35
  • 47
  • 1
    This worked well for me. I needed ISIN checksum validation also and wrote a variation on your helper for that: http://stackoverflow.com/a/29170156/68115 – grenade May 11 '15 at 10:36
  • 1
    I don't know how C# handle division, but a more accurate version should be something like val = (val % 10) + int (val / 10); – Katelynn ruan Jul 26 '15 at 19:44
  • 1
    In C#, the result will be an int because we are dividing with an integer literal (10). Your comment is still good clarification though, for people who want to use this algorithm in a language other than C#. – friggle Jul 27 '15 at 14:06
  • The code as it's shown does not work for me: I had to change the digits.Length to digits.Lenght -1 to get the right results. The last checksum digit is not part of the calculation. You can argue that you pass only the first 8 characters in but then itis not a CUSIP as the variable name suggests. – anmatr Aug 21 '23 at 07:25
5

If you pre-compute the values of check digits, and store them in a lookup table, your computation of check digit would become much simpler:

private static readonly int[,] Check = new int[128, 2];

static CusipCheckSum() {
    var cusipChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#";
    for (var i = 0 ; i != cusipChars.Length ; i++) {
        Check[cusipChars[i], 0] = i%10 + i/10;
        Check[cusipChars[i], 1] = 2*i%10 + 2*i/10;
    }
}

With the 2D lookup array in place you can compute check digit in a single line of code:

var checkDigit = (10-(cusip.Select((ch, pos) => Check[ch, pos%2]).Sum()%10))%10;
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 2
    I've checked this solution and on large volumes it works ~2 times faster than the solution in the accepted answer. I've also verified the correctness on several cusips. – nightcoder Apr 13 '17 at 21:24
5

I see that there is no algo for java so adding it as well:

String generateCusipCheckDigit(String cusip) {
        final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#";
        int sum = 0;
        int value = 0;
        char[] cusipChars = cusip.toUpperCase().toCharArray();

        for (int i = 0; i < cusipChars.length; i++) {
            char actualChar = cusipChars[i];
            if (Character.isDigit(actualChar)) {
                value = Integer.parseInt(String.valueOf(actualChar));
            } else if (Character.isAlphabetic(actualChar)){
                value = alphabet.indexOf(actualChar) + 10;
            } else if (cusipChars[i] == '*'){
                value = 36;
            } else if (cusipChars[i] == '@'){
                value = 37;
            } else if (cusipChars[i] == '#'){
                value = 38;
            }

            if ((i % 2) != 0){
                value *= 2;
            }
            value = (value % 10) + (value / 10);
            sum += value;
        }
        int check = (10 - (sum % 10)) % 10;

        return String.valueOf(check);
    }

and some tests:

    @Test
    void checkDigitTest1(){
        String actual = generator.generateCusipCheckDigit("925524BF");

        Assertions.assertEquals("6", actual);
    }

    @Test
    void checkDigitTest2(){
        String actual = generator.generateCusipCheckDigit("90284B96");

        Assertions.assertEquals("2", actual);
    }

    @Test
    void checkDigitTest3(){
        String actual = generator.generateCusipCheckDigit("90284B97");

        Assertions.assertEquals("0", actual);
    }
Michu93
  • 5,058
  • 7
  • 47
  • 80