Floating-Point Comparison Routines

From RAD Studio
Jump to: navigation, search

Go Up to Using Floating-Point Routines

There are several not obvious problems with comparing floating-point numbers.

Floating-Point Comparison Problems

One floating-point comparison problem is that what you see is not exactly how your numbers are represented in memory. You can have X := 99.9999964 and Y "= 100.000003 numbers, which both are shown as 100 but they are not equal:

 uses
   System.SysUtils;
 var X, Y: Single;
 begin
   X := 99.9999964;
   Y := 100.000003;
   Writeln('X =', FloatToStr(X));
   Writeln('Y =', FloatToStr(Y));   ReadLn;
 end.

The program outputs:

X = 100
Y = 100

However if you try to compare X and Y they are not equal. An opposite example is:

 var X:Single;
 begin
   X:=0.1;
   Writeln('X =', FloatToStr(X));
 end.

The program outputs:

X = 0.100000001490116

Anther problem roots to finite representation of floating-point numbers. Let us compare a floating-point number with 'itself':

 var X:Single;
 begin
  X:=0.1;
  if X=0.1 then
   Writeln('Equal')
  else
   Writeln('Not equal');
  ReadLn;
 end.

The program outputs:

Not equal

Why? To understand this, remember that the exact decimal 0.1 number has the binary representation as the infinite recurrent fraction 0.0(0011). Single precision X keeps only 23 bits for mantissa. Delphi executes all floating-point operations using Extended precision. When Delphi converts a Single precision X to the Extended representation having 63 bits mantissa, the program simply initialize all extra bits with zeros (not with the actual bits of 0.0(0011)). Read Finite Precision Implications for more information.

Now let us compare 0.1 in Single and Double precisions:

 var
    X:Single;
    Y:Double;
 begin
  X:=0.1;
  Y:=0.1;
  if X=Y then
   Writeln('Equal')
  else
   Writeln('Not equal');
  ReadLn;
 end.

The program outputs:

Not equal

Again Single and Double representations of 0.1 keep different numbers of mantissa bits.

Comparison Routines

The solution providing correct comparison of floating-point numbers is using of some small Epsilon margin. If the difference of the two floating-point numbers lies within the Epsilon margin, then they are estimated as equal.

The System.Math unit provides the CompareValue, SameValue, and IsZero functions which handle floating-point comparisons. These functions have declarations like the following:

 function CompareValue(const A, B: Extended; Epsilon: Extended): TValueRelationship;
 function SameValue(const A, B: Extended; Epsilon: Extended): Boolean;
 function IsZero(const A: Extended; Epsilon: Extended): Boolean;

Here A and B are floating-point numbers to compare and Epsilon is the small margin by which A and B can differ and still be considered the same value.

If the specified Epsilon = 0, then some reasonable default value is used implicitly. For example, the Extended version of SameValue uses the default value:

Epsilon = Max(Min(Abs(A), Abs(B)) * 1E-16, 1E-16)

See Also