Lazarus

Programming => General => Topic started by: lazpas on October 02, 2022, 03:26:33 am

Title: A computational problem
Post by: lazpas on October 02, 2022, 03:26:33 am
44950-7693*4.77=?
The code looks like this:

Code: Pascal  [Select][+][-]
  1. ......
  2. parser := TFPExpressionParser.Create(nil);
  3.   try
  4.     parser.BuiltIns := [bcMath];
  5.     parser.Expression := '44950-7693*4.77';
  6.     parserResult := parser.Evaluate;
  7.     Caption:= FloatToStr(parserResult.ResFloat);
  8.   finally
  9.     parser.Free;
  10.   end;
  11.  

The result is:8254.39000000001

The correct result is:8254.39

Is there a bug in my code?

Thanks
Title: Re: A computational problem
Post by: Bogen85 on October 02, 2022, 04:20:20 am
44950-7693*4.77=?
...

The result is:8254.39000000001

The correct result is:8254.39

Is there a bug in my code?

Thanks


No. Your code is fine. That is normal for binary to decimal conversion. You can't get an exact representation of the floating point number when you convert it to decimal due to base 10 not being a power of 2.
You need to display it with less precision then trim trailing zeros to get something that is more reasonable.
Title: Re: A computational problem
Post by: MarkMLl on October 02, 2022, 09:04:59 am
Or rescale the numbers before the calculation and handle them as integers. 64 bits should be enough to handle most financial calculations for at least a few more months :-/

MarkMLl
Title: Re: A computational problem
Post by: jcmontherock on October 02, 2022, 09:19:19 am
What about parenthesis in your expression ?
Title: Re: A computational problem
Post by: Bogen85 on October 02, 2022, 09:40:50 am
What about parenthesis in your expression ?

The result was off by 0.0000000001 according to @lazpas.

That is a normal floating point to decimal conversion issue.

Adding parenthesis around the 7693*4.77 is not going to make floating point to decimal conversion issues magically disappear.  :D

This problem is not limited to Pascal, specifically Free Pascal in this example. You would get the same results with C/C++/python/etc if you don't limit the precision on the displayed output.
Title: Re: A computational problem
Post by: Bogen85 on October 02, 2022, 10:16:10 am
44950-7693*4.77=?
...
The result is:8254.39000000001

The correct result is:8254.39

Is there a bug in my code?

Or just use sysutils.format. It will correct the display issues for you.
https://www.freepascal.org/docs-html/rtl/sysutils/format.html

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2.  
  3. program float_precision;
  4.  
  5. uses
  6.   fpexprpars,
  7.   sysutils;
  8.  
  9.   procedure primary (const parser: TFPExpressionParser);
  10.  
  11.     procedure do_sample (const tag, fmt, expression: string);
  12.       begin
  13.         parser.expression := expression;
  14.         writeln (tag, ': ', expression, ' = ', format (fmt, [parser.evaluate.resFloat]));
  15.       end;
  16.  
  17.     begin
  18.  
  19.       do_sample ('1', '%f', '44950-7693*4.77');
  20.       do_sample ('2', '%f', '(44950-7693)*4.77');
  21.       do_sample ('3', '%f', '44950-(7693*4.77)');
  22.       do_sample ('4', '%g', '44950-7693*4.77');
  23.       do_sample ('5', '%e', '44950-7693*4.77');
  24.  
  25.       parser.free;
  26.     end;
  27.  
  28.   function newParser: TFPExpressionParser;
  29.     begin
  30.       result := TFPExpressionParser.create (nil);
  31.       result.builtIns := [bcMath];
  32.     end;
  33.  
  34. begin
  35.   primary (newParser);
  36. end.

Code: Text  [Select][+][-]
  1. $ fpc lazforum/60783/float_precision.pas && ./lazforum/60783/float_precision
  2. Free Pascal Compiler version 3.3.1 [2022/10/01] for x86_64
  3. Copyright (c) 1993-2022 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling lazforum/60783/float_precision.pas
  6. Linking lazforum/60783/float_precision
  7. 36 lines compiled, 0.4 sec, 658752 bytes code, 361144 bytes data
  8. 1: 44950-7693*4.77 = 8254.39
  9. 2: (44950-7693)*4.77 = 177715.89
  10. 3: 44950-(7693*4.77) = 8254.39
  11. 4: 44950-7693*4.77 = 8254.3900000000067
  12. 5: 44950-7693*4.77 = 8.2543900000000067E+003

