Validate International Securities Identification Number: Difference between revisions

From Rosetta Code
Content added Content deleted
mNo edit summary
Line 4: Line 4:


It consists of an exchange identifier, usually a two character ISO country code, followed by nine characters to identify the security. If the security code is less then nine characters, it is left-padded with zeroes (ASCII character 48). The final character is a checksum between '0' and '9' (ASCII 48 to 57).
It consists of an exchange identifier, usually a two character ISO country code, followed by nine characters to identify the security. If the security code is less then nine characters, it is left-padded with zeroes (ASCII character 48). The final character is a checksum between '0' and '9' (ASCII 48 to 57).

=={{header|Ada}}==

=== package ISIN ===

We start with specifying an Ada package (a collection of subprograms) to compute the checksum digit for a given ISIN (without checksum), and to check the ISIN (when given with the checksum).

<lang Ada>package ISIN is
subtype Decimal is Character range '0' .. '9';
subtype Letter is Character range 'A' .. 'Z';
Invalid_Character: exception;
function Checksum(S: String) return Decimal;
function Valid(S: String) return Boolean is
(Checksum(S(S'First .. S'Last-1)) = S(S'Last));
end ISIN;</lang>

The implementation of the package is as follows.

<lang Ada>package body ISIN is
function To_Digits(S: String) return String is
-- converts a string of decimals and letters into a string of decimals
Offset: constant Integer := Character'Pos('A')-10;
-- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ...
begin
if S = "" then
return "";
elsif S(S'First) = ' ' then -- skip blanks
return To_Digits(S(S'First+1 .. S'Last));
elsif S(S'First) in Decimal then
return S(S'First) & To_Digits(S(S'First+1 .. S'Last));
elsif S(S'First) in Letter then
return To_Digits(Integer'Image(Character'Pos(S(S'First))-Offset))
& To_Digits(S(S'First+1 .. S'Last));
else
raise Invalid_Character;
end if;
end To_Digits;
function Checksum(S: String) return Decimal is
T: String := To_Digits(S);
-- first convert letters to numbers by adding their ordinal position
Double: Boolean := True;
Sum: Integer range 0 .. 9 := 0;
Add: Integer range 0 .. 18;
Result: String(1 .. 2);
begin
for I in reverse T'Range loop
Add := Integer'Value(T(I .. I));
if Double then
-- starting with the rightmost digit, every other digit is doubled
Add := Add * 2;
if Add > 8 then
-- if Add is 1X (*10, 12, ..., 18*), add X+1
Add := (Add mod 10) + 1;
end if;
end if;
Double := not Double;
Sum := (Sum + Add) mod 10;
end loop;
Result:= Integer'Image((10-Sum) mod 10); -- result is " X", with Decimal X
return Result(2);
end Checksum;
end ISIN;</lang>

=== Computing Checksums ===

Not the main program is easy. It reads a couple of ISINs (without checksum) from the command line and ouputs the checksum digits.

<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;

procedure Compute_ISIN is
begin
for I in 1 .. Ada.Command_Line.Argument_Count loop
Ada.Text_IO.Put_Line("The Checksum for " &
Ada.Command_Line.Argument(I) & " is " &
ISIN.Checksum(Ada.Command_Line.Argument(I)));
end loop;
end Compute_ISIN;</lang>

We compute the ISIN-Checksums for Apple, Apple with two digits swapped, the Treasury Corporation of Victoria, and the Treasury Corporation of Victoria with two digits swapped. Note that the first swap does actually change the checksum, while the second one does not. I.e., the ISIN checksums don't always discover flaws, such as swapping two adjacent digits.

<pre>./compute_isin US037833100 US037383100 AU0000XVGZA AU0000VXGZA
The Checksum for US037833100 is 5
The Checksum for US037383100 is 9
The Checksum for AU0000XVGZA is 3
The Checksum for AU0000VXGZA is 3</pre>

=== Verifying ISINs with given Checksums ===

Similarily to the above, we check if an ISIN with checksum is valid.

<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;

procedure Check_ISIN is
begin
for I in 1 .. Ada.Command_Line.Argument_Count loop
if ISIN.Valid(Ada.Command_Line.Argument(I)) then
Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " OK!");
else
Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " ** Fail! **");
end if;
end loop;
end Check_ISIN;</lang>

We check Apple's ISIN, and two "misspellings" of Apple's ISIN, we got by permuting two digits or letters. The error of permuting "US" to "SU" is not discovered by the algorithm, the error of permuting 83 to 38 is.

<pre>./check_isin US0378331005 SU0378331005 US0373831005
US0378331005 OK!
SU0378331005 OK!
US0373831005 ** Fail! **</pre>


=={{header|VB6}}==
=={{header|VB6}}==

Revision as of 15:18, 23 February 2015

Task
Validate International Securities Identification Number
You are encouraged to solve this task according to the task description, using any language you may know.

Calculate an International Securities Identification Number (ISIN)

An International Securities Identification Number (ISIN) is a unique international identifier for a financial security such as a stock or bond.

It consists of an exchange identifier, usually a two character ISO country code, followed by nine characters to identify the security. If the security code is less then nine characters, it is left-padded with zeroes (ASCII character 48). The final character is a checksum between '0' and '9' (ASCII 48 to 57).

Ada

package ISIN

We start with specifying an Ada package (a collection of subprograms) to compute the checksum digit for a given ISIN (without checksum), and to check the ISIN (when given with the checksum).

<lang Ada>package ISIN is

  subtype Decimal is Character range '0' .. '9';
  subtype Letter  is Character range 'A' .. 'Z';
  
  Invalid_Character: exception;
  
  function Checksum(S: String) return Decimal;
  function Valid(S: String) return Boolean is
    (Checksum(S(S'First .. S'Last-1)) = S(S'Last));
  

end ISIN;</lang>

The implementation of the package is as follows.

<lang Ada>package body ISIN is

  function To_Digits(S: String) return String is
     -- converts a string of decimals and letters into a string of decimals
     Offset: constant Integer := Character'Pos('A')-10;
     -- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ...
  begin
     if S = "" then

return "";

     elsif S(S'First) = ' ' then -- skip blanks

return To_Digits(S(S'First+1 .. S'Last));

     elsif S(S'First) in Decimal then 

return S(S'First) & To_Digits(S(S'First+1 .. S'Last));

     elsif S(S'First) in Letter then

return To_Digits(Integer'Image(Character'Pos(S(S'First))-Offset)) & To_Digits(S(S'First+1 .. S'Last));

     else 

raise Invalid_Character;

     end if;
  end To_Digits;
  
  function Checksum(S: String) return Decimal is
     T: String := To_Digits(S); 
       -- first convert letters to numbers by adding their ordinal position
     Double: Boolean := True;
     Sum: Integer range 0 .. 9 := 0;
     Add: Integer range 0 .. 18;
     Result: String(1 .. 2);
  begin
     for I in reverse T'Range loop

Add := Integer'Value(T(I .. I)); if Double then -- starting with the rightmost digit, every other digit is doubled Add := Add * 2; if Add > 8 then -- if Add is 1X (*10, 12, ..., 18*), add X+1 Add := (Add mod 10) + 1; end if; end if; Double := not Double; Sum := (Sum + Add) mod 10;

     end loop;
     Result:= Integer'Image((10-Sum) mod 10); -- result is " X", with Decimal X
     return Result(2); 
  end Checksum;
  

end ISIN;</lang>

Computing Checksums

Not the main program is easy. It reads a couple of ISINs (without checksum) from the command line and ouputs the checksum digits.

<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;

procedure Compute_ISIN is begin

  for I in 1 .. Ada.Command_Line.Argument_Count loop
     Ada.Text_IO.Put_Line("The Checksum for " & 

Ada.Command_Line.Argument(I) & " is " & ISIN.Checksum(Ada.Command_Line.Argument(I)));

  end loop;

end Compute_ISIN;</lang>

We compute the ISIN-Checksums for Apple, Apple with two digits swapped, the Treasury Corporation of Victoria, and the Treasury Corporation of Victoria with two digits swapped. Note that the first swap does actually change the checksum, while the second one does not. I.e., the ISIN checksums don't always discover flaws, such as swapping two adjacent digits.

./compute_isin US037833100 US037383100 AU0000XVGZA AU0000VXGZA 
The Checksum for US037833100 is 5
The Checksum for US037383100 is 9
The Checksum for AU0000XVGZA is 3
The Checksum for AU0000VXGZA is 3

Verifying ISINs with given Checksums

Similarily to the above, we check if an ISIN with checksum is valid.

<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;

procedure Check_ISIN is begin

  for I in 1 .. Ada.Command_Line.Argument_Count loop
     if ISIN.Valid(Ada.Command_Line.Argument(I)) then
        Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " OK!");
     else
        Ada.Text_IO.Put_Line(Ada.Command_Line.Argument(I) & " ** Fail! **");
     end if;
  end loop;

end Check_ISIN;</lang>

We check Apple's ISIN, and two "misspellings" of Apple's ISIN, we got by permuting two digits or letters. The error of permuting "US" to "SU" is not discovered by the algorithm, the error of permuting 83 to 38 is.

./check_isin US0378331005 SU0378331005 US0373831005
US0378331005 OK!
SU0378331005 OK!
US0373831005 ** Fail! **

VB6

<lang VB6> Option Explicit

Function MakeIsinCode(Exchange As String, security As String)

   Dim numLeadingZeroes As Integer
   
   numLeadingZeroes = 9 - Len(security)
   
   Dim leader As String
   
   leader = Exchange & String(numLeadingZeroes, "0") & security
   
   MakeIsinCode = leader & CStr(IsinCheckDigit(leader))

End Function

Function IsinCheckDigit(ByVal security As String) As Integer

   Dim digits As String
   
   Dim i As Integer
   
   For i = 1 To Len(security)
       Dim ch As String
       
       ch = UCase(Mid(security, i, 1))
       
       If ch >= "A" And ch <= "Z" Then
           ' A to Z translated to "10", "11", .. "35"
           digits = digits & CStr(Asc(ch) - 55)
       ElseIf ch >= "0" And ch <= "9" Then
           digits = digits & ch
       Else
           Err.Raise 50001, , "Security must contain only letters and digits"
       End If
   Next
   
   Dim total As Integer
   Dim tmp As Integer
   
   total = 0
   
   'If rightmost even, "other" digits for doubling are 2,4,6. If rightmost odd, they're 1,3,5.
   'rightmost digit is always doubled, so start with it and work backwards
   Dim other As Boolean
   other = True
   
   For i = Len(digits) To 1 Step -1
       tmp = CInt(Mid(digits, i, 1))
       
       If other Then
           If tmp < 5 Then
               ' 0 to 4 map to 0,2,4,6,8
               total = total + (tmp * 2)
           Else
               ' 5 to 9 map to 1,3,5,7,9
               total = total + ((tmp * 2) - 9)
           End If
       Else
           total = total + tmp
       End If
       
       'Toggle doubling flag
       other = Not other
   Next
   
   'Last Mod 10 is to wrap 10 to zero
   IsinCheckDigit = (10 - (total Mod 10)) Mod 10

End Function


</lang>