CUSIP

From Rosetta Code
Task
CUSIP
You are encouraged to solve this task according to the task description, using any language you may know.
This page uses content from Wikipedia. The original article was at CUSIP. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)


A   CUSIP   is a nine-character alphanumeric code that identifies a North American financial security for the purposes of facilitating clearing and settlement of trades. The CUSIP was adopted as an American National Standard under Accredited Standards X9.6.


Task

Ensure the last digit   (i.e., the   check digit)   of the CUSIP code (the 1st column) is correct, against the following:

  •   037833100       Apple Incorporated
  •   17275R102       Cisco Systems
  •   38259P508       Google Incorporated
  •   594918104       Microsoft Corporation
  •   68389X106       Oracle Corporation   (incorrect)
  •   68389X105       Oracle Corporation


Example pseudo-code below.
algorithm Cusip-Check-Digit(cusip) is
Input: an 8-character CUSIP
 
sum := 0
for 1 ≤ i ≤ 8 do
c := the ith character of cusip
if c is a digit then
v := numeric value of the digit c
else if c is a letter then
p := ordinal position of c in the alphabet (A=1, B=2...)
v := p + 9
else if c = "*" then
v := 36
else if c = "@" then
v := 37
else if' c = "#" then
v := 38
end if
if i is even then
v := v × 2
end if
 
sum := sum + int ( v div 10 ) + v mod 10
repeat
 
return (10 - (sum mod 10)) mod 10
end function
See related tasks



ALGOL 68[edit]

BEGIN
# returns TRUE if cusip is a valid CUSIP code #
OP ISCUSIP = ( STRING cusip )BOOL:
IF ( UPB cusip - LWB cusip ) /= 8
THEN
# code is wrong length #
FALSE
ELSE
# string is 9 characters long - check it is valid #
STRING cusip digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#"[ AT 0 ];
INT check digit := 0;
IF NOT char in string( cusip[ UPB cusip ], check digit, cusip digits )
THEN
# invalid check digit #
FALSE
ELSE
# OK so far compare the calculated check sum to the supplied one #
INT sum := 0;
INT c pos := LWB cusip - 1;
FOR i TO 8 DO
INT digit := 0;
IF NOT char in string( cusip[ i + c pos ], digit, cusip digits )
THEN
# invalid digit #
digit := -999
FI;
IF NOT ODD i
THEN
# even digit #
digit *:= 2
FI;
sum +:= ( digit OVER 10 ) + ( digit MOD 10 )
OD;
( 10 - ( sum MOD 10 ) ) MOD 10 = check digit
FI
FI ; # ISCUSIP #
 
# task test cases #
 
PROC test cusip = ( STRING cusip )VOID:
print( ( cusip, IF ISCUSIP cusip THEN " valid" ELSE " invalid" FI, newline ) );
 
test cusip( "037833100" );
test cusip( "17275R102" );
test cusip( "38259P508" );
test cusip( "594918104" );
test cusip( "68389X106" );
test cusip( "68389X105" )
END
Output:
037833100 valid
17275R102 valid
38259P508 valid
594918104 valid
68389X106 invalid
68389X105 valid

ALGOL W[edit]

Based on Algol 68

begin    % returns true if cusip is a valid CUSIP code %
logical procedure isCusip ( string(9) value cusip ) ;
begin
 % returns the base 39 digit corresponding to a character of a CUSIP code %
integer procedure cusipDigit( string(1) value cChar ) ;
if cChar >= "0" and cChar <= "9" then ( decode( cChar ) - decode( "0" ) )
else if cChar >= "A" and cChar <= "Z" then ( decode( cChar ) - decode( "A" ) ) + 10
else if cChar = "*" then 36
else if cChar = "@" then 37
else if cChar = "#" then 38
else  % invalid digit % -999 ;
 