Results from 1 or 3 are what you are looking for.
Title: Re: A computational problem
Post by: jamie on October 02, 2022, 03:26:50 pm
Currency type
WriteStr(.... with Format speciifiers)

Fixed Point math unit etc.

Title: Re: A computational problem
Post by: J-G on October 02, 2022, 06:51:24 pm
I've taken to using my own two procs. to circumvent this issue.

In case it may be of interest :

Code: Pascal  [Select][+][-]
  1. Var
  2.   TmpStr :    String;
  3.  
  4. procedure StripZero(Var S : String;NoTrail : Boolean);
  5. begin
  6.   while S[length(S)] = '0' do
  7.     begin
  8.       S := copy(S,1,Pred(Length(S)));
  9.     end;
  10.   if S[length(S)] ='.' then
  11.     begin
  12.       if NoTrail then
  13.         S := copy(S,1,Pred(Length(S)))
  14.       else
  15.         S := S+'0';
  16.     end;
  17.   if Length(S) = 0 then
  18.     S := '0';
  19. end;
  20.  
  21. procedure MakeStr(Val : single;Trail : boolean;DP : byte);
  22. begin
  23.   Str(Val:4:DP,TmpStr);
  24.   StripZero(TmpStr,trail);
  25. end;
  26.  
Title: Re: A computational problem
Post by: jamie on October 02, 2022, 08:14:45 pm
I've taken to using my own two procs. to circumvent this issue.

In case it may be of interest :

Code: Pascal  [Select][+][-]
  1. Var
  2.   TmpStr :    String;
  3.  
  4. procedure StripZero(Var S : String;NoTrail : Boolean);
  5. begin
  6.   while S[length(S)] = '0' do
  7.     begin
  8.       S := copy(S,1,Pred(Length(S)));
  9.     end;
  10.   if S[length(S)] ='.' then
  11.     begin
  12.       if NoTrail then
  13.         S := copy(S,1,Pred(Length(S)))
  14.       else
  15.         S := S+'0';
  16.     end;
  17.   if Length(S) = 0 then
  18.     S := '0';
  19. end;
  20.  
  21. procedure MakeStr(Val : single;Trail : boolean;DP : byte);
  22. begin
  23.   Str(Val:4:DP,TmpStr);
  24.   StripZero(TmpStr,trail);
  25. end;
  26.  

Ah, you mean this
Code: Pascal  [Select][+][-]
  1. RemoveTrailingChars(S,['0','.']);  
  2.  

and this
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. Var
  3.   S:String;
  4. begin
  5.    WriteStr(S,8254.000000001:0:2);
  6.    RemoveTrailingChars(S,['0','.']);
  7.    Caption := S;
  8. end;                                    
  9.  

Etc
 :o
Title: Re: A computational problem
Post by: jamie on October 02, 2022, 08:28:22 pm
To add a little bit to it

Code: Pascal  [Select][+][-]
  1. Function RemoveTrailingCharsF(S:String; C:TSysCharSet;EmptyStr:String='0'):String;
  2. Begin
  3.    RemoveTrailingChars(S,C);
  4.    If S ='' then S :=EmptyStr;
  5.    Result := S;
  6. end;
  7.  
  8. procedure TForm1.Button1Click(Sender: TObject);
  9. Var
  10.   S:String;
  11. begin
  12.    WriteStr(S,8254.000000001:0:2);
  13.    Caption := RemoveTrailingCharsF(S,['0','.']);
  14. end;                                                    
  15.  
  16.  
Title: Re: A computational problem
Post by: Arioch on October 02, 2022, 08:55:51 pm
   If S ='' then S :=EmptyStr;
end;

but why???

with short strings this is 
Code: Pascal  [Select][+][-]
  1. if s[0] = #0 then s[0] := #0;

with long strings this should be equal to 
Code: Pascal  [Select][+][-]
  1. if s = nil then s := nil;

in both cases nothing was gained...
Title: Re: A computational problem
Post by: jamie on October 02, 2022, 09:22:02 pm
now just think about that for a moment.

Why limit the function?
Title: Re: A computational problem
Post by: Arioch on October 02, 2022, 09:35:26 pm
Ouch...

