If I am not wrong, ISIN numbers last position is a verification digit. What is the mathematical function that determines its value in function of the first 11 digits?
http://en.wikipedia.org/wiki/International_Securities_Identification_Number
The procedure for calculating ISIN check digits is similar to the "Modulus 10 Double Add Double" technique used in CUSIPs. To calculate the check digit, first convert any letters to numbers by adding their ordinal position in the alphabet to 9, such that A = 10 and M = 22. Starting with the right most digit, every other digit is multiplied by two. (For CUSIP check digits, these two steps are reversed.) The resulting string of digits (numbers greater than 9 becoming two separate digits) are added up. Subtract this sum from the smallest number ending with zero that is greater than or equal to it: this gives the check digit, which is also known as the ten's complement of the sum modulo 10. That is, the resulting sum, including the check-digit, is a multiple of 10.
They have a good example too.
every other digit
to mean alternative digits
. It's not clear what you think the mistake(s) in the Wikipedia entry are. –
Terza Building on the examples of others, here is a C# implementation which will validate both ISINs and CUSIPs (and maybe some other Luhn variations).
Usage:
foreach (var isin in ValidIsins)
{
var calculatedChecksum = SecuritiesValidation.CalculateChecksum(isin.Substring(0, 11));
var actualChecksum = (isin.Last() - '0');
Assert.AreEqual(calculatedChecksum, actualChecksum);
}
foreach (var cusip in ValidCusips)
{
var calculatedChecksum = SecuritiesValidation.CalculateChecksum(cusip.Substring(0, 8), true, true);
var actualChecksum = (cusip.Last() - '0');
Assert.AreEqual(calculatedChecksum, actualChecksum);
}
Implementation:
public static class SecuritiesValidation
{
public static int CalculateChecksum(IEnumerable<char> codeWithoutChecksum, bool reverseLuhn = false, bool allowSymbols = false)
{
return reverseLuhn
? codeWithoutChecksum
.Select((c, i) => c.OrdinalPosition(allowSymbols).ConditionalMultiplyByTwo(i.IsOdd()).SumDigits())
.Sum()
.TensComplement()
: codeWithoutChecksum
.ToArray()
.ToDigits(allowSymbols)
.Select((d, i) => d.ConditionalMultiplyByTwo(i.IsEven()).SumDigits())
.Sum()
.TensComplement();
}
public static bool IsChecksumCorrect(string code, bool reverseLuhn = false, bool allowSymbols = false)
{
try
{
var checksum = code.Last().ToInt();
return checksum == CalculateChecksum(code.Take(code.Length - 1), reverseLuhn, allowSymbols);
}
catch
{
return false;
}
}
/* Be careful here. This method is probably inapropriate for anything other than its designed purpose of Luhn-algorithm based validation.
* Specifically:
* - numbers are assigned a value equal to the number ('0' == 0, '1' == 1).
* - letters are assigned a value indicating the number 9 plus the letters ordinal position in the English alphabet ('A' == 10, 'B' == 11).
* - if symbols are allowed (eg: for CUSIP validation), they are assigned values beginning from 36 ('*' == 36, '@' == 37).
*/
private static int OrdinalPosition(this char c, bool allowSymbols = false)
{
if (char.IsLower(c))
return char.ToUpper(c) - 'A' + 10;
if (char.IsUpper(c))
return c - 'A' + 10;
if (char.IsDigit(c))
return c.ToInt();
if (allowSymbols)
switch (c)
{
case '*':
return 36;
case '@':
return 37;
case '#':
return 38;
}
throw new ArgumentOutOfRangeException("Specified character is not a letter, digit or allowed symbol.");
}
private static bool IsEven(this int x)
{
return (x % 2 == 0);
}
private static bool IsOdd(this int x)
{
return !IsEven(x);
}
private static int ToInt(this char digit)
{
if (char.IsDigit(digit))
return digit - '0';
throw new ArgumentOutOfRangeException("Specified character is not a digit.");
}
private static IEnumerable<int> ToDigits(this char[] s, bool allowSymbols = false)
{
var digits = new List<int>();
for (var i = s.Length - 1; i >= 0; i--)
{
var ordinalPosition = s[i].OrdinalPosition(allowSymbols);
digits.Add(ordinalPosition % 10);
if (ordinalPosition > 9)
digits.Add(ordinalPosition / 10);
}
return digits;
}
private static int SumDigits(this int value)
{
//return value > 9 ? ((value / 10) + (value % 10)) : value;
return ((value / 10) + (value % 10));
}
private static int ConditionalMultiplyByTwo(this int value, bool condition)
{
return condition ? value * 2 : value;
}
private static int TensComplement(this int value)
{
return (10 - (value % 10)) % 10;
}
}
It will likely make sense to use checksum validation in conjunction with a regular expression pattern match. These are the regex I use:
ISIN: ^(XS|AD|AE|AF|AG|AI|AL|AM|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BL|BM|BN|BO|BQ|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|EH|ER|ES|ET|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MF|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|SS|ST|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TR|TT|TV|TW|TZ|UA|UG|UM|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)([0-9A-Z]{9})([0-9]{1})$
CUSIP: ^[A-Z0-9]{8}[0-9]$
http://en.wikipedia.org/wiki/International_Securities_Identification_Number
The procedure for calculating ISIN check digits is similar to the "Modulus 10 Double Add Double" technique used in CUSIPs. To calculate the check digit, first convert any letters to numbers by adding their ordinal position in the alphabet to 9, such that A = 10 and M = 22. Starting with the right most digit, every other digit is multiplied by two. (For CUSIP check digits, these two steps are reversed.) The resulting string of digits (numbers greater than 9 becoming two separate digits) are added up. Subtract this sum from the smallest number ending with zero that is greater than or equal to it: this gives the check digit, which is also known as the ten's complement of the sum modulo 10. That is, the resulting sum, including the check-digit, is a multiple of 10.
They have a good example too.
every other digit
to mean alternative digits
. It's not clear what you think the mistake(s) in the Wikipedia entry are. –
Terza Based on the examples published in Wikipedia, the method is:
- Replace each letter by its ordinal (A=1, B=2 and so on) plus 9 ->
- For each digit at an even position starting from the rightmost position (), replace it by the digits of its double (two digits in two vector entries) -> ;
- Verification code:
A possible implementation in JavaScript is:
function getVerificationCode(isin)
{
if(isin.length != 12) return null;
var v = [];
for(var i = isin.length-2; i >= 0; i--)
{
var c = isin.charAt(i);
if(isNaN(c)) //Not a digit
{
var letterCode = isin.charCodeAt(i)-55; //Char ordinal + 9
v.push(letterCode % 10);
if(letterCode > 9)
v.push(Math.floor(letterCode/10));
}
else
v.push(Number(c));
}
var sum = 0;
var l = v.length;
for(var i = 0; i < l; i++)
if(i % 2 == 0)
{
var d = v[i]*2;
sum += Math.floor(d/10);
sum += d % 10;
}
else
sum += v[i];
return 10 - (sum % 10);
}
EDIT: To include @queso updates:
function getVerificationCode(isin) {
if (isin.length != 12) return false;
var v = [];
for (var i = isin.length - 2; i >= 0; i--) {
var c = isin.charAt(i);
if (isNaN(c)) { //not a digit
var letterCode = isin.charCodeAt(i) - 55; //Char ordinal + 9
v.push(letterCode % 10);
if (letterCode > 9) {
v.push(Math.floor(letterCode / 10));
}
} else {
v.push(Number(c));
}
}
var sum = 0;
var l = v.length;
for (var i = 0; i < l; i++) {
if (i % 2 == 0) {
var d = v[i] * 2;
sum += Math.floor(d / 10);
sum += d % 10;
} else {
sum += v[i];
}
}
return (10 - (sum % 10)) % 10
}
false
instead of null
on the second line, and 2) return (10 - (sum % 10)) % 10
instead of 10 - (sum % 10)
in the second-to-last last line, adding an extra mod 10 as the final step. diffchecker.com/jEeML1lU –
Thimbu I'd like to share my implementation in R. It does not require any specific package.
The mgsub is a support function that allows the substitution of all the characters in the ISIN code in one single command. It is copied from Replace multiple letters with accents with gsub
the iso3166alpha2$Code
contains the list of countries that Grenade has listed
The algorithm is implemented in the isIsin(x)
function, which returns TRUE
in case of valid ISIN code
mgsub <- function(pattern, replacement, x, ...) {
if (length(pattern)!=length(replacement)) {
stop("pattern and replacement do not have the same length.")
}
result <- x
for (i in 1:length(pattern)) {
result <- gsub(pattern[i], replacement[i], result, ...)
}
result
}
isIsin <- function (identifier) {
correctPrefix <- substr(identifier, 1, 2) %in% c(iso3166alpha2$Code, "XS")
correctLength <- nchar(identifier) == 12
correctCharset <- !grepl('[[:punct:]]', identifier)
if(!correctPrefix | !correctLength | !correctCharset) {
return(FALSE)
}
# replace all character with its equivalent number
identifierOnlyNumbers <- mgsub(LETTERS, seq(10, 35), substr(identifier, 1, 11))
# split the identifier in single digits and reverse its order
characterVector <- rev(unlist(strsplit(identifierOnlyNumbers, "")))
# Double every second digit of the group of digits with the rightmost character
characterVector[seq(1, nchar(identifierOnlyNumbers), 2)] <-
as.character(as.numeric(characterVector[seq(1, nchar(identifierOnlyNumbers), 2)]) * 2)
# Subtract 9 if > 9 (can apply to all since no digit can be greater than 9 before doubling)
# Add up the digits
summation <- sum(ifelse(as.numeric(characterVector) > 9, as.numeric(characterVector) - 9, as.numeric(characterVector)))
# Take the 10s modulus of the sum, subtract it from 10 and take the 10s modulus of the result
# this final step is important in the instance where the modulus of the sum is 0, as the resulting check digit would be 10
correctCheckDigit <- (10 - (summation %% 10)) %% 10 == as.numeric(substr(identifier, 12, 12))
correctCheckDigit
}
DescTools
, data("d.countries")
and I needed to replace iso3166alpha2$Code
with d.countries$a2
. Then it works. search.r-project.org/CRAN/refmans/DescTools/html/… –
Anagnos <?php
function cusipToIsin($CUSIP, $Country)
{
if (strlen($CUSIP) == 9) {
$string = charToCusipBinary($Country) . charToCusipBinary($CUSIP); //Convert any letters to numbers
$arrayString = str_split($string);
//check wether string length is even or odd
if (strlen($string) % 2 != 0) {
$num = 0;
foreach ($arrayString as $key => $value) {
//Collect odd and even characters
if ($key % 2 != 0) {
$values = $value;
} else {
$values = $value * 2; //The key is in odd position, so Multiply by 2
}
$sumValue = array_sum(str_split($values)); //Add up the individual digits
$num += $sumValue;
}
$isinCheckDigit = (10 - ($num % 10)) % 10;
$result1 = strtoupper($Country . $CUSIP . $isinCheckDigit);
} else {
$num = 0;
foreach ($arrayString as $key => $value) {
//Collect odd and even characters
if ($key % 2 != 0) {
$values = $value * 2; //The key is in even position, so Multiply by 2
} else {
$values = $value;
}
$sumValue = array_sum(str_split($values)); //Add up the individual digits
$num += $sumValue;
}
$isinCheckDigit = (10 - ($num % 10)) % 10;
$result1 = strtoupper($Country . $CUSIP . $isinCheckDigit);
}
$Validate = isinValidate($result1);
if ($Validate == true) {
$result = $result1;
} else {
$result = 'Please check the CUSIP';
}
} else {
$result = 'Please check the CUSIP';
}
return $result;
}
function charToCusipBinary($string)
{
return strtr(strtoupper($string), ['A' => '10', 'B' => '11', 'C' => '12', 'D' => '13', 'E' => '14', 'F' => '15', 'G' => '16', 'H' => '17', 'I' => '18', 'J' => '19', 'K' => '20', 'L' => '21', 'M' => '22', 'N' => '23', 'O' => '24', 'P' => '25', 'Q' => '26', 'R' => '27', 'S' => '28', 'T' => '29', 'U' => '30', 'V' => '31', 'W' => '32', 'X' => '33', 'Y' => '34', 'Z' => '35']);
}
function isinValidate($isin)
{
if (!preg_match('/^[A-Z]{2}[A-Z0-9]{9}[0-9]$/i', $isin)) {
return false;
}
$base10 = '';
for ($i = 0; $i <= 11; $i++) {
$base10 .= base_convert($isin{$i}, 36, 10);
}
$checksum = 0;
$len = strlen($base10) - 1;
$parity = $len % 2;
for ($i = $len; $i >= 0; $i--) {
$weighted = $base10{$i} << (($i - $parity) & 1);
$checksum += $weighted % 10 + (int) ($weighted / 10);
}
return !(bool) ($checksum % 10);
}
echo cusipToIsin('78012KD61', 'US'); //ISIN: US78012KD617
?>
I share with you a function in Matlab, thanks to @pablo and @queso.
function isISIN = checkISINCode(Isin)
%
%
%
% see:
% - source:https://en.wikipedia.org/wiki/International_Securities_Identification_Number
% - source: https://mcmap.net/q/915429/-how-to-validate-a-international-securities-identification-number-isin-number
%
%
isISIN = 0;
if length(Isin) ~= 12
return;
end
v = [];
for i = (length(Isin)-1):-1:1
c = Isin(i);
if isnan(str2double(Isin(i)))
% from ASCII
letterCode = double(upper(Isin(i))) - 64 + 9;
v = [mod(letterCode, 10), v];
if letterCode > 9
v = [floor(letterCode/10),v];
end
else
v = [int8(str2double(Isin(i))), v];
end
end
sum_ = 0;
l = length(v);
for i=1:l
if(mod(i-1,2) == 0)
d = v(i) * 2.;
sum_ = sum_ + floor( double(d) / 10.0);
sum_ = sum_ + mod(d, 10);
else
sum_ = sum_ + v(i);
end
end
checkValue = mod((10 - mod(sum_, 10)),10);
% Check Computed value with last digit
isISIN = int8(str2double(Isin(end))) == checkValue;
end
This is an approach in Swift.
It checks first for the requirement 2 letters + 10 alphanumeric characters with Regular Expression
func validateISIN(_ isin : String) -> Bool {
guard isin.range(of: "^[A-Z]{2}[A-Z0-9]{10}$", options: .regularExpression) != nil,
let checksum = Int(isin.suffix(1)) else { return false }
let digits = isin.dropLast().map{Int(String($0), radix: 36)!}.map(String.init).joined()
var sum = 0
var evenFlag = true
digits.reversed().forEach { character in
var integer = Int(String(character))!
if evenFlag { integer *= 2 }
sum += integer / 10
sum += integer % 10
evenFlag.toggle()
}
return (10 - (sum % 10)) % 10 == checksum
}
kotlin version to validate checksum:
fun check(isin: String): Boolean {
val isinInts = isin.map { it.toString().toInt(36) }
.joinToString("").map { Character.getNumericValue(it) }
val multipliers = isinInts.indices.map { it % 2 + 1 }.reversed()
val sum = multipliers.indices.sumOf { index: Int ->
(isinInts[index] * multipliers[index]).let { it / 10 + it % 10 }
}
return (10 - (sum % 10)) % 10 == 0
}
I prefer SecuMas (a python package). Check documentation on https://secumas.dev/identifier/#isin
import SecuMas
''' Validate (ISIN)'''
print (SecuMas.isin.validate('IN8081309367'))
© 2022 - 2024 — McMap. All rights reserved.