Floating-Point Comparison Routines
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)