Jump to content
JeanCremers

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. 

Share this post


Link to post

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
  • Sad 1

Share this post


Link to post

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.

 

image.png.78b618800d4d78b33535e3368f8a84ea.png

 

Edited by programmerdelphi2k

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×