[comp.sys.mac.programmer] Floating Point accuracy in Think C

francis@csli.Stanford.EDU (Dave Francis) (11/29/90)

Hello netters!

I have a roundoff problem when using floating point numbers in Think C 4.0.2.

I've written a function which takes a floating point number and returns
a string that can be used for displaying with QuickDraw.

The problem is that when I pass the value to be converted Think C changes
it to a less accurate number so when I generate the string I don't get exactly
what was passed in. Here's a example:

The number input is 2.314 but inside the routine it becomes 2.3139996765 or
something like that (I think becuase Think C passes floating point as
extended). Anyway when I generate the string the return is "2.313" if the
precision is set to 3 (the function also takes a precision parameter)
It gets worse if I pass a value which has less significant digits than the
precision. So the value 3.2 passed in with a precision parameter of 3
in some cases gets returned as "3.199", which is unexceptable for display
purposes if the user e ntered 3.2.

Is there a way to stop this loss of accuracy when passing floating point
values? Why is there  a loss of accuracy anyway? What should I do? Are
there any rounding functions out there or better float->string converters?


Help!!

Dave Francis  Sapphire Design Systems

252u3129@fergvax.unl.edu (Mike Gleason) (11/29/90)

I solved a similar problem by changing my floating point variables from
pascal REALs to EXTENDEDs.  Try changing any floats or doubles to long doubles.


-- mike gleason, unemployed student looking for employment.
-- 252u3129@fergvax.unl.edu
-- Go Georgia Tech!

ech@cbnewsk.att.com (ned.horvath) (11/30/90)

From article <16594@csli.Stanford.EDU>, by francis@csli.Stanford.EDU (Dave Francis):
> I have a roundoff problem when using floating point numbers in Think C
> 4.0.2...

> Is there a way to stop this loss of accuracy when passing floating point
> values? Why is there  a loss of accuracy anyway? What should I do? Are
> there any rounding functions out there or better float->string converters?

Floating point numbers always experience SOME loss of accuracy: consider
expressing 1/3 as a decimal number: the sequence of threes is periodic, but
infinite, and so there's always a bit of loss.  It ain't the Mac,
and it ain't SANE, and it ain't a ThC problem.

This can't turn into a numerical methods lecture (course, career, ...) but
suffice to say that some algorithms are much more sensitive ("unstable") to
rounding error than others, so depending on how you manipulate the numbers,
you can get good or bad results.  Look in the (book) library under numerical
analysis: authors like Foreman Acton and Webb Miller will help you find good
(i.e. less sensitive) algorithms.

At display time, use an old APL trick, the "fuzz."  Decide what level of
accuracy is important to you, then add a bit to your result (or subtract a bit
if the value is negative) to allow rounding in the right direction.  If you
plan to display answers like 99.999, add .0005 before using sprintf.

If you're dealing with numbers with limited accuracy (like currency, interest
rates correct to 3 places, etc.) then you might consider avoiding floating
point altogether, using a fixed-point scheme, i.e. holding your numbers as
(long?) integers with an implicit scale factor.  Not only will you have better
accuracy, you'll have much better performance.  The Mac Toolbox Utilities
support a particular flavor of fixed-point (Fixed) -- see IM 1-467 -- with the
binary-point in the middle of a 32-bit word.  To display one of these using
sprintf, and three digits to the right of the decimal point:

    Fixed val;
...
    sprintf (buffer, "%d.%d",
        (int) (val >> 16),      /* integer part */
        FixRound (labs(val) * 1000) % 1000     /* decimal part */
    );

Using Fixed will still result in rounding error when you use FixRatio to
convert decimal to Fixed. An alternative approach, if you are dealing with a
fixed number of decimal digits, e.g. currency, is to hold an amount of money
as an integer in terms of thousandths-of-a-dollar.  Then, when you go to
display it, use something like

    typedef long Bucks;
    Bucks val;

    sprintf (buffer, "$%d.%d",
        val/1000,       /* dollars */
        val%1000/10);   /* cents */

Now, when you want to add such numbers, just do it.  To multiply, remember
that you have to renormalize: in this example, multiplying two of these
numbers gives us millionths of a dollar, so we have to post-divide by 1000 to
get back to thousandths:
    Bucks a, b;
...
    a = (a*b)/1000;
or, to take a percentage, say 8.5 percent:
    b = (a*85)/1000;    /* 8.5% == .085 == 85/1000 */

The bottom line -- and it's not for everyone! -- is that if you think about
your problem a bit, and work a bit harder, you can get high accuracy AND high
speed by avoiding floating point.  If the round-up-before-display works for
you, and the performance is adequate for your target audience, then just
ignore all this stuff.

At the risk of being tarred and feathered, COBOL handles this kind of thing
really well...

=Ned Horvath=
ehorvath@attmail.com

Disclaimer: I haven't programmed COBOL since '71.  But I'd use it if I had the
right kind of problem...