Module 97 check digits - Decompose and Compose

 

We can calculate the remainder of a relatively small number divided by 97 using this F# function:

let mod97Small (num:int64) =

       let num97=Convert.ToDouble(num)/97.0

       Convert.ToInt64 ((num97-(Math.Truncate(num97)))*97.0)

 

But this function is unusable when the number (num) is larger then fifteen digits. In case of a large number, we can decompose it in smaller numbers (the map phase), compute the reminder modulo 97 on this small numbers and aggregate (reduce) the reminders.

In Mod97 procedure in F#  we presented the F# code for the computation of the check digits using Modulo 97. Here we present the F# code where we compute the Modulo 97 check digits in a more systematic way:

-        we decompose the large number in a list of (small) numbers ,

-        compute the reminders on the decomposed list of smaller numbers and

-        compose (aggregate) the reminders.

 

Some examples

 

The properties of modulo 97 computations are:

1) for addition: mod(x+y)=mod(mod(x)+mod(y))

2) for multiplication:  mod(x*y)=mod(mod(x)*mod(y))

 

mod(10)=10

mod(100)=mod(10*10)=3

mod(170)=mod(97) + mod(73)=mod( 0+73)=73

mod(1000)=mod(100*10)=mod(100)*mod(10)=3*10=30 

mod(100*100)=9

mod(10**8)=mod(10**4 * 10**4)= mod(9*9=81)=81

mod(10**11)=mod(1000*10**8)= mod(30*81=2430)=5

mod (10**14)=mod(10**11)*mod(10**3)=mod(5*30)=mod(150)=53

 

F# code

 

#light

namespace Modul97

module Mod97DecomposeCompose=

    open System

        //1). Modulo 97 of a "small" number, less than 16 digits long

    let mod97Small (num:int64) =

       let num97=Convert.ToDouble(num)/97.0

       Convert.ToInt64 ((num97-(Math.Truncate(num97)))*97.0)

 

    // 2). Decompose the problem - split the string of digits in chunks, for example 123 4567 8901 for chunk length 4

    let splitInChunks (chunkLen:int) (inputString:string) =

        let noChunks (inputString:string)=

           Convert.ToInt32(Math.Truncate(0.999+ (Convert.ToDouble(inputString.Length)/ Convert.ToDouble(chunkLen))))

        let diffInChunks inputString=chunkLen-(noChunks inputString)*chunkLen+inputString.Length

        let chunkLenFrom i inputString=

           match i with

            |0 -> 0

            |_ -> (diffInChunks inputString)+(i-1)*chunkLen

        let chunkLenInd i inputString=

          match i with

            |0 -> diffInChunks inputString

            |_ -> chunkLen

        let inputString00=inputString+"00"

        //  printfn "nochunks %A " (noChunks inputString00)

        List.init (noChunks inputString00) (fun i->

        //  printfn "cf %A ct %A" (chunkLenFrom i inputString00) (chunkLenInd i inputString00)

        Convert.ToInt64(inputString00.Substring(chunkLenFrom i inputString00,(chunkLenInd i inputString00)) ))

 

    //3). Calculate the modulo 97 on the decomposed list, for example mod(123)*mod(100000000) + mod(4567)*mod(10000)+mod(8901)

    let mod97List (chunkLen:int) (splitedList:int64 list)  =

       let modPowTen =

           Math.Pow(Convert.ToDouble(10),Convert.ToDouble(chunkLen))

           |>Convert.ToInt64

           |> mod97Small

           |> Convert.ToDouble 

       splitedList

       |>List.mapi (fun i (elem:int64) ->

        //printfn "%A xxxx %A xxx %A %A" elem (mod97Small elem)

        //   (Math.Pow(modPowTen,Convert.ToDouble ((splitedList.Length)- i-1 )))

        //   (Convert.ToDouble(mod97Small elem)* Math.Pow(modPowTen,Convert.ToDouble ((splitedList.Length)- i-1 )))

         Convert.ToDouble ((mod97Small elem))* Math.Pow(modPowTen, Convert.ToDouble ((splitedList.Length)- i-1 )))    

   

    //4). Compose the results - accumulate the reminders

    let accReminders (flist:float list)=

       flist

       |>List.fold_left (fun acc (ll:float)->

         //printfn "ac %A %A" ll (mod97Small(Convert.ToInt64(ll)))

         acc+ Convert.ToDouble (mod97Small(Convert.ToInt64(ll)))) 0.0 

    let tryChoiceExc chunkLen  task =

        try

           if  chunkLen<2 || chunkLen>15 then

              failwith  "Please pass a number from 2-15"

           else Choice2_1 task

        with 

          | Microsoft.FSharp.Core.Failure (ex) as  err ->   Choice2_2 err

 

    //5). Compact form of the function in one line of code

    let mod97 chunkLen inputString =

       (98L- (inputString

       |>splitInChunks chunkLen

       |>mod97List chunkLen

       |>accReminders

       |> Convert.ToInt64

       |>mod97Small)

       |>Convert.ToString).PadLeft (2,'0')

       |>tryChoiceExc chunkLen

  

    let fromChoice (res:Choice<'a,Exception>)=

       match res with

        | Choice2_1 u -> printfn "Modulo 97 check digits: %A" u; Some u

        | Choice2_2 exn -> printfn "Error: %A" exn.Message ; None

 

    //6). Example

    (mod97 14 "123456789012345678901234567890" )|>fromChoice

 

Conclusion

We calculate the check digits modulo 97 for a large number by decomposing the large number into smaller numbers and by calculating theirs modulo 97. Using the map and reduce style we reduce the list of remainders into a value that is congruent modulo 97 with the original number. We can make this calculation using a single function. The calculations of particular parts (small numbers) are independent.  

We can run in parallel the functions in 2), 3) and 4).

For extremely large numbers the value of the function accRemainders (4) can be larger then 15 digits. In that case, we can decompose the result and repeat the procedure in 1), 2), 3) and 4).