Code: Pascal  [Select][+][-]
  1. EmptyStr:String='0'):

EmptyStr is parameter here....

There is RTL constant with same name AFAIR
Title: Re: A computational problem
Post by: jamie on October 02, 2022, 10:08:44 pm
What if I wanted the Empty Value to be something other than a single digit char ?

what if it's a UTF8 for special symbols etc.?
Title: Re: A computational problem
Post by: lazpas on October 26, 2022, 10:16:39 am
Thank you all for your reply. :)
Title: Re: A computational problem
Post by: lazpas on October 26, 2022, 10:24:18 am
44950-7693*4.77=?
...
The result is:8254.39000000001

The correct result is:8254.39

Is there a bug in my code?

Or just use sysutils.format. It will correct the display issues for you.
https://www.freepascal.org/docs-html/rtl/sysutils/format.html

Code: Pascal  [Select][+][-]
  1. {$mode delphi}
  2.  
  3. program float_precision;
  4.  
  5. uses
  6.   fpexprpars,
  7.   sysutils;
  8.  
  9.   procedure primary (const parser: TFPExpressionParser);
  10.  
  11.     procedure do_sample (const tag, fmt, expression: string);
  12.       begin
  13.         parser.expression := expression;
  14.         writeln (tag, ': ', expression, ' = ', format (fmt, [parser.evaluate.resFloat]));
  15.       end;
  16.  
  17.     begin
  18.  
  19.       do_sample ('1', '%f', '44950-7693*4.77');
  20.       do_sample ('2', '%f', '(44950-7693)*4.77');
  21.       do_sample ('3', '%f', '44950-(7693*4.77)');
  22.       do_sample ('4', '%g', '44950-7693*4.77');
  23.       do_sample ('5', '%e', '44950-7693*4.77');
  24.  
  25.       parser.free;
  26.     end;
  27.  
  28.   function newParser: TFPExpressionParser;
  29.     begin
  30.       result := TFPExpressionParser.create (nil);
  31.       result.builtIns := [bcMath];
  32.     end;
  33.  
  34. begin
  35.   primary (newParser);
  36. end.

Code: Text  [Select][+][-]
  1. $ fpc lazforum/60783/float_precision.pas && ./lazforum/60783/float_precision
  2. Free Pascal Compiler version 3.3.1 [2022/10/01] for x86_64
  3. Copyright (c) 1993-2022 by Florian Klaempfl and others
  4. Target OS: Linux for x86-64
  5. Compiling lazforum/60783/float_precision.pas
  6. Linking lazforum/60783/float_precision
  7. 36 lines compiled, 0.4 sec, 658752 bytes code, 361144 bytes data
  8. 1: 44950-7693*4.77 = 8254.39
  9. 2: (44950-7693)*4.77 = 177715.89
  10. 3: 44950-(7693*4.77) = 8254.39
  11. 4: 44950-7693*4.77 = 8254.3900000000067
  12. 5: 44950-7693*4.77 = 8.2543900000000067E+003

Results from 1 or 3 are what you are looking for.
Hi,Bogen85.Thanks for help.

I'm writing a calculator. The number of decimal places is indeterminate.
Title: Re: A computational problem
Post by: lazpas on October 26, 2022, 10:27:54 am
I've taken to using my own two procs. to circumvent this issue.

In case it may be of interest :

Code: Pascal  [Select][+][-]
  1. Var
  2.   TmpStr :    String;
  3.  
  4. procedure StripZero(Var S : String;NoTrail : Boolean);
  5. begin
  6.   while S[length(S)] = '0' do
  7.     begin
  8.       S := copy(S,1,Pred(Length(S)));
  9.     end;
  10.   if S[length(S)] ='.' then
  11.     begin
  12.       if NoTrail then
  13.         S := copy(S,1,Pred(Length(S)))
  14.       else
  15.         S := S+'0';
  16.     end;
  17.   if Length(S) = 0 then
  18.     S := '0';
  19. end;
  20.  
  21. procedure MakeStr(Val : single;Trail : boolean;DP : byte);
  22. begin
  23.   Str(Val:4:DP,TmpStr);
  24.   StripZero(TmpStr,trail);
  25. end;
  26.  
Thanks for sharing these codes. I will learn it.
TinyPortal © 2005-2018