Felipe
Felipe

Reputation: 283

Delphi wrong double precision calculation

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

Answers (3)

Jeroen Wiert Pluimers
Jeroen Wiert Pluimers

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

Max Williams
Max Williams

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

David Heffernan
David Heffernan

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

Related Questions