Validate International Securities Identification Number: Difference between revisions

→‎{{header|Ada}}: update to use algorithm from luhn task and newer test cases
(→‎{{header|Ada}}: update to use algorithm from luhn task and newer test cases)
Line 65:
 
=={{header|Ada}}==
Calling the existing Luhn algorithm implementation from the ''[[Luhn test of credit card numbers]]'' task.
 
<lang Ada>procedure ISIN is
{{update|Ada|Use the new test-cases, and consider calling the existing Luhn algorithm implementation from the ''[[Luhn test of credit card numbers]]'' task instead of duplicating it.}}
-- Luhn_Test copied from other Task
 
function Luhn_Test (Number: String) return Boolean is
=== package ISIN ===
Sum : Natural := 0;
 
Odd : Boolean := True;
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).
Digit: Natural range 0 .. 9;
 
begin
<lang Ada>package ISIN is
for p in reverse Number'Range loop
Digit := Integer'Value (Number (p..p));
if Odd then
Sum := Sum + Digit;
else
Sum := Sum + (Digit*2 mod 10) + (Digit / 5);
end if;
Odd := not Odd;
end loop;
return (Sum mod 10) = 0;
end Luhn_Test;
subtype Decimal is Character range '0' .. '9';
subtype Letter is Character range 'A' .. 'Z';
subtype ISIN_Type is String(1..12);
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
-- converts a string of decimals and letters into a string of decimals
function To_Digits(S: String) return String is
-- Character'Pos('A')-Offset=10, Character'Pos('B')-Offset=11, ...
-- 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, ...
Invalid_Character: exception;
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 ChecksumIs_Valid_ISIN(S: StringISIN_Type) return DecimalBoolean is
TNumber : 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
forreturn S(S'First) I in reverseLetter T'Range loopand
S(S'First+1) in Letter and
Add := Integer'Value(T(I .. I));
S(S'Last) in Decimal and
if Double then
Luhn_Test(Number);
-- starting with the rightmost digit, every other digit is doubled
end Add := Add * 2Is_Valid_ISIN;
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 ===
 
Now the main program is easy.
It reads a couple of ISINs (without checksum) from the command line
and outputs the checksum digits.
 
<lang Ada>with Ada.Command_Line, Ada.Text_IO, ISIN;
 
Test_Cases : constant Array(1..6) of ISIN_Type :=
procedure Compute_ISIN is
("US0378331005",
"US0373831005",
"U50378331005",
-- excluded by type with fixed length
-- "US03378331005",
"AU0000XVGZA3",
"AU0000VXGZA3",
"FR0000988040");
begin
for I in 1 .. Ada.Command_Line.Argument_CountTest_Cases'Range loop
Ada.Text_IO.Put_Line("TheTest_Cases(I) Checksum for& ":" &
Boolean'Image(Is_Valid_ISIN(Test_Cases(I))));
Ada.Command_Line.Argument(I) & " is " &
ISIN.Checksum(Ada.Command_Line.Argument(I)));
end loop;
-- using wrong length will result in an exception:
end Compute_ISIN;</lang>
Ada.Text_IO.Put("US03378331005:");
 
Ada.Text_IO.Put_Line(Boolean'Image(Is_Valid_Isin("US03378331005")));
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.
exception
Note that the first swap does actually change the checksum, while the second one does not.
when others =>
I.e., the ISIN checksums don't always discover flaws, such as swapping two adjacent digits.
Ada.Text_IO.Put_Line("Exception occured");
 
end ISIN;</lang>
<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.
 
Output:
<pre>./check_isin US0378331005 SU0378331005 US0373831005
<pre>US0378331005 OK!:TRUE
US0373831005:FALSE
SU0378331005 OK!
U50378331005:FALSE
US0373831005 ** Fail! **</pre>
AU0000XVGZA3:TRUE
AU0000VXGZA3:TRUE
FR0000988040:TRUE
US03378331005:Exception occured</pre>
 
=={{header|C}}==
256

edits