Skip to content

Modulus 11 ‐ English

Jorge Rojas edited this page Feb 14, 2024 · 1 revision

History

Modulus 11 started as an experiment with a simple problem, focused on calculating the check digit of the "RUT" (Rol Único Tributario) or RUN, which is a code used to identify individuals and entities in Chile.

The modulus 11, or check digit, is generated from the integer number. Therefore, it is a function of the number, and each number has only one check digit.

If entering a pair of number-check digit yields a result different from the calculated one, either the number is incorrect or the check digit is incorrect. In both cases, we confirm that it is invalid and cannot be used.

I generalized the approach, thinking about other potential applications that also use this algorithm, dividing the software into two packages:

  1. Generic calculation of modulus 11 for an integer.
  2. Validation and generation of RUTs, based on general rules.

1. Generic Calculation of Modulus 11

The calculation is documented on the Modulus 11 project page, in English. Here is a summary.

Algorithm

Given a positive integer, number, including zero and ordered as a vector of its digits,
And the circular buffer of six digits (2,3,4,5,6,7), truncated or extended to the number of digits of the number,

  1. Multiply in order from lowest to highest, like vectors of the number of digits.
  2. Sum the results.
  3. Then, obtain the modulus 11 (remainder).
  4. The generic calculation of modulus 11 involves the following conversion: a) Subtract the remainder from 11 (11-remainder). b) If the result of the subtraction is 10, the modulus is 1. c) If the result of the subtraction is 11, the modulus is 0. d) If not, the result of the subtraction is the same digit (between 0-9).

Note that:

  1. Zero can appear in two circumstances: The result of the subtraction is 11 or 0.
  2. One can appear in two circumstances: The result of the subtraction is 10 or 1.

In the case of the Chilean RUT, when the result of the subtraction is 10, the algorithm is modified and a letter K is returned.

Example 1: 987654321

Circular buffer 4 3 2 7 6 5 4 3 2
Number 9 8 7 6 5 4 3 2 1
Multiplication 4x9 3x8 2x7 7x6 6x5 5x4 4x3 3x2 2x1
Result 36 24 14 42 30 20 12 6 2

The sum is 186
186 % 11 = 10
DIFF = 11-10 = 1
Code = 1, 987654321 / 1

Example 2: 44261539

Circular buffer 4 3 2 7 6 5 4 3 2
Number 4 4 2 6 1 5 3 9
Multiplication 4x 3x4 2x4 7x2 6x6 5x1 4x5 3x3 2x9
Result 0 12 8 14 36 5 20 9 18

The sum is 122
122 % 11 = 1
DIFF = 11-1 = 10 // Rule: if 11 => 1
Code = 1, 44261539 / 1

As it is a Chilean RUT, when the result of the subtraction is 10, the algorithm is modified and a letter K is returned, so it becomes 44261539 / K or 44261539-K (with dots, 44.261.539-K)

Design of the Calculation Module

The design consists of a class that encapsulates the calculation logic, called Modulus11, and a read-only record that encapsulates the response with its format, called ModulusRecord.

Modulus11

To prevent concurrency issues, this class is not abstract. The raw modulus calculation can be done using the GetModulus(long input) method.
The following generic test shows how to use it to calculate the modulus:

[Test]
public void TestBorderCondition_ModulusIs_11()
{
    var m11 = new Modulo11.Modulus11(charFor10Value: "10", charFor11Value: "11");

    var ver = m11.GetModulus(11111151);

    Assert.That<string>(ver, Is.EqualTo("11"));
}

The parameters charFor10Value and charFor11Value are used to define the response when returning 10 or 11, and by default, they are 1 and 0, according to the standard definition (in the case of the Chilean RUT, we change the value of charFor10Value to 'k').
This other test shows how to use it to return a record with the data separated, allowing to stop the complete value already calculated and apply formatting for a simple display (in addition to showing how to do generic tests based on use cases in NUnit):

[TestCase("1.000.000.000", "6", Category = "Border", TestName = "Border Conditions - 1M")]
[TestCase("1.000.000.001", "4", Category = "Border", TestName = "Border Conditions - 1M+1")]
public void TestDecimalOrders(string input, string expectedResult)
{
    var m11 = new Modulo11.Modulus11("k");

    var aNumberInt = long.Parse(input.Replace(".", ""));

    ModulusRecord rec = m11.GetModulusRecord(aNumberInt);

    Assert.That<string>(rec.Digit, Is.EqualTo(expectedResult));

    Assert.That<string>(rec.ToString(), Is.EqualTo($"{aNumberInt}-{expectedResult}"));
}

ModulusRecord

The ModulusRecord record stores the number, its modulus, and formatting options used for ToString(). It allows obtaining a self-contained, lightweight, and versatile copy usable directly by applications.
It has the following C# 12 shorthand initialization:

public record ModulusRecord(long Number, string Digit, string Hyphen = "-", string NumberGroupSeparator = "")

Internally, it overrides the ToString() function to display a number-modulus output with various options and some optimizations to speed up the display in numeric format, with or without dots.