Search code examples
cobolmainframe

How do I do the MOD 97 calculation


I'm writing a program that converts international account numbers into the standard IBAN structure.

I have so far managed to write a program that checks the input for validity, strings said input together without embedded spaces or zeroes, converts the alphabetic characters into numbers corresponding with their position in the alphabet and creates a string with those numbers.

Now all I have to do is calculate the Mod 97 on the number and subtract the remainder from 98. If the result is a single digit then I have to insert a leading zero.

What I have right now is this:

Note: I used a pointer to get the numbers into the string before this step, so the pointer (after subtracting 1 from it) contains the total length of the string.

WORKING STORAGE SECTION.
01 WORK-FIELDS.
   05  HELP FIELDS.
       10  POINTER                PIC S9(04)           COMP.
       10  DIGITIZED-STRING       PIC X(66).
       10  STRING-FOR-CALCULATION PIC 9(31).

   05  IBAN.
       10 COUNTRY-CODE            PIC X(02).
       10 CHECK-DIGITS            PIC 9(02).
       10 BANK-CODE               PIC X(10).
       10 BRANCH-CODE             PIC X(10).
       10 ACCOUNT-NUMBER          PIC X(28).
       10 OUTPUT-IBAN             PIC X(34). 


PROCEDURE DIVISION.

SUBTRACT 1 FROM POINTER
           STRING DIGITIZED-STRING(1:POINTER)
                  DELIMITED BY SIZE
                  INTO STRING-FOR-CALCULATION
           EVALUATE TRUE
               WHEN POINTER < 32
                    COMPUTE IBAN-CHECK-DIGITS = 98
                    - FUNCTION MOD(STRING-FOR-CALCULATION, 97)
           END-EVALUATE
           .

I have 2 problems:

  1. Even with the ARITH(EXTEND) option in the compiler, COBOL can't handle numeric fields larger than 31 digits, which my program will be dealing with. There is a reason my DIGITIZED-STRING field has a length of 66. When I compile my program, I get this message:

"Truncation of high-order digit positions may occur due to precision of intermediate results exceeding 31 digits."

  1. When the length of my STRING-FOR-CALCULATION field (as seen above) is longer than the content I move to it from the DIGITIZED-STRING, the gap is filled by trailing zeroes. This affects the calculation. When I manually adjust the STRING-FOR-CALCULATION field to be exactly as long as the content of the DIGITIZED-STRING, the calculation goes well.

I just don't know how to make sure it works when I'm dealing with shorter and longer numbers, which I have to split up in parts.

Additional info:

I'm using this document as a guide: www.bpfi.ie/wp-content/uploads/2014/10/MOD-97-Final-May-2013.pdf

But I have no clue how to do this in COBOL.

Also, for those who have kindly read and answered my previous questions, the reason these fields aren't National is because my project manager changed his mind about it. Now all I have to do is use a MOVE FUNCTION NATIONAL-OF to move the IBAN-OUTPUT field to the copybook.

UPDATE

I'm using a COBOL/390 COMPILER.

Below is the code I ended up using:

WORKING-STORAGE SECTION.

01   DIGITIZED-STRING                  PIC X(66).
01   POINTER                           PIC S9(04)           COMP.

01   TEST-FIELDS.
            05  VERY-LONG-NUMBER.
                10  VLN-FIRST-PART     PIC 9(11).
                10  VLN-SECOND-PART    PIC 9(11).
                10  VLN-THIRD-PART     PIC 9(11).
                10  VLN-FOURTH-PART    PIC 9(11).
                10  VLN-FIFTH-PART     PIC 9(11).
                10  VLN-SIXTH-PART     PIC 9(11).
            05  EXPANDED-DIVIDEND                    PIC 9(13).
            05  FILLER
                REDEFINES EXPANDED-DIVIDEND.
                10  ED-REMAINDER                     PIC 99.
                10  ED-PART                          PIC 9(11).
            05  IRRELEVANT-ANSWER                    PIC 9(12).



