Skip to content

Commit c724d0c

Browse files
authored
Merge pull request #47 from shinji-san/feature-ISIN_AlphaNumericSupport
Feature - Converting an ISIN to a numeric string for Luhn validation & Luhn check digit computation Resolves: #47
2 parents b54a9de + e67cca2 commit c724d0c

File tree

5 files changed

+225
-1
lines changed

5 files changed

+225
-1
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
### Added
9+
- Add `ConvertAlphaNumericToNumeric` method to convert a string containing alphanumeric characters to a string containing only numeric characters (use case: converting an ISIN to a numeric string for Luhn validation).
10+
711
## [1.0.1] - 2024-02-19
812
### Fixed
913
- Fixed a bug in `Luhn.ComputeLuhnNumber` and `Luhn.ComputeLuhnCheckDigit` methods that sometimes returned an incorrect result.

README.md

+53
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# LuhnDotNet
22
An C# implementation of the Luhn algorithm.
33

4+
The Luhn algorithm is a checksum formula used to validate identification numbers like credit card numbers. It works by doubling every second digit from the right, summing all the digits, and checking if the total is a multiple of 10. It's widely used and is specified in ISO/IEC 7812-1.
5+
46
# Build & Test Status Of Default Branch
57
<table>
68
<thead>
@@ -210,6 +212,57 @@ namespace Example4
210212
}
211213
```
212214

215+
## Validate ISIN with LuhnDotNet and ConvertAlphaNumericToNumeric
216+
217+
The `LuhnDotNet` library can be used in combination with the `ConvertAlphaNumericToNumeric` method to validate an International Securities Identification Number (ISIN). An ISIN uniquely identifies a security, such as stocks, bonds or derivatives. It is a 12-character alphanumeric code.
218+
219+
The `ConvertAlphaNumericToNumeric` method is used to convert the alphanumeric ISIN to a numeric string, where each letter in the input string is replaced by its decimal ASCII value minus 55. This numeric string can then be validated using the `Luhn.IsValid` method.
220+
221+
Here is an example of how to use these methods to validate an ISIN:
222+
223+
```csharp
224+
using System;
225+
using LuhnDotNet;
226+
227+
namespace Example5
228+
{
229+
public class Program
230+
{
231+
public static void Main(string[] args)
232+
{
233+
string isin = "US0378331005";
234+
bool isValid = Luhn.IsValid(isin.ConvertAlphaNumericToNumeric());
235+
236+
Console.WriteLine($"The ISIN {isin} is valid: {isValid}");
237+
}
238+
}
239+
}
240+
```
241+
## Compute ISIN Check Digit with LuhnDotNet and ConvertAlphaNumericToNumeric
242+
243+
The `LuhnDotNet` library provides the `ComputeLuhnCheckDigit` method which can be used to compute the check digit of a numeric string according to the Luhn algorithm. When dealing with an International Securities Identification Number (ISIN), which is a 12-character alphanumeric code, we first need to convert the alphanumeric ISIN to a numeric string. This can be achieved using the `ConvertAlphaNumericToNumeric` method.
244+
245+
Here is an example of how to compute the check digit of an ISIN:
246+
247+
```csharp
248+
using System;
249+
using LuhnDotNet;
250+
251+
namespace Example6
252+
{
253+
public class Program
254+
{
255+
public static void Main(string[] args)
256+
{
257+
string isinWithoutCheckDigit = "US037833100";
258+
byte checkDigit = Luhn.ComputeLuhnCheckDigit(isinWithoutCheckDigit.ConvertAlphaNumericToNumeric());
259+
260+
Console.WriteLine($"The check digit for ISIN {isinWithoutCheckDigit} is: {checkDigit}");
261+
}
262+
}
263+
}
264+
```
265+
213266
# CLI building instructions
214267
For the following instructions, please make sure that you are connected to the internet. If necessary, NuGet will try to restore the [xUnit](https://xunit.net/) packages.
215268
## Using dotnet to build for .NET 6, .NET 7, .NET 8 and .NET FX 4.x

src/Luhn.cs

+63
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace LuhnDotNet
3838
using System.Diagnostics.CodeAnalysis;
3939
using System.Globalization;
4040
#if !NET6_0_OR_GREATER
41+
using System.Text;
4142
using System.Text.RegularExpressions;
4243
#endif
4344

@@ -162,6 +163,68 @@ public static bool IsValid(string number, byte checkDigit)
162163
.SumDigits() == 0;
163164
}
164165

166+
/// <summary>
167+
/// Converts an alphanumeric string to a numeric string.
168+
/// </summary>
169+
/// <param name="alphaNumeric">The alphanumeric string to convert.</param>
170+
/// <returns>A numeric string where each letter in the input string is replaced by its decimal ASCII value
171+
/// minus 55.</returns>
172+
/// <remarks>
173+
/// This method iterates over each character in the input string. If the character is a letter, it is replaced
174+
/// by its decimal ASCII value minus 55. If the character is a digit, it is left unchanged.
175+
/// </remarks>
176+
public static string ConvertAlphaNumericToNumeric(this string alphaNumeric)
177+
#if NET6_0_OR_GREATER
178+
{
179+
Span<char> result = stackalloc char[alphaNumeric.Length * 2];
180+
int index = 0;
181+
182+
foreach (char c in alphaNumeric.ToUpper())
183+
{
184+
if (char.IsLetter(c))
185+
{
186+
string numericValue = (c - 55).ToString();
187+
foreach (char numChar in numericValue)
188+
{
189+
result[index++] = numChar;
190+
}
191+
}
192+
else if (char.IsDigit(c))
193+
{
194+
result[index++] = c;
195+
}
196+
else
197+
{
198+
throw new ArgumentException($"The character '{c}' is not a letter or a digit!", nameof(alphaNumeric));
199+
}
200+
}
201+
202+
return result[..index].ToString();
203+
}
204+
#else
205+
{
206+
var result = new StringBuilder();
207+
208+
foreach (char c in alphaNumeric.ToUpper())
209+
{
210+
if (char.IsLetter(c))
211+
{
212+
result.Append(c - 55);
213+
}
214+
else if (char.IsDigit(c))
215+
{
216+
result.Append(c);
217+
}
218+
else
219+
{
220+
throw new ArgumentException($"The character '{c}' is not a letter or a digit!", nameof(alphaNumeric));
221+
}
222+
}
223+
224+
return result.ToString();
225+
}
226+
#endif
227+
165228
/// <summary>
166229
/// Doubling of every second digit.
167230
/// </summary>

src/LuhnDotNet.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<SignAssembly>True</SignAssembly>
77
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
88
<ImplicitUsings>disable</ImplicitUsings>
9+
<LangVersion>latest</LangVersion>
910
<Nullable>disable</Nullable>
1011
<Authors>Sebastian Walther</Authors>
1112
<PackageId>LuhnDotNet</PackageId>

tests/LuhnTest.cs

+104-1
Original file line numberDiff line numberDiff line change
@@ -243,5 +243,108 @@ public void LuhnCheckDigitValidationExceptionTest(string invalidNumber, byte che
243243
{
244244
Assert.Throws<ArgumentOutOfRangeException>(() => IsValid(invalidNumber, checkDigit));
245245
}
246+
247+
/// <summary>
248+
/// Test data for ConvertAlphaNumericToNumeric method.
249+
/// </summary>
250+
public static IEnumerable<object[]> ConvertAlphaNumericToNumericData =>
251+
new List<object[]>
252+
{
253+
new object[] { "A1B2C3", "101112123" },
254+
new object[] { "Z9Y8X7", "359348337" },
255+
new object[] { "123", "123" },
256+
new object[] { "DE0006069008", "13140006069008" },
257+
new object[] { "ABC", "101112" },
258+
new object[] { "", "" },
259+
};
260+
261+
/// <summary>
262+
/// Tests the ConvertAlphaNumericToNumeric method.
263+
/// </summary>
264+
/// <param name="input">Input string</param>
265+
/// <param name="expected">Expected output</param>
266+
[Theory(DisplayName = "Converts an alphanumeric string to a numeric string")]
267+
[MemberData(nameof(ConvertAlphaNumericToNumericData), MemberType = typeof(LuhnTest))]
268+
public void ConvertAlphaNumericToNumericTest(string input, string expected)
269+
{
270+
Assert.Equal(expected, input.ConvertAlphaNumericToNumeric());
271+
}
272+
273+
/// <summary>
274+
/// Tests the ConvertAlphaNumericToNumeric method with invalid input.
275+
/// </summary>
276+
/// <remarks>
277+
/// This test checks if the ConvertAlphaNumericToNumeric method throws an ArgumentException when it is given an
278+
/// invalid input string that contains non-alphanumeric characters. The test uses the Assert.Throws method from
279+
/// xUnit to check if the expected exception is thrown.
280+
/// </remarks>
281+
[Fact]
282+
public void ConvertAlphaNumericToNumericExceptionTest()
283+
{
284+
Assert.Throws<ArgumentException>(()=> "!@#$%^&*()".ConvertAlphaNumericToNumeric());
285+
}
286+
287+
/// <summary>
288+
/// Test data for IsValid method in combination with ConvertAlphaNumericToNumeric.
289+
/// </summary>
290+
public static IEnumerable<object[]> IsValidWithConvertData =>
291+
new List<object[]>
292+
{
293+
new object[] { "DE0006069008", true },
294+
new object[] { "DE0006069007", false },
295+
new object[] { "DE000BAY0017", true },
296+
new object[] { "DE000BAY0018", false },
297+
new object[] { "AU0000XVGZA3", true },
298+
new object[] { "US0378331005", true },
299+
};
300+
301+
/// <summary>
302+
/// Tests the IsValid method in combination with ConvertAlphaNumericToNumeric.
303+
/// </summary>
304+
/// <param name="input">Input string</param>
305+
/// <param name="expected">Expected output</param>
306+
[Theory(DisplayName = "Validates a numeric string converted from an alphanumeric string")]
307+
[MemberData(nameof(IsValidWithConvertData), MemberType = typeof(LuhnTest))]
308+
public void IsValidWithConvertTest(string input, bool expected)
309+
{
310+
Assert.Equal(expected, IsValid(input.ConvertAlphaNumericToNumeric()));
311+
}
312+
313+
/// <summary>
314+
/// Provides test data for the ComputeLuhnCheckDigit method in combination with ConvertAlphaNumericToNumeric.
315+
/// </summary>
316+
/// <remarks>
317+
/// This method returns a collection of object arrays, where each array contains an input string and the
318+
/// expected output for the ComputeLuhnCheckDigit method. The input string is an alphanumeric string that
319+
/// represents a part of an ISIN without the check digit. The expected output is the check digit that makes the
320+
/// entire ISIN valid according to the Luhn algorithm.
321+
/// </remarks>
322+
public static IEnumerable<object[]> ComputeLuhnCheckDigitWithConvertData =>
323+
new List<object[]>
324+
{
325+
new object[] { "DE000606900", 8 },
326+
new object[] { "DE000BAY001", 7 },
327+
new object[] { "AU0000XVGZA", 3 },
328+
new object[] { "US037833100", 5 },
329+
};
330+
331+
/// <summary>
332+
/// Tests the ComputeLuhnCheckDigit method in combination with ConvertAlphaNumericToNumeric.
333+
/// </summary>
334+
/// <param name="input">Input string</param>
335+
/// <param name="expected">Expected output</param>
336+
/// <remarks>
337+
/// This test checks if the ComputeLuhnCheckDigit method returns the expected check digit when it is given an
338+
/// alphanumeric string that represents a part of an ISIN without the check digit. The input string is first
339+
/// converted to a numeric string using the ConvertAlphaNumericToNumeric method, and then the
340+
/// ComputeLuhnCheckDigit method is called with this numeric string. The test uses the Assert.Equal method from
341+
/// xUnit to check if the actual check digit matches the expected check digit.
342+
/// </remarks>
343+
[Theory]
344+
[MemberData(nameof(ComputeLuhnCheckDigitWithConvertData), MemberType = typeof(LuhnTest))]
345+
public void ComputeLuhnCheckDigitWithConvertTest(string input, byte expected)
346+
{
347+
Assert.Equal(expected, ComputeLuhnCheckDigit(input.ConvertAlphaNumericToNumeric()));
348+
}
246349
}
247-
}
350+
}

0 commit comments

Comments
 (0)