Flag that overflow!
Those of you who intend to do serious number crunching often need to trace calculations throughout your programs, usually for comparison with hand calculations of the problems. In Forth, this is difficult. There does not appear to be an easy way to print out calculations for validation. Further, if you have "over-flow" you cannot even tell when and where it occurred. To improve this situation, I developed a couple of techniques that I will share with you here.
I am designing a game that requires speed, and I elected to program in Forth. The program contains more than 60 equations, most of which result in large numbers. As you know, there is a + /- 32768 maximum value in the integer, non-floating-point mode. Using existing Forth words, I scaled the calculations up and down - generally by 1000 - to keep the answers within the integer range. To accomplish this, I used a standard Forth definition, */, the notation for which is:
( n1 n2 n3 -- n )
This definition first uses the 32-bit register for the multiplication of the first two items, and then divides by the third item, leaving the answer as a single word on top of the stack. The value should be less than 32768.
In non-Forth notation, this operatron is:
n = ( n1 * n2 ) / n3
In Forth, the data load-in might look like:
22222 3 100 */
which produces 666.
Well, I was moving right along, crunching through equations, when I discovered that random values exceeded the l6 bit limit of 32768, and overflowed, thereby losing the most significant bits of the answer. This was disastrous. To make matters worse, I couldn't tell where in the sequence the overflow(s) occurred. Though my knowledge of assembly language is sketchy, I was sure I needed to check the overflow bit of the Processor Status register.
Unfortunately, the PS register only shows overflow in addition, and I didn't know enough to break into the machine code so it would print a warning showing when and where an overflow occurred. I wasted a week of evenings thinking about ways to observe overflow.
Finally, there came a glimmer of hope. I was using */, which is a Forth word definition that I could modify. The definition for */ was essentially */MOD, which was:
:*/MOD>R M* R> M/;
I decided to insert a DUP and PRINT after M* and M/ so that by comparing the two side-by-side values (which generally would be different by a scaling factor of 10, 100, 1000, etc.) I could see if the significant figures changed. If so, an overflow was likely. Examples of this would be:
66666 666 (okay,division by 100 probable)
22222 22222 (okay, no scaling)
66666 -1 (overflow, bad scaling chosen)
By tracing through and numbering each */ used on each screen in the program, I was able to identify exactly where overflow had occurred.
I also found a need to turn off the trace facilities, so I created a flag for this purpose. The calculations are in a big loop that prints out the final values once, at the end of the loop. I've injected the ?TERMINAL to break into the calculations at the end of the loop. When normal output shows that an overflow has occurred, I invoke the yellow terminal key, set the trace flag to one (1) with:
and continue to run the program. Now the */ printout shows the suspect values.
To accomplish this, the following */MOD definitions are redefined:
0 VARIABLE TFLAG
:*/MOD >R M* TFLAG @ IF 2DUP D.
R> M/ DUP . CR ELSE
R> M/ ENDIF;
: */ */MOD SWAP DROP ;
Note that the */ did not change from the original definition, but it must be redefined because the */MOD it calls is now a new definition.
Remember, you must first boot your Forth system, then load these new definitions, and then load your program in order for it to use the redefined sequence.
Still, I had occasional troubles. Because my number-crunching problem was so complex, I couldn't do a good hand calculation to check the answers. So I made a complete copy of my Forth disk and proceeded to convert the entire program to floating-point arithmetic. This was not as time consuming as I had feared; it was just a short-term fix to check the equations and their implementation. I still intended to return to integer calculations when I got the calcuiations correct. Having just conquered the */ integer problem, I was led to modify the floating-point operators in a similar way.
As you Forth users know, there are four main floating-point operators: F + , F-, F*, and F/. Again, the plan is to load the original operator definitions, then to insert the modified ones that suit your needs, and then to load the program. In this case, I wanted to see the result of each operation and to compare it with the hand calculations. The technique developed here is simply to perform a FDUP and then a F. each time an operation occurs. This prints out only the flonting-point value, with no identification, so I inserted a ." to print the operator symbol as a clue to the origin of the number. The modified definitions become:
:F+ F+ FDUP CR ." + " F. ;
: F- F- FDUP CR ." - F. ;
: F* F* FDUP CR ." * " F. ;
: F/ F/ FDUP CR ." / " F.;
Then it was relatively easy to line up the symbol identification with each screen, and thereby have numerical values that could be compared directly for each operation to the hand-calculated values. This was progress! Of course, the TFLAG approach, used in the */ modification above, can also be used here to turn the trace on and off.
In my regular work as an engineer, I constantly use computer programs. To get them working properly, I usually trace and inspect the values of each step and compare them to hand calculations. Until now, I had not been able to do that at home. But using these little mods to the standard Forth operators, I am now able to follow the number crunching and have a way to find (most of) my errors. Maybe I'll soon get this monster program checked out and on the market for big bucks. If I don't, at least I will have had the satisfaction of discovering something new for this neat language called Forth.