Rounding Errors
When using floating-point
data types such as Single, Double or Extended
you must be careful of what happens with rounding errors. By way
of example, lets assume we are working with a Calculator that
can only display 4 decimal digits:
As you can see rather than obtain 1 we instead have a number that
is close to 1. This problem gets even more involved when we start
to allow for binary - just as a Third cannot be expressed
in a finite number of places using Base 10 (this is what
causes the rounding error above) - neither can a Tenth
be expressed in a finite number of places using Base 2 -
and this is the case for the IEEE standard format used for Single,
Double and Extended.
Hence Equality is something we should avoid when
using floating-point numbers, as something like the following:
var
X, Y: Double;
begin
...
repeat
. . .
until X = Y;
may never terminate.
So we must always avoid comparing two floating-point
values in equality and inequality (since <> is just the "opposite"
of =).
One way I like to handle this is with a Tolerance
parameter. I have a unit called ESBGlobals.pas that stores all my
global data types and global variables/constants that I want. In
this I have:
var
ESBTolerance = 0.00000001;
Of course you could put the declaration into your own maths unit
with the routines below.
Then we can develop routines like the following:
function FloatIsZero (const X: Extended):
Boolean;
// Is X = 0?
begin
Result := abs (X) < ESBTolerance;
end;
function SameFloat (const X, Y: Extended): Boolean;
// Is X = Y?
begin
Result := abs (X Y) < ESBTolerance;
end;
Whilst the comparisons of > and <
then are not as susceptible to problems, you can still encounter
problems depending on how you are using them and hence we can use
Tolerance here as well.
function GreaterFloat (const X, Y: Extended):
Boolean;
// Is X > Y?
begin
Result := X Y > ESBTolerance;
end;
function LesserFloat (const X, Y: Extended):
Boolean;
// Is X < Y?
begin
Result := Y X > ESBTolerance;
end;
You can even let the user adjust the Tolerance within
your application.
The above routines are not good when you have very small numbers,
unless you adjust the tolerance accordingly. Likewise for very large
numbers you could use a Tolerance that is quite large. For example
when dealing with numbers greater than 1,000,000,000 you could use
a tolerance of 100.
Remember: Delphi's TDataTime is really just a Double,
so all Date/Time calculations can suffer from the above.
In practical situations, you need to judge the accuracy of the
data being used and set the Tolerance accordingly.
|