MIAMEX MX_ROUND

A new tool, clumsily called "The Floating Point Variable Rounding Factor", has been introduced in the ongoing battle between floating point imprecision and expected accounting precision.

The feature consists of the ability to declare a rounding factor for all floating point variables, that is applied whenever the variable is accessed. As an example, if your application stores all monetary figures in pennies (always dividing by 100 to convert to dollars and cents), and has no interest in fractions of pennies, then you could set the rounding factor to 1, which means that all floating point variables would be rounding to the nearest integer whenever they were accessed. On the other hand, if you store some of your amounts in dollars and cents, then you might want to set your rounding factor to .01, meaning that all variables would be rounded to the nearest .01 whenever accessed. The default factor is 0, which means that no rounding is applied. You can set the factor to anything, not just numbers 1 or smaller. For example, a financial statement program for a Fortune 100 company might set it to 1000, which would round every variable to the nearest 1000. Or a point-of-sale program in a country whose monetary system is in transition might set it to the denomination of the smallest common current unit (perhaps 25). The important thing to remember is that the factor, once set, applies to every floating point variable accessed from memory, until it is reset to some other factor. It is not automatically reset at the end of each program. However, it does not apply to LIT programs.

The concept is somewhat similar to the OPTIONS=FPROUND, but obviously much more drastic. FPROUND operates on expressions rather than variables, generally adding or subtracting a "fudge factor" of .000005 or less, depending on the expression, in order to coerce numbers like 1.9999999997 or 2.00000000001 to "stick" to the nearest integer. (But as you can see from the number of patches to the way it works over the years, the technique is difficult to manage, and sometimes results in making the "problem" seem worse rather than better. The new variable rounding feature is much more blunt, but also more uniform and easy to understand, and thus might actually serve as a magic bullet for some applications which have not conquered the floating point precision problem through other means (such as manually rounding the results of calculations before storing them).

To query or set the rounding factor, use:

xcall MIAMEX, MX_ROUND, opcode, factor

where opcode is 0 to retrieve the current setting and 1 to update it. Factor is the rounding factor.

The following test program will be useful for testing and experimenting with the feature.

SIGNIFICANCE 11

map1 F1,F

map1 F2,F

map1 FPROUNDFACTOR,F

map1 A$,S,1

 

xcall MIAMEX, MX_ROUND, 0,FPROUNDFACTOR

? "FP Rounding factor is ";FPROUNDFACTOR

input "Change it? ",A$

if ucs(A$)="Y" then &

    input "Enter new factor (0,1,.1,.01 etc): ",FPROUNDFACTOR :&

    xcall MIAMEX,MX_ROUND,1,FPROUNDFACTOR

 

    LOOP:

        input "Enter a value: ",F1

        ? "Value = ";F1

        F2 = F1 / 3

        ? "Value/3 = ";F2;" (";F1/3;")"

        F2 = F1 * .01

        ? "Value*.01 = ";F2;" (";F1*.01;")"

        goto LOOP

 

The program illustrates that the variable rounding factor only affects the variables F1 and F2, but does not show up when an expression is printed directly (such as F1/3).

At the risk of running this topic into the ground, it is worth saying a few words about why the problem exists, why it seems worse (to some people) under A-Shell than under AMOS, and why it sometimes surfaces in programs that were "working" for many years. First, the problem is inherent when floating point values are represented in the computer with a finite number of bits. Even a simple calculation like 7/2 might result in 3.499999999998 rather than the 3.5 you were expecting. In this example, with the 48 bit floating point format used by the Alpha, the effective number of significant digits is about 11, and thus 3.499999999998 would be internally rounded to 3.5 and you wouldn't notice the problem. But with the 64 bit IEEE format used by virtually every other machine outside of the Alpha, you get about 16 significant decimal digits and thus 3.499999999998 would remain as is. As long as you printed it with a mask, e.g. "######.##", it would always appear as "3.50" but if you didn't round it before storing it, subsequent use of the variable would carry the imprecision into other calculations. For example, if you added up several hundred thousand of these kinds of values, each with a slight rounding error, the cumulative error could easily grow to the point that it made a difference in the pennies column.

The reason why the problem seems worse under A-Shell is simply due to the seemingly paradoxical fact that the more bits of precision, you have in the internal floating point calculations, the more likely your result will be off by a significant digit. For example, if the floating point format could only represent about 2 decimal digits, then the result of every calculation would effectively be rounded to the nearest hundredth. (Which is effectively the "capability" that the new variable rounding factor feature provides.)

The reason why the problem sometimes suddenly appears in programs that have been working for years, is that as businesses grow and computers get faster, there is a tendency to deal with greater amounts of data and for the computations to increase in complexity. For example, in 1980, a company might have had a payroll of 100 and only a couple of simple deduction types. Now, through mergers and other growth, in both the economy and the tax code, it might have 1000 employees and several more deductions. As the number of calculations increases, the internal rounding errors can accumulate to the point that one day, a paycheck is off by a penny. And suddenly there is a panic. Or, in a more insidious variation of this problem, results from calculations may be stored in files without the rounding errors being first corrected. (For example, a product history file might contain a record of the dollar amount of each sale of that product. Assuming the dollar sale amount was the result of a calculation (qty times price, perhaps with a discount), it might well contain a slight rounding error. As in the example above with 7/2 = 3.499999999998, it may take a long time before this your history file grows big enough for the error to become visible. And since the errors will, on average, split evenly on both the low and high sides, they might tend to cancel themselves out, on average. But one day, you select the right combination of dates or other reporting factors and voila, the report is off by a penny.

(One way that careful programmers have dealt with this problem is to always use a rounding mask before storing a result, for example, RESULT = <calculation> USING "#########.##". But if you haven't done that all along, you might find it a bit daunting to start now.)

Going back and fixing a problem like the one just described may not be easy, especially if the application now contains thousands of programs. For this kind of situation, the variable rounding factor might provide a near miracle solution. Even if you couldn't be sure whether you might have some variables that are supposed to contain tenths, or hundredths, or thousandths, you can still benefit by setting the rounding factor to, say, .00001 or even .000000001. Since the rounding errors tend to start out in the vicinity of .00000000000001, this would be more than enough to eliminate them as they occur.

The main thing to watch out for is situations where you really do want to have a precise representation of factions like 1/3. For example, if you wanted to apply a discount of 1/3 to some vary large number, then you probably wouldn't want your 1/3 to be rounded to .33333 (instead of .33333333333). After all, 1/3 of a million is 333,333.33 but if your representation of 1/3 was merely .33333, then the result would be 333,330.00. Note, however, that the rounding factor is only applied to variables, so it would not stop you from getting a precise result for 1000000/3; it would only cause a problem if you set F=1/3 and then multiplied 1000000 by F.

The final word then, is that if you fear that you have this problem and want to try the new variable rounding factor, start small (perhaps .000000001) and work up if that isn't sufficient. But it goes without saying that this kind of technique should not be applied without some diligent analysis and testing of your application.