David Heffernan 2353 Posted November 24, 2022 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
Fr0sT.Brutal 900 Posted November 24, 2022 (edited) 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 November 24, 2022 by Fr0sT.Brutal 1 Share this post Link to post
David Heffernan 2353 Posted November 24, 2022 1 hour ago, Fr0sT.Brutal said: Comparing with tolerance is the only way of comparing floats isn't it? No Share this post Link to post
programmerdelphi2k 237 Posted November 27, 2022 (edited) 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 November 27, 2022 by programmerdelphi2k Share this post Link to post