Search code examples
cobol

Why does my COBOL working storage variable have trailing zeroes?


I'm building a COBOL program to calculate the average of up to 15 integers. The execution displays a number that is far bigger than intended with a lot of trailing zeroes. Here is the relevant code:

       Data Division.
       Working-Storage Section.
       01 WS-COUNTER          PIC 9(10).
       01 WS-INPUT-TOTAL      PIC 9(10).
       01 WS-NEXT-INPUT       PIC X(8).
       01 WS-CONVERTED-INPUT  PIC 9(8).
       01 WS-AVG              PIC 9(8)V99.

       Procedure Division.
       PROG.
           PERFORM INIT-PARA
           PERFORM ADD-PARA UNTIL WS-COUNTER = 15 OR WS-NEXT-INPUT = 'q'
           PERFORM AVG-PARA
           PERFORM END-PARA.
       INIT-PARA.
           DISPLAY 'This program calculates the average of inputs.'.
           MOVE ZERO TO WS-COUNTER
           MOVE ZERO TO WS-INPUT-TOTAL
           MOVE ZERO TO WS-AVG.
       ADD-PARA.
           DISPLAY 'Enter an integer or type q to quit: '
           ACCEPT WS-NEXT-INPUT
           IF WS-NEXT-INPUT NOT = 'q'
               MOVE WS-NEXT-INPUT TO WS-CONVERTED-INPUT
               ADD WS-CONVERTED-INPUT TO WS-INPUT-TOTAL
               ADD 1 TO WS-COUNTER
           END-IF.
       AVG-PARA.
           IF WS-COUNTER > 1
               DIVIDE WS-INPUT-TOTAL BY WS-COUNTER GIVING WS-AVG
               DISPLAY 'Your average is ' WS-AVG '.' WS-NEXT-INPUT
           END-IF.

The reason I put WS-NEXT-INPUT as alphanumeric and move it to a numeric WS-CONVERTED-INPUT if the IF condition is satisfied is because I want it to be able to take "q" to break the UNTIL loop, but after the condition is satisfied, I want a numeric variable for the arithmetical statements. Here's what it looks like with the numbers 10 and 15 as inputs:

10is program calculates the average of inputs.
Enter an integer or type q to quit:
15
Enter an integer or type q to quit:
q
Your average is 1250000000.

The console is a bit buggy so it forces me to input the 10 in that top left corner most of the time. Don't worry about that.

You see my problem in that execution. The result is supposed to be 00000012.50 instead of 1250000000. I tried inserting a few of my other variables into that display statement and they're all basically as they should be except for WS-INPUT-TOTAL which with that combination of numbers ends up being 0025000000 instead of 0000000025 as I would have expected. Why are these digits being stored in such a weird and unexpected way?


Solution

  • You have that strange output because of undefined behavior - computing with spaces.

    The MOVE you present has the exact same USAGE and same size - it will commonly be taken over "as is", it normally does not convert the trailing spaces by some magic, so WS-CONVERTED-INPUT ends up with 10 . As the standard says for the move:

    De-editing takes place only when the sending operand is a numeric-edited data item and the receiving item is a numeric or a numeric-edited data item.

    and if it would be an edited field then it still should raise an exception on the MOVE:

    When a numeric-edited data item is the sending operand of a de-editing MOVE statement and the content of that data item is not a possible result for any editing operation in that data item, the result of the MOVE operation is undefined and an EC-DATA-INCOMPATIBLE exception condition is set to exist.

    When computing with spaces you commonly would raise a fatal error, but it seems your compile does not have that activated (and because you didn't share your compile command or even your compiler, we can't help with that).

    Different COBOL dialects often use (partial only when checks are not activated which would lead to an abort) zero for invalid data, at least for spaces (but they can use everything. This will then lead to WS-CONVERTED-INPUT "seen as" 10000000 - so your computation will then include those big numbers.

    So your program should work if you enter the necessary amount of leading zeroes on input.

    General:

    • "never trust input data - validate" (and error or convert as necessary)
    • at least if something looks suspicious - activate all runtime checks available, re-try.

    Solution - Do an explicit conversion:

    MOVE FUNCTION NUMVAL(WS-NEXT-INPUT) TO WS-CONVERTED-INPUT, this will strip surrounding spaces and then convert from left to right until invalid data is found. A good coder would also check up-front using FUNCTION TEST-NUMVAL, otherwise you compute with zero if someone enters "TWENTY".