Reputation: 283
I am having a problem calculating a simple arithmetic equation using double precision variables.
I have a component that has a property Value which is of double precision, and I am setting this property to 100.
Then I am doing a simple subtraction to check if this value is really 100:
var
check: double;
begin
check:= 100 - MyComponent.Value
showmessage(floattostr(check));
end;
The problem is that I don't get zero, I get -1.4210854715202E-14, which is a problem because I the program checks if this result is exactly zero
any idea how to solve it?
Upvotes: 1
Views: 5057
Reputation: 24523
When using floating point numbers, you should never perform exact compares; always use a small Epsilon
value as explained in The Floating-Point Guide - What Every Programmer Should Know.
Note: I deliberately state this in an absolute sense. Of course there are exceptions, but in practice these are usually exceptional. Of course you might be in the situation that your problem domain is exceptional, and warrants exact compares. In the vast majority of code that I have maintained, this is not the case.
The Math
unit (it has been with Delphi for a long long time) contains the below functions that handle Epsilon
values for you.
When you pass an Epsilon
of zero (i.e. 0.0
) or no value at all to the functions below, they will estimate an reasonable value using these constants.
Note:
The appropriate value of
Epsilon
you want to use depends on the calculations you use: sometimes inaccuracies can accumulate to much larger values than these constants.
const
FuzzFactor = 1000;
SingleResolution = 1E-7 * FuzzFactor;
DoubleResolution = 1E-15 * FuzzFactor;
{$IFDEF EXTENDEDIS10BYTES}
ExtendedResolution = 1E-19 * FuzzFactor;
{$ELSE EXTENDEDIS10BYTES}
ExtendedResolution = DoubleResolution;
{$ENDIF EXTENDEDIS10BYTES}
The functions:
function CompareValue(const A, B: Extended; Epsilon: Extended = 0): TValueRelationship; overload;
function CompareValue(const A, B: Double; Epsilon: Double = 0): TValueRelationship; overload;
function CompareValue(const A, B: Single; Epsilon: Single = 0): TValueRelationship; overload;
function SameValue(const A, B: Extended; Epsilon: Extended = 0): Boolean; overload;
function SameValue(const A, B: Double; Epsilon: Double = 0): Boolean; overload;
function SameValue(const A, B: Single; Epsilon: Single = 0): Boolean; overload;
function IsZero(const A: Extended; Epsilon: Extended = 0): Boolean; overload;
function IsZero(const A: Double; Epsilon: Double = 0): Boolean; overload;
function IsZero(const A: Single; Epsilon: Single = 0): Boolean; overload;
Upvotes: 4
Reputation: 821
Suggest you never check for zero by simple subtraction when one of the numbers is a float. Instead, use the IsZero function with whatever precision you want (e.g., 0.00001) for the Epsilon parameter.
procedure CheckFor100(MyPrecision);
begin
if IsZero(100 - MyComponent.Value, MyPrecision) then
ShowMessage('MyComponent value was 100')
else
ShowMessage('MyComponent value was not 100');
end;
Alternatively, you may wish to consider the SameValue function. I think both IsZero and SameValue are in the Math unit.
Upvotes: 3
Reputation: 613481
Although you claim otherwise, the value returned by MyComponent.Value
is clearly not exactly equal to 100
. If it was, then 100 - MyComponent.Value
would be exactly equal to 0
. We can say that because 100
is exactly representable in binary floating point.
It's easy enough to see that 100.0 - 100.0 = 0.0
.
var
x, y: Double;
....
x := 100.0;
y := 100.0;
Assert(x-y=0.0);
You will find, in your scenario, that
MyComponent.Value = 100.0
evaluates as False
.
In general, it can always be a dangerous thing to try to compare floating point values exactly. Particularly if the values are the result of arithmetic operations, then the inherent imprecision of floating point operations will mean that exact comparison will often not give the results that you expect.
I hypothesise that MyComponent.Value
actually performs arithmetic rather than, as you claim, returning 100.0
.
Sometimes the best way to check for equality with floating point values is to check for approximate equality. For example, abs(x-y)<tol
where tol
is some small number. The difficulty with that is that it can be hard to come up with a robust choice of tol
.
Exactly how you should implement this test is hard to say without knowing more detail.
Upvotes: 5