Lazarus

Free Pascal => Beginners => Topic started by: Vittorio on November 08, 2018, 10:31:56 am

Title: Idiot's question about Frac
Post by: Vittorio on November 08, 2018, 10:31:56 am
Hi there! Could you explain for dummy this example.
I'm trying to extract last digit of 2-digit value and compare it with 4.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i : Integer;
  4. begin
  5.   i := 14;
  6.   Memo1.Lines.Add(FloatToStr(10*Frac(i/10)));
  7.   if 10*Frac(i/10) >= 4 then Memo1.Lines.Add('Test Passed!');
  8. end;

FloatToStr shows "4" but infact this value is lesser than 4 and test is not passed.
But! If i := 15 then 10*Frac(i/10) = 5 and test is passed. Why?
Title: Re: Idiot's question about Frac
Post by: howardpc on November 08, 2018, 10:52:29 am
Why do you use floating point functions for integers?
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i : Integer;
  4.  
  5. begin
  6.   i := 14;
  7.   Memo1.Lines.Add(Format('i = %d, last digit = %d',[i, i mod 10]));
  8.   if (i mod 10) >= 4 then
  9.     Memo1.Lines.Add('Test Passed!')
  10.   else Memo1.Lines.Add('comparison failed');
  11. end;    
  12.  
Title: Re: Idiot's question about Frac
Post by: marcov on November 08, 2018, 11:23:01 am
Not all values can be exactly represented in floating point. Probably the result is slightly rounded (like 1.3999999) and you get that).

Never test floating point for absolute exactness. If you search online you will find a lot of documentation about it. It is a kind of rite of passage for beginning programmers.
Title: Re: Idiot's question about Frac
Post by: Handoko on November 08, 2018, 11:29:56 am
Or use CompareValue. Read more:
https://www.freepascal.org/docs-html/rtl/math/comparevalue.html

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i : Integer;
  4. begin
  5.   i := 14;
  6.   Memo1.Lines.Add(FloatToStr(10*Frac(i/10)));
  7.   if CompareValue(10*Frac(i/10), 4) >= 0 then Memo1.Lines.Add('Test Passed!');
  8. end;
Title: Re: Idiot's question about Frac
Post by: wp on November 08, 2018, 11:50:05 am
Mathematically speaking, there is an infinete count of floating point numbers. But in a computer there is only a finite number of bytes to represent these numbers; therefore, floating point values are normally rounded to the closest value which can be expressed exactly as a floating point value. Note that floating points values are stored in a binary system, therefore values which look to be exact for our eyes (like 1.4) are not exact for the computer at all.

The string conversion routines are highly sophisticated and round off many of the floating point inaccuracies. But when you go to a console program and simply "WriteLn(frac(i/10))" then you'll get "3.99999999999999999978E-0001". This shows immediately that the result of frac(14/10) cannot be expressed exactly as a floating point number. Therefore, when comparing floating point number NEVER use the '=' operator, the function SameValue in unit math accepts a tolerance parameter and is much more reliable:

Code: Pascal  [Select][+][-]
  1. function SameValue(const A, B: Double): Boolean; overload;
  2. function SameValue(const A, B: Double; Epsilon: Double): Boolean; overload;
  3. // more overloads for extended and single

When you don't specify the tolerance parameter like in the first call above, a relative tolerance of 1E-12 is used for double, and 1E-16 for extended and 1E-4 for single. This is ok for most cases. But sometimes, in particular after heavy number crunching using numerical calculations with limited accuracy, a higher value must be specified (note that the Epsilon specified is the absolute tolerance).

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. uses
  4.   math;
  5.  
  6. var
  7.   i: Integer;
  8. begin
  9.   i := 14;
  10.   WriteLn(frac(i/10));
  11.   WriteLn(SameValue(frac(i/10), 0.4));
  12.   WriteLn(SameValue(frac(i/10), 0.4, 1e-9));
  13.   ReadLn;
  14. end.
Title: Re: Idiot's question about Frac
Post by: Thaddy on November 08, 2018, 02:49:48 pm
[edit] missed post by wp, so this is doubling up.
Or use CompareValue. Read more:
https://www.freepascal.org/docs-html/rtl/math/comparevalue.html

Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i : Integer;
  4. begin
  5.   i := 14;
  6.   Memo1.Lines.Add(FloatToStr(10*Frac(i/10)));
  7.   if CompareValue(10*Frac(i/10), 4) >= 0 then Memo1.Lines.Add('Test Passed!');
  8. end;

You mean SameValue here (which returns boolean)? https://www.freepascal.org/docs-html/rtl/math/samevalue.html

But indeed that also works.

Code: Pascal  [Select][+][-]
  1.   if SameValue(10*Frac(i/10),4) then Memo1.Lines.Add('Test Passed!');
Title: Re: Idiot's question about Frac
Post by: jamie on November 08, 2018, 11:47:34 pm
If you are not working in fixed point then the effect you are seeing is just that..

Floats will represent the number 4  as 3.9999999999100 etc....

and the number 5 would be like  5.0000000010 or what ever..

if you use the FloatToStrF and specify that you want to see it all you'll see the real value that is being worked with...

You can also use the old standby which I do so often "STR(Frac(10*(I/10)), OutPutstring)"

 That will display the complete text …

 You could also do as I do in cases where this is an issue and that is to use "Currency Type ", that gives you like four digits
to the right..

TinyPortal © 2005-2018