integer checkDigit, sum;
checkDigit := cusipDigit( cusip( 8 // 1 ) );
for cPos := 1 until 8 do begin
integer digit;
digit := cusipDigit( cusip( ( cPos - 1 ) // 1 ) );
if not odd( cPos ) then digit := digit * 2;
sum := sum + ( digit div 10 ) + ( digit rem 10 )
end for_cPos ;
( ( 10 - ( sum rem 10 ) ) rem 10 ) = checkDigit
end isCusip ;
 
begin % task test cases %
procedure testCusip ( string(9) value cusip ) ;
write( s_w := 0, cusip, if isCusip( cusip ) then " valid" else " invalid" );
 
testCusip( "037833100" );
testCusip( "17275R102" );
testCusip( "38259P508" );
testCusip( "594918104" );
testCusip( "68389X106" );
testCusip( "68389X105" )
end testCases
end.
Output:
037833100 valid
17275R102 valid
38259P508 valid
594918104 valid
68389X106 invalid
68389X105 valid

AWK[edit]

 
# syntax: GAWK -f CUSIP.AWK
BEGIN {
n = split("037833100,17275R102,38259P508,594918104,68389X106,68389X105",arr,",")
for (i=1; i<=n; i++) {
printf("%9s %s\n",arr[i],cusip(arr[i]))
}
exit(0)
}
function cusip(n, c,i,sum,v,x) {
# returns: 1=OK, 0=NG, -1=bad data
if (length(n) != 9) {
return(-1)
}
for (i=1; i<=8; i++) {
c = substr(n,i,1)
if (c ~ /[0-9]/) {
v = c
}
else if (c ~ /[A-Z]/) {
v = index("ABCDEFGHIJKLMNOPQRSTUVWXYZ",c) + 9
}
else if (c == "*") {
v = 36
}
else if (c == "@") {
v = 37
}
else if (c == "#") {
v = 38
}
else {
return(-1)
}
if (i ~ /[02468]/) {
v *= 2
}
sum += int(v / 10) + (v % 10)
}
x = (10 - (sum % 10)) % 10
return(substr(n,9,1) == x ? 1 : 0)
}
 
Output:
037833100 1
17275R102 1
38259P508 1
594918104 1
68389X106 0
68389X105 1

Caché ObjectScript[edit]

Class Utils.Check [ Abstract ]
{
 
ClassMethod CUSIP(x As %String) As %Boolean
{
SET x=$TRANSLATE(x," ")
// https://leiq.bus.umich.edu/res_codes_cusip.htm
IF x'?8UNP1N QUIT 0
SET cd=$EXTRACT(x,*), x=$EXTRACT(x,1,*-1), t=0
FOR i=1:1:$LENGTH(x) {
SET n=$EXTRACT(x,i)
IF n'=+n SET n=$CASE(n,"*":36,"@":37,"#":38,:$ASCII(n)-55)
IF i#2=0 SET n=n*2
SET t=t+(n\10)+(n#10)
}
QUIT cd=((10-(t#10))#10)
}
 
}
Examples:
USER>For  { Read s Quit:s=""  Write ": "_##class(Utils.Check).CUSIP(s), ! }         
037833100: 1
17275R102: 1
38259P508: 1
594918104: 1
68389X106: 0
68389X105: 1

USER>

Common Lisp[edit]

(defun char->value (c)
(cond ((digit-char-p c 36))
((char= c #\*) 36)
((char= c #\@) 37)
((char= c #\#) 38)
(t (error "Invalid character: ~A" c))))
 
(defun cusip-p (cusip)
(and (= 9 (length cusip))
(loop for i from 1 to 8
for c across cusip
for v = (char->value c)
when (evenp i)
do (setf v (* 2 v))
sum (multiple-value-bind (quot rem) (floor v 10)
(+ quot rem))
into sum
finally (return (eql (digit-char-p (char cusip 8))
(mod (- 10 (mod sum 10)) 10))))))
 
(defun main ()
(dolist (cusip '("037833100" "17275R102" "38259P508" "594918104" "68389X106" "68389X105"))
(format t "~A: ~A~%" cusip (cusip-p cusip))))
Output:
037833100: T
17275R102: T
38259P508: T
594918104: T
68389X106: NIL
68389X105: T

Fortran[edit]

The key notion here is to employ a single sequence of valid characters, VALID, and for each character C of the code under test, use function INDEX(VALID,C) to find its position within that sequence, which turns out to be the desired v of the example pseudocode. The only slight difficulty is that INDEX starts its counting with one for the first character of VALID, which is zero, so one must be subtracted; similarly, to return a digit character code via indexing into VALID, one must be added. By using a list of valid characters rather than peculiar character arithmetic (such as c <= "9" & c >= "0" or similar) there is no reliance on the ASCII way of things. Recall that EBCDIC encodements have different orderings and notably, non-alphabetic characters between A and Z.

The source does not bother with the MODULE protocol of F90 and later, and so the type of function CUSIPCHECK must be declared in all routines wishing to invoke it. However, the F90 feature of having the END statement of a subroutine or function give its name is to valuable to ignore. The function returns a character code rather than an integer, since the presumption is that it is to be compared to the check character of the code being inspected, which is known as a character not an integer. This means some blather when extracting the eight characters to be presented to CUSIPCHECK and comparing the result to the ninth character, but the test can be done in one expression.

There is no checking that only valid characters are presented, nor that eight-character codes only are offered, though the compiler might complain if the function were to be invoked with a text literal of the wrong size. In the absence of such checks, there need be no added complications to support a scheme for reporting such errors.
      CHARACTER*1 FUNCTION CUSIPCHECK(TEXT)	!Determines the check sum character.
Committee on Uniform Security Identification Purposes, of the American (i.e. USA) Bankers' Association.
CHARACTER*8 TEXT !Specifically, an eight-symbol code.
CHARACTER*(*) VALID !These only are valid.
PARAMETER (VALID = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#")
INTEGER I,V,S !Assistants.
S = 0 !Start the checksum.
DO I = 1,LEN(TEXT) !Step through the text.
V = INDEX(VALID,TEXT(I:I)) - 1 !Since counting starts with one.
IF (MOD(I,2).EQ.0) V = V*2 !V = V*(2 - MOD(I,2))?
S = S + V/10 + MOD(V,10) !Specified calculation.
END DO !On to the next character.
I = MOD(10 - MOD(S,10),10) + 1 !Again, counting starts with one.
CUSIPCHECK = VALID(I:I) !Thanks to the MOD 10, surely a digit.
END FUNCTION CUSIPCHECK !No checking for invalid input...
 
PROGRAM POKE !Just to try it out.
INTEGER I,N !Assistants.
PARAMETER (N = 6) !A whole lot of blather
CHARACTER*9 CUSIP(N) !Just to have an array of test codes.
DATA CUSIP/ !Here they are, as specified.
1 "037833100",
2 "17275R102",
3 "38259P508",
4 "594918104",
5 "68389X106",
6 "68389X105"/
CHARACTER*1 CUSIPCHECK !Needed as no use of the MODULE protocol.
 
DO I = 1,N !"More than two? Use a DO..."
WRITE (6,*) CUSIP(I),CUSIPCHECK(CUSIP(I)(1:8)).EQ.CUSIP(I)(9:9)
END DO
 
END

Output: standard output is to I/O unit 6, and free-format (the *) will suffice for this. Each line output starts with a space (in case it is to go to a lineprinter, with carriage control), which is convenient for layout here.

037833100 T
17275R102 T
38259P508 T
594918104 T
68389X106 F
68389X105 T

This would have worked first time, except that a fymgre frmble caused the omission of the digit 2 from the text of VALID. The benefits of checking checksums reach to unexpected places!

FreeBASIC[edit]

' version 04-04-2017
' compile with: fbc -s console
 
sub cusip(input_str As String)
 
Print input_str;
If Len(input_str) <> 9 Then
Print " length is incorrect, invalid cusip"
Return
End If
 
Dim As Long i, v , sum
Dim As UByte x
 
For i = 1 To 8
x = input_str[i-1]
Select Case x
Case Asc("0") To Asc("9")
v = x - Asc("0")
Case Asc("A") To Asc("Z")
v = x - Asc("A") + 1 + 9
Case Asc("*")
v= 36
Case Asc("@")
v = 37
Case Asc("#")
v = 38
Case Else
Print " found a invalid character, invalid cusip"
return
End Select
 
If (i And 1) = 0 Then v = v * 2
sum = sum + v \ 10 + v Mod 10
Next
 
sum = (10 - (sum Mod 10)) Mod 10
If sum = (input_str[8] - Asc("0")) Then
Print " is valid"
Else
Print " is invalid"
End If
 
End Sub
 
' ------=< MAIN >=------
 
Data "037833100", "17275R102", "38259P508"
Data "594918104", "68389X106", "68389X105"
 
Dim As String input_str
 
Print
For i As Integer = 1 To 6
Read input_str
cusip(input_str)
Next
 
' empty keyboard buffer
While InKey <> "" : Wend
Print : Print "hit any key to end program"
Sleep
End
Output:
037833100 is valid
17275R102 is valid
38259P508 is valid
594918104 is valid
68389X106 is invalid
68389X105 is valid

Haskell[edit]

import Data.List(elemIndex)
 
data Result = Valid | BadCheck | TooLong | TooShort | InvalidContent deriving Show
 
prependMaybe :: Maybe a -> Maybe [a] -> Maybe [a]
prependMaybe (Just v) (Just vs) = Just (v:vs)
prependMaybe _ _ = Nothing
 
-- convert a list of Maybe to a Maybe list.
-- result is Nothing if any of values from the original list are Nothing
allMaybe :: [Maybe a] -> Maybe [a]
allMaybe = foldr prependMaybe (Just [])
 
toValue :: Char -> Maybe Int
toValue c = elemIndex c $ ['0'..'9'] ++ ['A'..'Z'] ++ "*&#"
 
-- check a list of ints to see if they represent a valid CUSIP
valid :: [Int] -> Bool
valid ns0 =
let -- multiply values with even index by 2
ns1 = zipWith (\i n -> (if odd i then n else 2*n)) [1..] $ take 8 ns0
 
-- apply div/mod formula from site and sum up results
sm = sum $ fmap (\s -> ( s `div` 10 ) + s `mod` 10) ns1
 
in -- apply mod/mod formula from site and compare to last value in list
ns0!!8 == (10 - (sm `mod` 10)) `mod` 10
 
-- check a String to see if it represents a valid CUSIP
checkCUSIP :: String -> Result
checkCUSIP cs
| l < 9 = TooShort
| l > 9 = TooLong
| otherwise = case allMaybe (fmap toValue cs) of
Nothing -> InvalidContent
Just ns -> if valid ns then Valid else BadCheck
where l = length cs
 
testData =
[ "037833100"
, "17275R102"
, "38259P508"
, "594918104"
, "68389X106"
, "68389X105"
]
 
main = mapM_ putStrLn (fmap (\s -> s ++ ": " ++ show (checkCUSIP s)) testData)
Output:
037833100: Valid
17275R102: Valid
38259P508: Valid
594918104: Valid
68389X106: BadCheck
68389X105: Valid

Or, making some alternative selections from Haskell's rich libraries:

import qualified Data.Map as M (Map, fromList, lookup)
import Control.Monad (sequence)
import Data.Maybe (fromMaybe)
 
cusipMap :: M.Map Char Int
cusipMap = M.fromList $ zip (['0' .. '9'] ++ ['A' .. 'Z'] ++ "*&#") [0 ..]
 
cusipValid :: String -> Bool
cusipValid s =
let ns = (fromMaybe [] . sequence . fmap (`M.lookup` cusipMap)) s
in (9 == length ns) &&
let qrSum =
sum
([quot, rem] <*> zipWith id (cycle [id, (* 2)]) (take 8 ns) <*>
[10])
in last ns == rem (10 - rem qrSum 10) 10
 
main :: IO ()
main =
mapM_
(print . ((,) <*> cusipValid))
[ "037833100"
, "17275R102"
, "38259P508"
, "594918104"
, "68389X106"
, "68389X105"
]
Output:
("037833100",True)
("17275R102",True)
("38259P508",True)
("594918104",True)
("68389X106",False)
("68389X105",True)

J[edit]

One-liner:

   ccd =. 10 | 10 - 10 | [: +/ [: , 10 (#.^:_1) (8 $ 1 2) * '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' i. ]
ccd '68389X10'
5

More verbose version that checks for correct input:

   CUSIPcheckdigit =. 3 : 0
assert. 8 = $ y NB. Only accept an 8-element long list
assert. */ y e. '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' NB. Only accept characters from the list of 38
values =. (8 $ 1 2) * '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#' i. ] NB. Verb to translate characters and then double every second value.
sumdigits =. +/@,@(10 10&#:) NB. Verb to sum the base-10 digits in a numerical array
invertedmod =. 10 | 10 - 10 | ] NB. Verb to find the mod-10 of 10 minus mod-10
": invertedmod sumdigits values y NB. Return the check digit as a character
)
addCUSIPcheckdigit =: , CUSIPcheckdigit
verifyCUSIPcheckdigit =: {: = [email protected]}:

Examples:

   addCUSIPcheckdigit '68389X10'
68389X105
verifyCUSIPcheckdigit '68389X106'
0
verifyCUSIPcheckdigit '68389X105'
1
samples =: '037833100', '17275R102', '38259P508', '594918104', '68389X106',: '68389X105'
samples ; verifyCUSIPcheckdigit"1 samples
┌─────────┬─┐
0378331001
17275R1021
38259P5081
5949181041
68389X1060
68389X1051
└─────────┴─┘

Julia[edit]

# v0.6
 
function cusip_lastdigit(input::String)::Char
digits = Char.(48:57)
letters = Char.(65:90)
 
input = uppercase(input)
sum = 0
 
for (i, c) in enumerate(input)
if c in digits
v = Int(c) - 48
elseif c in letters
v = Int(c) - 64 + 9
elseif c == '*'
v = 36
elseif c == '@'
v = 37
elseif c == '#'
v = 38
end
 
if iseven(i); v *= 2 end
 
sum += div(v, 10) + rem(v, 10)
end
 
return Char(rem(10 - rem(sum, 10), 10) + 48)
end
 
cusip_checkdigit(input::String)::Bool = input[9] == cusip_lastdigit(input[1:8])
 
for code in ("037833100", "17275R102", "38259P508", "594918104", "68389X106", "68389X105")
println("$code is ", cusip_checkdigit(code) ? "correct" : "not correct")
end
Output:
037833100 is correct
17275R102 is correct
38259P508 is correct
594918104 is correct
68389X106 is not correct
68389X105 is correct

Kotlin[edit]

// version 1.1.0
 
fun isCusip(s: String): Boolean {
if (s.length != 9) return false
var sum = 0
for (i in 0..7) {
val c = s[i]
var v = when (c) {
in '0'..'9' -> c.toInt() - 48
in 'A'..'Z' -> c.toInt() - 64 // lower case letters apparently invalid
'*' -> 36
'@' -> 37
'#' -> 38
else -> return false
}
if (i % 2 == 1) v *= 2 // check if odd as using 0-based indexing
sum += v / 10 + v % 10
}
return s[8].toInt() - 48 == (10 - (sum % 10)) % 10
}
 
fun main(args: Array<String>) {
val candidates = listOf(
"037833100",
"17275R102",
"38259P508",
"594918104",
"68389X106",
"68389X105"
)
for (candidate in candidates)
println("$candidate -> ${if(isCusip(candidate)) "correct" else "incorrect"}")
}
Output:
037833100 -> correct
17275R102 -> correct
38259P508 -> correct
594918104 -> correct
68389X106 -> incorrect
68389X105 -> correct

Lua[edit]

The checkDigit function is a line-for-line translation of the pseudo-code algorithm.

function checkDigit (cusip)
if #cusip ~= 8 then return false end
 
local sum, c, v, p = 0
for i = 1, 8 do
c = cusip:sub(i, i)
if c:match("%d") then
v = tonumber(c)
elseif c:match("%a") then
p = string.byte(c) - 64
v = p + 9
elseif c == "*" then
v = 36
elseif c == "@" then
v = 37
elseif c == "#" then
v = 38
end
if i % 2 == 0 then
v = v * 2
end
 
sum = sum + math.floor(v / 10) + v % 10
end
 
return tostring((10 - (sum % 10)) % 10)
end
 
local testCases = {
"037833100",
"17275R102",
"38259P508",
"594918104",
"68389X106",
"68389X105"
}
for _, CUSIP in pairs(testCases) do
io.write(CUSIP .. ": ")
if checkDigit(CUSIP:sub(1, 8)) == CUSIP:sub(9, 9) then
print("VALID")
else
print("INVALID")
end
end
Output:
037833100: VALID
17275R102: VALID
38259P508: VALID
594918104: VALID
68389X106: INVALID
68389X105: VALID

Perl[edit]

#!/usr/bin/env perl -T
use 5.018_002;
use warnings;
use Readonly;
 
our $VERSION = 1.000_000;
 
Readonly my $ALGORITHM_LETTER_START_VALUE => 9;
Readonly my $ALGORITHM_TEN => 10;
Readonly my $CUSIP_CHECK_DIGIT_INDEX => 8;
Readonly my $CUSIP_DATA_LAST_INDEX => 7;
 
my %values = (
'037833100' => 'Apple Incorporated',
'17275R102' => 'Cisco Systems',
'38259P508' => 'Google Incorporated',
'594918104' => 'Microsoft Corporation',
'68389X106' => 'Oracle Corporation',
'68389X105' => 'Oracle Corporation',
);
 
# Character-value look-up table
my %cv = (
q[*] => 36,
q[@] => 37,
q[#] => 38,
);
my $inc = 0;
for ( '0' .. '9' ) {
$cv{$_} = $inc++;
}
$inc = $ALGORITHM_LETTER_START_VALUE;
for ( 'A' .. 'Z' ) {
$cv{$_} = ++$inc;
}
 
for ( keys %values ) {
say "$_ $values{$_}" . cusip_check_digit($_);
}
 
sub cusip_check_digit {
my @cusip = split m{}xms, shift; # 9-digit CUSIP string to char array
my $sum = 0;
 
for my $i ( 0 .. $CUSIP_DATA_LAST_INDEX ) {
my $v = 0;
 
if ( $cusip[$i] =~ m{\A [[:digit:][:upper:]*@#] \z}xms ) {
$v = $cv{ $cusip[$i] };
}
else {
return 'Invalid character found'; # Return value on failure
}
 
if ( $i % 2 ) {
$v *= 2;
}
 
$sum += int( $v / $ALGORITHM_TEN ) + $v % $ALGORITHM_TEN;
}
 
my $check_digit =
( $ALGORITHM_TEN - ( $sum % $ALGORITHM_TEN ) ) % $ALGORITHM_TEN;
 
if ( $check_digit == $cusip[$CUSIP_CHECK_DIGIT_INDEX] ) {
return q[]; # Return empty string on success
}
else {
return ' (incorrect)'; # Return value on failure
}
}
Output:
68389X105 Oracle Corporation
68389X106 Oracle Corporation (incorrect)
17275R102 Cisco Systems
38259P508 Google Incorporated
594918104 Microsoft Corporation
037833100 Apple Incorporated

Perl 6[edit]

Works with: Rakudo version 2017.01
sub divmod ($v, $r) { $v div $r, $v mod $r }
my %chr = (flat 0..9, 'A'..'Z', <* @ #>) Z=> 0..*;
 
sub cuisp-check ($cuisp where *.chars == 9) {
my ($code, $chk) = $cuisp.comb(8);
my $sum = [+] $code.comb.kv.map: { [+] (($^k % 2 + 1) * %chr{$^v}).&divmod(10) };
so (10 - $sum mod 10) mod 10 eq $chk;
}
 
# TESTING
say "$_: ", $_.&cuisp-check for <
037833100
17275R102
38259P508
594918104
68389X106
68389X105
>
Output:
037833100: True
17275R102: True
38259P508: True
594918104: True
68389X106: False
68389X105: True

Phix[edit]

sequence cch = {}
 
function CusipCheckDigit(string cusip)
integer s = 0, c, v
if length(cch)=0 then
cch = repeat(-1,256)
for i='0' to '9' do
cch[i] = i-'0'
end for
for i='A' to 'Z' do
cch[i] = i-55
end for
cch['*'] = 36
cch['@'] = 37
cch['#'] = 38
end if
if length(cusip)!=9 or find('\0',cusip) then return 0 end if
for i=1 to 8 do
c := cusip[i]
v := cch[c]
if v=-1 then return 0 end if
if remainder(i,2)=0 then
v *= 2
end if
s += floor(v/10)+mod(v,10)
end for
return cusip[9]=mod(10-mod(s,10),10)+'0'
end function
 
sequence tests = {"037833100", -- Apple Incorporated
"17275R102", -- Cisco Systems
"38259P508", -- Google Incorporated
"594918104", -- Microsoft Corporation
"68389X106", -- Oracle Corporation (incorrect)
"68389X105"} -- Oracle Corporation
 
for i=1 to length(tests) do
string ti = tests[i]
printf(1,"%s : %s\n",{ti,{"invalid","valid"}[CusipCheckDigit(ti)+1]})
end for
Output:
037833100 : valid
17275R102 : valid
38259P508 : valid
594918104 : valid
68389X106 : invalid
68389X105 : valid

Python[edit]

Requires Python 3.6 for the string template literal in the print statement.

#!/usr/bin/env python3
 
import math
 
def cusip_check(cusip):
if len(cusip) != 9:
raise ValueError('CUSIP must be 9 characters')
 
cusip = cusip.upper()
total = 0
for i in range(8):
c = cusip[i]
if c.isdigit():
v = int(c)
elif c.isalpha():
p = ord(c) - ord('A') + 1
v = p + 9
elif c == '*':
v = 36
elif c == '@':
v = 37
elif c == '#':
v = 38
 
if i % 2 != 0:
v *= 2
 
total += int(v / 10) + v % 10
check = (10 - (total % 10)) % 10
return str(check) == cusip[-1]
 
if __name__ == '__main__':
codes = [
'037833100',
'17275R102',
'38259P508',
'594918104',
'68389X106',
'68389X105'
]
for code in codes:
print(f'{code} -> {cusip_check(code)}')
 

Output:

037833100 -> True
17275R102 -> True
38259P508 -> True
594918104 -> True
68389X106 -> False
68389X105 -> True

Racket[edit]

#lang racket
(require srfi/14)
 
(define 0-char (char->integer #\0))
(define A-char (char->integer #\A))
 
(define (cusip-value c)
(cond
[(char-set-contains? char-set:digit c)
(- (char->integer c) 0-char)]
[(char-set-contains? char-set:upper-case c)
(+ 10 (- (char->integer c) A-char))]
[(char=? c #\*) 36]
[(char=? c #\@) 37]
[(char=? c #\#) 38]))
 
(define (cusip-check-digit cusip)
(modulo
(- 10
(modulo
(for/sum
((i (sequence-map add1 (in-range 8))) (c (in-string cusip)))
(let* ((v (cusip-value c)) (v′ (if (even? i) (* v 2) v)))
(+ (quotient v′ 10) (modulo v′ 10)))) 10)) 10))
 
(define (CUSIP? s)
(char=? (string-ref s (sub1 (string-length s)))
(integer->char (+ 0-char (cusip-check-digit s)))))
 
(module+ test
(require rackunit)
(check-true (CUSIP? "037833100"))
(check-true (CUSIP? "17275R102"))
(check-true (CUSIP? "38259P508"))
(check-true (CUSIP? "594918104"))
(check-false (CUSIP? "68389X106"))
(check-true (CUSIP? "68389X105")))

no output indicates all tests passed.

REXX[edit]

idiomatic[edit]

/*REXX program validates that the  last digit (the check digit)  of a  CUSIP  is valid. */
@.=
parse arg @.1 .
if @.1=='' | @.1=="," then do; @.1= 037833100 /* Apple Incorporated */
@.2= 17275R102 /* Cisco Systems */
@.3= 38259P508 /* Google Incorporated */
@.4= 594918104 /* Microsoft Corporation */
@.5= 68389X106 /* Oracle Corporation (incorrect)*/
@.6= 68389X105 /* Oracle Corporation */
end
 
do j=1 while @.j\=''; chkDig=CUSIPchk(@.j) /*calculate check digit from func*/
OK=word("isn't is", 1 + (chkDig==right(@.j,1) ) ) /*validate check digit with func*/
say 'CUSIP ' @.j right(OK, 6) "valid." /*display the CUSIP and validity.*/
end /*j*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
CUSIPchk: procedure; arg x 9; $=0; abc= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
do k=1 for 8
y=substr(x, k, 1)
select
when datatype(y,'W') then #=y
when datatype(y,'U') then #=pos(y, abc) + 9
when y=='*' then #=36
when y=='@' then #=37
when y=='#' then #=38
otherwise return 0 /*invalid character.*/
end /*select*/
if k//2==0 then #=#+# /*K even? Double it*/
$=$ + #%10 + #//10
end /*k*/
return (10- $//10) // 10

output   when using the default input:

CUSPID  037833100     is valid.
CUSPID  17275R102     is valid.
CUSPID  38259P508     is valid.
CUSPID  594918104     is valid.
CUSPID  68389X106  isn't valid.
CUSPID  68389X105     is valid.

conciser function[edit]

/*REXX program validates that the  last digit (the check digit)  of a  CUSIP  is valid. */
@.=
parse arg @.1 .
if @.1=='' | @.1=="," then do; @.1= 037833100 /* Apple Incorporated */
@.2= 17275R102 /* Cisco Systems */
@.3= 38259P508 /* Google Incorporated */
@.4= 594918104 /* Microsoft Corporation */
@.5= 68389X106 /* Oracle Corporation (incorrect)*/
@.6= 68389X105 /* Oracle Corporation */
end
 
do j=1 while @.j\=''; chkDig=CUSIPchk(@.j) /*calculate check digit from func*/
OK=word("isn't is", 1 + (chkDig==right(@.j,1) ) ) /*validate check digit with func*/
say 'CUSIP ' @.j right(OK, 6) "valid." /*display the CUSIP and validity.*/
end /*j*/
exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
CUSIPchk: procedure; arg x 9; $=0; abc= '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#'
/* [↓] if Y isn' found, then POS returns zero.*/
do k=1 for 8; y=substr(x,k,1) /*get a character. */
#=pos(y, abc) - 1 /*get its position.*/
if # == -1 then return 0 /*invalid character*/
if k//2== 0 then #=#+# /*K even? double it*/
$=$ + #%10 + #//10
end /*k*/
return (10-$//10) // 10

output   is the same as the idiomatic REXX version.

Rust[edit]

fn cusip_check(cusip: &str) -> bool {
if cusip.len() != 9 {
return false;
}
 
let mut v: u8 = 0;
let mut total = 0u8;
let capital_cusip = cusip.to_uppercase();
let foo = capital_cusip.as_str().char_indices().take(7);
for (i, c) in foo {
if c.is_digit(10) {
v = c.to_digit(10).unwrap() as u8;
} else if c.is_alphabetic() {
let p = (c as u8) - ('A' as u8) + 1;
v = p + 9;
} else if c == '*' {
v = 36;
} else if c == '@' {
v = 37;
} else if c == '#' {
v = 38;
}
 
if i % 2 != 0 {
v *= 2
}
total += (v / 10) + v % 10;
}
let check: u8 = (10 - (total % 10)) % 10;
(check.to_string().chars().nth(0).unwrap()) == cusip.chars().nth(cusip.len() - 1).unwrap()
}
 
fn main() {
let codes = [
"037833100",
"17275R102",
"38259P508",
"594918104",
"68389X106",
"68389X105",
];
for code in codes.iter() {
println!("{} -> {}", code, cusip_check(code))
}
}
 

Output:

037833100 -> True
17275R102 -> True
38259P508 -> True
594918104 -> True
68389X106 -> False
68389X105 -> True

Tcl[edit]

Direct translation of pseudocode[edit]

proc ordinal-of-alpha {c} {                     ;#  returns ordinal position of c in the alphabet (A=1, B=2...)
lsearch {_ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z} [string toupper $c]
}
 
proc Cusip-Check-Digit {cusip} { ;# algorithm Cusip-Check-Digit(cusip) is
if {[string length $cusip] != 8} { ;# Input: an 8-character CUSIP
return false
}
 
set sum 0 ;# sum := 0
for {set i 1} {$i <= 8} {incr i} { ;# for 1 ≤ i ≤ 8 do
set c [string index $cusip $i-1] ;# c := the ith character of cusip
if {[string is digit $c]} { ;# if c is a digit then
set v $c ;# v := numeric value of the digit c
} elseif {[string is alpha $c]} { ;# else if c is a letter then
set p [ordinal-of-alpha $c] ;# p := ordinal position of c in the alphabet (A=1, B=2...)
set v [expr {$p + 9}] ;# v := p + 9
} elseif {$c eq "*"} { ;# else if c = "*" then
set v 36 ;# v := 36
} elseif {$c eq "@"} { ;# else if c = "@" then
set v 37 ;# v := 37
} elseif {$c eq "#"} { ;# else if c = "#" then
set v 38 ;# v := 38
} ;# end if
if {$i % 2 == 0} { ;# if i is even then
set v [expr {$v * 2}] ;# v := v × 2
} ;# end if
 
incr sum [expr {$v / 10 + $v % 10}] ;# sum := sum + int ( v div 10 ) + v mod 10
} ;# repeat
 
expr {(10 - ($sum % 10)) % 10} ;# return (10 - (sum mod 10)) mod 10
}
proc check-cusip {cusip} {
set last [string index $cusip end]
set cusip [string range $cusip 0 end-1]
expr {$last eq [Cusip-Check-Digit $cusip]}
}

More idiomatic Tcl[edit]

proc check-cusip {code} {
if {[string length $code] != 9} {
return false
}
set alphabet [email protected]#
set code [split [string tolower $code] ""]
foreach char $code idx {1 2 3 4 5 6 7 8 9} {
set v [string first $char $alphabet]
if {$v == -1} {return false}
if {$idx % 2 == 0} {
incr v $v
}
set v [::tcl::mathop::+ {*}[split $v ""]]
incr sum $v
}
expr {$sum % 10 == 0}
}

Common test harness[edit]

proc test {} {
foreach {cusip name} {
037833100 "Apple Incorporated"
17275R102 "Cisco Systems"
38259P508 "Google Incorporated"
594918104 "Microsoft Corporation"
68389X106 "Oracle Corporation (incorrect)"
68389X105 "Oracle Corporation"
} {
puts [format %-40s%s $name [expr {[check-cusip $cusip] ? "valid" : "invalid"}]]
puts [format %-40s%s $name [expr {[cusip-check $cusip] ? "valid" : "invalid"}]]
}
}
test

Output[edit]

Output:
Apple Incorporated                      valid
Cisco Systems                           valid
Google Incorporated                     valid
Microsoft Corporation                   valid
Oracle Corporation   (incorrect)        invalid
Oracle Corporation                      valid

zkl[edit]

fcn cusipCheckDigit(cusip){
var [const] vs=[0..9].chain(["A".."Z"],T("*","@","#")).pump(String);
try{
sum:=Walker.cycle(1,2).zipWith(fcn(n,c){ v:=vs.index(c)*n; v/10 + v%10 },
cusip[0,8]).reduce('+);
((10 - sum%10)%10 == cusip[8].toInt()) and cusip.len()==9
}catch{ False }
}
foreach cusip in (T("037833100", "17275R102",
"38259P508", "594918104", "68389X106", "68389X105")){
println(cusip,": ",cusipCheckDigit(cusip));
}
Output:
037833100: True
17275R102: True
38259P508: True
594918104: True
68389X106: False
68389X105: True