PROCEDURE DIVISION.
MOVE SPACES TO TEST-FIELDS
           MOVE ZEROES TO TEST-FLDS
           MOVE DIGITIZED-STRING(1:POINTER)
                TO VERY-LONG-NUMBER(66 - POINTER + 1:POINTER)

               MOVE ZERO                    TO ED-REMAINDER
               MOVE VLN-FIRST-PART          TO ED-PART
               DIVIDE EXPANDED-DIVIDEND     BY 97
                 GIVING                     IRRELEVANT-ANSWER
                 REMAINDER                  ED-REMAINDER
               MOVE VLN-SECOND-PART         TO ED-PART
               DIVIDE EXPANDED-DIVIDEND     BY 97
                 GIVING                       IRRELEVANT-ANSWER
                 REMAINDER                    ED-REMAINDER
               MOVE VLN-THIRD-PART          TO ED-PART
               DIVIDE EXPANDED-DIVIDEND     BY 97
                 GIVING                       IRRELEVANT-ANSWER
                 REMAINDER                    ED-REMAINDER
               MOVE VLN-FOURTH-PART         TO ED-PART
               DIVIDE EXPANDED-DIVIDEND     BY 97
                 GIVING                       IRRELEVANT-ANSWER
                 REMAINDER                    ED-REMAINDER
               MOVE VLN-FIFTH-PART         TO ED-PART
               DIVIDE EXPANDED-DIVIDEND     BY 97
                 GIVING                       IRRELEVANT-ANSWER
                 REMAINDER                    ED-REMAINDER
               MOVE VLN-SIXTH-PART         TO ED-PART
               DIVIDE EXPANDED-DIVIDEND     BY 97
                 GIVING                       IRRELEVANT-ANSWER
                 REMAINDER                    ED-REMAINDER

           COMPUTE CHECK-DIGITS = 98
                   - ED-REMAINDER
           .

Solution

  • You do it like "long division".

    1234 divided by 97 =
    123 divided by 97, which gives x (doesn't matter for you) remainder 26
    264 divided by 97, gives x, remainder 70
    

    70 is the mod 97 of 1234.

    Here's an example program:

       ID DIVISION. 
       PROGRAM-ID. STAB22. 
       DATA DIVISION. 
       WORKING-STORAGE SECTION. 
       01  VERY-LONG-NUMBER                     PIC 9(8). 
       01  FILLER 
           REDEFINES VERY-LONG-NUMBER. 
           05  VLN-FIRST-PART                   PIC 9(4). 
           05  VLN-SECOND-PART                  PIC 9(4). 
       01  EXPANDED-DIVIDEND                    PIC 9(6). 
       01  FILLER 
           REDEFINES EXPANDED-DIVIDEND. 
           05  ED-REMAINDER                     PIC 99. 
           05  ED-PART                          PIC 9(4). 
       01  IRRELEVANT-ANSWER                    PIC 9(5). 
       01  VALUE-FOR-MOD-97      PACKED-DECIMAL PIC 99 VALUE 97.
       PROCEDURE DIVISION. 
           MOVE 1234                    TO VERY-LONG-NUMBER 
           MOVE ZERO                    TO ED-REMAINDER 
           MOVE VLN-FIRST-PART          TO ED-PART 
           DIVIDE EXPANDED-DIVIDEND     BY VALUE-FOR-MOD-97 
             GIVING                     IRRELEVANT-ANSWER 
             REMAINDER                  ED-REMAINDER 
           MOVE VLN-SECOND-PART         TO ED-PART 
           DIVIDE EXPANDED-DIVIDEND     BY VALUE-FOR-MOD-97 
             GIVING                     IRRELEVANT-ANSWER 
             REMAINDER                  ED-REMAINDER 
           DISPLAY ED-REMAINDER 
           MOVE 12345678                TO VERY-LONG-NUMBER 
           MOVE ZERO                    TO ED-REMAINDER 
           MOVE VLN-FIRST-PART          TO ED-PART 
           DIVIDE EXPANDED-DIVIDEND     BY VALUE-FOR-MOD-97 
             GIVING                     IRRELEVANT-ANSWER 
             REMAINDER                  ED-REMAINDER 
           MOVE VLN-SECOND-PART         TO ED-PART 
           DIVIDE EXPANDED-DIVIDEND     BY VALUE-FOR-MOD-97 
             GIVING                     IRRELEVANT-ANSWER 
             REMAINDER                  ED-REMAINDER 
           DISPLAY ED-REMAINDER 
           GOBACK 
           . 
    

    That gives 70 and 03 as the results.

    You extrapolate. I'd suggest you have six parts of 11 digits, making a 13-digit dividend each time. That will be more efficient than trying to use longer numbers with less code, using compiler option ARITH(EXTEND).