# IsZero or SameValue

## Recommended Posts

15 hours ago, Anders Melander said:

So how would you compare floats with tolerance?

```
function BinarySearch(const List: TArray<Double>; Value: Double; Tolerance: Double): integer;
begin
var Lo := 0;
var Hi := High(List);
while (Lo ≤ Hi) do
begin
var Mid := (Lo + Hi) div 2;
if SameValue(List[Mid], Value, Tolerance) then
exit(Mid);
if List[Mid] < Value then
Lo := Mid+1
else
Hi := Mid-1;
end;
Result := -1;
end;```

Yikes!! Even leaving aside Stefan's point, functions that use a relative tolerance, relative to the comparands, mean that your comparisons depend on the path through the array. I think it very likely that inserting some extra values away from the target could lead to a different value being found.

There certainly are situations where comparing to tolerance is useful. However they need to be considered in detail and the tolerance choice and surrounding algorithm must be carefully thought out. A one size fits all function like this is not useful in my experience.

Furthermore, I would say that a lot of times I have seen tolerance checking, it comes from a misunderstanding of floating point representations. Tolerances get used when they aren't needed.

Sometimes it's not entirely the programmer to blame. For instance delphi's string conversion functions are broken because you can't rely on a round trip from float to string and back to float. So if you save as json or YAML or ini, say, then you don't get back the original value when you load from text. This is intolerable for a serious floating point developer. And it often drives people to compare to tolerance. My solution is to use conversion libraries that work correctly. Emba know about the problem but unless I am mistaken still have not fixed the problem.

Comparing with tolerance is the only way of comparing floats isn't it?

I wrote a little test

```procedure RunCompare(Epsilon: Double);
const
Base: Double = 0.1;
var
i: Integer;
res: Double;
begin
res := 0;
for i := 0 to MaxInt do
begin
if CompareValue(res, Base*i, Epsilon) <> EqualsValue then
begin
WriteLn( Format('Epsilon is %.17f: deviation at step %d', [Epsilon, i]) );
Exit;
end;
res := res + Base;
end;
end;

procedure TestCompare;
var
i: Integer;
EpsilonDiv: Int64;
begin
RunCompare(0);
EpsilonDiv := Trunc(10E16);
for i := 0 to 16 do
begin
RunCompare(1/EpsilonDiv);
EpsilonDiv := EpsilonDiv div 10;
end;
end;```

it outputs

```Epsilon is 0,00000000000000000: deviation at step 8
Epsilon is 0,00000000000000001: deviation at step 3
Epsilon is 0,00000000000000010: deviation at step 8
Epsilon is 0,00000000000000100: deviation at step 29
Epsilon is 0,00000000000001000: deviation at step 73
Epsilon is 0,00000000000010000: deviation at step 262
Epsilon is 0,00000000000100000: deviation at step 928
Epsilon is 0,00000000001000000: deviation at step 2512
Epsilon is 0,00000000010000000: deviation at step 7415
Epsilon is 0,00000000100000000: deviation at step 23039
Epsilon is 0,00000001000000000: deviation at step 75679
Epsilon is 0,00000010000000000: deviation at step 261471
Epsilon is 0,00000100000000000: deviation at step 942813
Epsilon is 0,00001000000000000: deviation at step 2489005
Epsilon is 0,00010000000000000: deviation at step 7379011
Epsilon is 0,00100000000000000: deviation at step 23073610
Epsilon is 0,01000000000000000: deviation at step 76188297
Epsilon is 0,10000000000000001: deviation at step 264487878```

0 is the value auto-calculated from both operands. As you can see, this epsilon starts to fail already after 8th step. So it seems one should provide explicit sane Epsilon for each Same|CompareValue call to be sure the desired result is achieved

Edited by Fr0sT.Brutal
• 1

1 hour ago, Fr0sT.Brutal said:

Comparing with tolerance is the only way of comparing floats isn't it?

No

and if... using "FloatToStrF(...), System.SysUtils, line 19060 on RAD11.2 (using SetString internal function)

Quote

implementation

{\$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
LText: string; // just avoid use Memo all time...
LVal : double;
begin
LVal := 0;
//
try
// FormatSettings.DecimalSeparator := ... ',' or '.'
//
for var i: integer := 0 to 1_800_000 do
begin
//LText := LText + ',' + FloatToStr(LVal);  = x.nnnnnn
LText := LText + ',' + FloatToStrF(LVal, TFloatFormat.ffGeneral, 10, 2);  //  = x.nn
//LText := LText + ',' + FloatToStrF(LVal, TFloatFormat.ffGeneral, 10, 2, FormatSettings);
//
LVal := LVal + 0.1;
end;
except
on e: Exception do
showMessage('error' + slinebreak + e.Message);
end;
//
Memo1.Lines.Delimiter     := ',';
Memo1.Lines.DelimitedText := LText.Remove(0, 1);
end;

end.

Edited by programmerdelphi2k