【问题标题】:How to validate a International Securities Identification Number (ISIN) number如何验证国际证券识别码 (ISIN) 号码
【发布时间】:2013-04-14 23:30:48
【问题描述】:

如果我没记错的话,ISIN 数字最后一个位置是验证数字。 在前 11 位的函数中确定其值的数学函数是什么?

【问题讨论】:

    标签: math financial


    【解决方案1】:

    在其他示例的基础上,这是一个 C# 实现,它将验证 ISIN 和 CUSIP(可能还有其他一些 Luhn 变体)。

    用法:

    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);
    }
    

    实施:

    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;
        }
    }
    

    将校验和验证与正则表达式模式匹配结合使用可能很有意义。这些是我使用的正则表达式:

    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]$

    【讨论】:

    • 也适用于FIGI。
    • 这很有帮助。谢谢!请注意,ISIN 命名空间不再是最新的。例如,现在存在“EU”前缀。
    【解决方案2】:

    http://en.wikipedia.org/wiki/International_Securities_Identification_Number

    计算 ISIN 校验位的过程类似于 CUSIP 中使用的“Modulus 10 Double Add Double”技术。要计算校验位,首先将任何字母转换为数字,方法是将它们在字母表中的序数位置加到 9,这样 A = 10 和 M = 22。从最右边的数字开始,每隔一个数字乘以 2。 (对于 CUSIP 校验位,这两个步骤是相反的。)将生成的数字串(大于 9 的数字变成两个单独的数字)相加。从大于或等于它的以零结尾的最小数字减去这个和:这给出了校验位,也称为和模 10 的十进制补码。也就是说,得到的和,包括校验 -位,是 10 的倍数。

    他们也有good example。

    【讨论】:

    • 该描述有一些错误:“...每隔一个数字乘以 2”。正如我在其他参考资料中可以找到的,并检查示例:不是所有其他数字,而是替代数字都乘以 2。
    • @PabloFranciscoPérezHidalgo:将every other digit 解释为alternative digits 是完全合理的。不清楚您认为维基百科条目中的错误是什么。
    【解决方案3】:

    根据维基百科上公布的例子,方法是:

    1. 将每个字母替换为其序数(A=1、B=2 等)加 9 ->
    2. 对于从最右边开始的偶数位置 () 的每个数字,将其替换为其双精度数(两个向量条目中的两个数字)-> ;
    3. 验证码:

    JavaScript 中可能的实现是:

    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);
    }
    

    编辑:包含@queso 更新:

    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
    }
    

    【讨论】:

    • 对于上面的代码,ISIN XS0977502110 无法在此处查看:en.wikipedia.org/wiki/… 我能够在此处更新您的代码 jsfiddle.net/markbenda/nh2w1Lbh/16。感谢您付出了艰辛的努力。
    • @Queso 我已经编辑了我的答案以包含您的代码建议。感谢您的改进!
    • 我已经区分了这两个版本,非空白/括号修订似乎是 1) 在第二行返回 false 而不是 null,以及 2) 改为返回 (10 - (sum % 10)) % 10 10 - (sum % 10) 在倒数第二行中,添加一个额外的 mod 10 作为最后一步。 diffchecker.com/jEeML1lU
    【解决方案4】:

    感谢 @pablo 和 @queso,我与您分享了 Matlab 中的一个函数。

    function isISIN = checkISINCode(Isin)
    %
    %
    %
    % see:
    %   - source:https://en.wikipedia.org/wiki/International_Securities_Identification_Number
    %   - source: https://stackoverflow.com/questions/16140753/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
    

    【讨论】:

      【解决方案5】:

      我想分享我在 R 中的实现。它不需要任何特定的包。

      mgsub 是一种支持功能,允许在一个命令中替换 ISIN 代码中的所有字符。抄自Replace multiple letters with accents with gsub

      iso3166alpha2$Code 包含Grenade 列出的国家/地区列表

      算法在isIsin(x)函数中实现,如果ISIN码有效,则返回TRUE

      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 
      
      }
      

      【讨论】:

        【解决方案6】:
        <?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
        ?>
        

        【讨论】:

        • 我创建了一个基于 [en.wikipedia.org/wiki/… 的 PHP 函数,您可以提供 7 位 CUSIP 号码和 2 位国家代码
        【解决方案7】:

        这是 Swift 中的一种方法。

        它首先使用正则表达式检查要求 2 个字母 + 10 个字母数字字符

        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
        }
        

        【讨论】:

          【解决方案8】:

          用于验证校验和的 kotlin 版本:

          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
          }
          

          【讨论】:

            猜你喜欢
            • 2012-01-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-22
            • 2010-09-15
            • 2012-01-13
            • 1970-01-01
            相关资源
            最近更新 更多