Recent

Author Topic: StrToFloat - formatted  (Read 2430 times)

J-G

  • Hero Member
  • *****
  • Posts: 875
StrToFloat - formatted
« on: June 25, 2022, 12:53:11 pm »
I may be trying to accomplish the impossible but being aware of  FormatFloat & FloatToStrF I can't find a means by which the opposite may be accomplished.

Given a String '2.5625'  I want to return the number 2.5625  NOT  2.562500023841 . . . . . .

I am aware of 'SameValue' using a small value for the tolerance but have not been able to use that effectively.

This question arose due to an incorrect comparison failure which I still cannot fathom. I created a new Project to demonstrate the issue but was surprised when I couldn't reproduce the same result that I had seen in my main project. This involves comparing two values to determine if the first is an integer.
Code: Pascal  [Select][+][-]
  1. Good := (T1*Ratio) = Int(T1*Ratio);
In the main project, with T1 (byte) being 20 and Ratio (Single) being 2.54999952 (which ought to be 2.55) >:(  'Good' is [False]
but I KNOW that 20x2.55 = 51 (integer)
whereas in the test project:
Code: Pascal  [Select][+][-]
  1. Good := (G1*Ratio) = Int(G1*Ratio);
with G1 (byte) = 20 and Ratio exactly as before, Good returns [True] !  %)
A .zip file of the test project is attached.

I've found a way to force the correct result (for 'Good') in the main project by adding two variables declared as 'single' and comparing those :
Code: Pascal  [Select][+][-]
  1.       A := T1*Ratio;
  2.       B := Int(A);
  3.       Good := A = B;

... but the logic behind this defeats me. :(







FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

Thaddy

  • Hero Member
  • *****
  • Posts: 11892
Re: StrToFloat - formatted
« Reply #1 on: June 25, 2022, 02:01:47 pm »
Code: [Select]
var s:string;
begin
   writestr(s, 2.5625:4);
   writeln(s);
end.
Black themes should be banned.

Bart

  • Hero Member
  • *****
  • Posts: 4744
    • Bart en Mariska's Webstek
Re: StrToFloat - formatted
« Reply #2 on: June 25, 2022, 02:02:35 pm »
Given a String '2.5625'  I want to return the number 2.5625  NOT  2.562500023841 . . . . . .

StrToFloat('2.5625') gives me 2.562500000000000000 (when the result value is a Double).
FormatFloat & FloatToStrF have nothing to do with that (given the quote above).

Bart

Thaddy

  • Hero Member
  • *****
  • Posts: 11892
Re: StrToFloat - formatted
« Reply #3 on: June 25, 2022, 02:06:15 pm »
I made a slight mistake:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. var s:string;
  3. begin
  4.    writestr(s,2.5625:4:4); // note the format punctuation
  5.    writeln(s);
  6. end.
Is correct. It is not the writeln, but the writestr that is actually formatting. It can be both, but writestr is usable in GUI appications. The writeln is just to verify its output. The above code is correct.

https://wiki.freepascal.org/WriteStr
https://www.freepascal.org/docs-html/rtl/system/write.html
https://www.freepascal.org/docs-html/rtl/system/writeln.html
https://www.freepascal.org/docs-html/rtl/system/writestr.html

All fully documented.
Once you know that, it is much easier to use than FloatToStr and family.

« Last Edit: June 25, 2022, 02:15:42 pm by Thaddy »
Black themes should be banned.

J-G

  • Hero Member
  • *****
  • Posts: 875
Re: StrToFloat - formatted
« Reply #4 on: June 25, 2022, 02:19:27 pm »
Code: [Select]
var s:string;
begin
   writestr(s, 2.5625:4);
   writeln(s);
end.
Thanks for the input Thaddy but I have no problem in adjusting the  DISPLAY  of a float (or any number really)  it's making the VALUE  accurate to the real number of decimal places which is my difficulty. 

I've now seen your edit/correction  which adds another dimension which I hadn't considered (I did know that the second argument was needed) - the subtle difference between 'writestr' & 'writeln' - I seldom write 'console' programs and have never used the former.

I now see - thanks to the link you provided - that there is also a ReadStr which I assume is the equivalent of StrToFloat -- where WriteStr is = to FloatToStr  -- I'll now have to see whether there is any benefit in using these methods to convert Strings to Values.

@Bart  may have put his finger on the underlying issue though - I tend to use the smallest 'container' for any variable so seldom need 'Double' or beyond - maybe I need to take a broader approach :)

« Last Edit: June 25, 2022, 02:27:31 pm by J-G »
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

MathMan

  • Sr. Member
  • ****
  • Posts: 274
Re: StrToFloat - formatted
« Reply #5 on: June 25, 2022, 02:39:40 pm »
@J-G - not sure if the following helps your understanding, but I'll try

* you are fiddling with two values 2.55 & 2.5625 which are very different and maybe even got mixed up in your testing such that the results confused you
 - 2.5625 = 41 / 16, that means this value can exactly be represented as a float (single,double or extended) because the divisor is a small power of 2 <= as Bart already pointed out
 - 2.55 = 51/20, that means this value can not be exactly represented as a float (single, double or extended) because the divisor is a multiple of 5
 - the float will contain the closest approximation to '2.55' which, in case of single, is '2.54999952'

* you can calculate at nauseam wrt 2.55 you'll simply never get to 'good = TRUE'
* when calculating wrt 2.5625 you'll get 'good = TRUE' because the floating point value is exact

However, I am currently at a loss to explain where / how you got the info that '2.5625 = 2.562500023841...' - there has to be an intermediate step you overlooked that changed the value to an inexact approximation.

Cheers,
MathMan
« Last Edit: June 25, 2022, 02:49:43 pm by MathMan »

J-G

  • Hero Member
  • *****
  • Posts: 875
Re: StrToFloat - formatted
« Reply #6 on: June 25, 2022, 02:58:27 pm »
@Mathman has come back with some interesting infomation but I'll respond to that next.

I've now declared 'Ratio' as Double and can confirm that the 2.55 does return correctly (no trailing rubbish), however, 'Good' still returns false with the (T1*Ratio) = Int(T1*Ratio) test  and True with the A=B test  - - -  that is the logic that defeats me.

I've also tried ReadStr()  which seems more elegant than StrToFloat() and StrToInt() so that is likely to be my 'goto' method in the future. I've only tried it as a replacement for StrToFloat() as yet.
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

J-G

  • Hero Member
  • *****
  • Posts: 875
Re: StrToFloat - formatted
« Reply #7 on: June 25, 2022, 03:39:45 pm »
@J-G - not sure if the following helps your understanding, but I'll try
And you have succeded!  - I was aware of the issues concerned with accurate representation of 'floats' but your explanation is succinct.

Quote from: MathMan
* you are fiddling with two values 2.55 & 2.5625 which are very different and maybe even got mixed up in your testing such that the results confused you
I wasn't 'confused',   I confused you by referencing two different subjects and providing a very poor (inaccurate) example   :-[    -  I was just trying to indicate that the were values beyond the d.places needed.

The two figures derive from a TPI input of 20.4 and 20.5  which I'd found to give differing results. I'd also had a problem with 24.8 TPI which had returned 3.099999905 rather than 3.1.

Quote from: MathMan
- 2.5625 = 41 / 16, that means this value can exactly be represented as a float (single,double or extended) because the divisor is a small power of 2 <= as Bart already pointed out
Since the minimum allowable figure is 20, I hadn't seen 41/16  -  the first 'good' pairing was at 82/32.

Quote from: MathMan
- 2.55 = 51/20, that means this value can not be exactly represented as a float (single, double or extended) because the divisor is a multiple of 5
 - the float will contain the closest approximation to '2.55' which, in case of single, is '2.54999952'

* you can calculate ad nauseam wrt 2.55 you'll simply never get to 'good = TRUE'
* when calculating wrt 2.5625 you'll get 'good = TRUE' because the floating point value is exact
I completely understand that logic  and even see why using 'Double' is better  - but still cannot see how  A=B  does return true ???  (They are declared : single)  -  I'm grateful that it does!

Quote from: MathMan
However, I am currently at a loss to explain where / how you got the info that '2.5625 = 2.562500023841...' - there has to be an intermediate step you overlooked that changed the value to an inexact approximation.

Cheers,
MathMan
Again - appologies for that particular confusion  :-[
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

dseligo

  • Hero Member
  • *****
  • Posts: 703
Re: StrToFloat - formatted
« Reply #8 on: June 25, 2022, 03:49:12 pm »
I've now declared 'Ratio' as Double and can confirm that the 2.55 does return correctly (no trailing rubbish), however, 'Good' still returns false with the (T1*Ratio) = Int(T1*Ratio) test  and True with the A=B test  - - -  that is the logic that defeats me.

Single and double types (IEEE 754) can't always represent exact decimal number and that is the reason your test fails.

What you can do is compare difference between two number and if it is within certain precision you can say there are same, maybe something like:
Code: Pascal  [Select][+][-]
  1. Good := abs(T1*Ratio - Int(T1*Ratio)) < 0.00001;

Maybe you could use 'Currency' type. You won't have problems like this with Currency, but it is limited to 4 decimal places.

J-G

  • Hero Member
  • *****
  • Posts: 875
Re: StrToFloat - formatted
« Reply #9 on: June 25, 2022, 04:12:25 pm »
I've now declared 'Ratio' as Double and can confirm that the 2.55 does return correctly (no trailing rubbish), however, 'Good' still returns false with the (T1*Ratio) = Int(T1*Ratio) test  and True with the A=B test  - - -  that is the logic that defeats me.
Single and double types (IEEE 754) can't always represent exact decimal number and that is the reason your test fails.
Whilst I understand the problem of exact representaion of decimal numbers, that doesn't tell me why assigning  A (a Single) to (T1*Ratio)  and B (another Single) to Int(A) -- effectively Int(T1*Ratio)  --- returns in a different Boolean result than a direct comparison of the two.

Quote from: dseligo
What you can do is compare difference between two number and if it is within certain precision you can say there are same, maybe something like:
Code: Pascal  [Select][+][-]
  1. Good := abs(T1*Ratio - Int(T1*Ratio)) < 0.00001;
That may well be a viable option as well  -  I have tried the 'SameValue((T1*Ratio),Int(T1*Ratio),Tol) -- where I've set 'Tol' to various values such as 0.001 and 0.000001  -- but without success.

Quote from: dseligo
Maybe you could use 'Currency' type. You won't have problems like this with Currency, but it is limited to 4 decimal places.
Regrettably there is a likelyhood that a viable Ratio can have as many as 8 d.Places - certainly 6 - but thanks for the suggestion.#
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

Paolo

  • Sr. Member
  • ****
  • Posts: 323
Re: StrToFloat - formatted
« Reply #10 on: June 25, 2022, 05:17:59 pm »
Quote
Whilst I understand the problem of exact representaion of decimal numbers, that doesn't tell me why assigning  A (a Single) to (T1*Ratio)  and B (another Single) to Int(A) -- effectively Int(T1*Ratio)  --- returns in a different Boolean result than a direct comparison of the two.
This may happen. It happens if you do in one case
(1)a rounding to specific size and then do the comparison,
and in the other case
(2) do the same computation but in undefinite type and compare the result
In the first case you did
(1)
A:=formula 1;
B:=formula2;
If A=B then...
In the second case
(2)
If formula1 = formula2 then...
Even if formally formula are identical, different floating format with different precision could be involved
Please provide "exactly" the two codes you are speaking about that provider different results
« Last Edit: June 25, 2022, 06:04:38 pm by Paolo »

J-G

  • Hero Member
  • *****
  • Posts: 875
Re: StrToFloat - formatted
« Reply #11 on: June 25, 2022, 06:04:29 pm »
Please provide "exactly" the two code you are speaking about that provided different result
I did so in the very first post.
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

jamie

  • Hero Member
  • *****
  • Posts: 4746
Re: StrToFloat - formatted
« Reply #12 on: June 25, 2022, 06:07:15 pm »
Code: Pascal  [Select][+][-]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. Var
  3.   D:Single = 2.54999952;
  4. begin
  5.   D := RoundTo(D,-4)*20;
  6.   Caption := Int(D).Tostring;
  7. end;                            
  8.  

Produces 51

Does that help you at all?
The only true wisdom is knowing you know nothing

Paolo

  • Sr. Member
  • ****
  • Posts: 323
Re: StrToFloat - formatted
« Reply #13 on: June 25, 2022, 06:24:31 pm »
but you said

Quote
Good := (T1*Ratio) = Int(T1*Ratio);
with T1:byte = 20 and Ratio : single =2.54999952

and then
Quote
Good := (G1*Ratio) = Int(G1*Ratio);
with again G1:byte = 20 and Ratio : single =2.54999952

so they "identical" in any respect ...they must provide the same result.

but you said also
Quote
  A := T1*Ratio;
  B := Int(A);
  Good := A = B;
with A and B : single that makes me suspect that it is the problem I pointed out

as an example look at this code (the attempt is to calculate the number of points on a square grid inside a circle)
Code: Pascal  [Select][+][-]
  1. var
  2.  nX,nY,NumX,NumY : integer;
  3.  X,Y: double;
  4. ....
  5.   for nX:=0 to NumX-1 do begin
  6.     X:=X1+(X2-X1)*nX/(NumX-1);
  7.     for nY:=0 to ANumY-1 do begin
  8.        Y:=Y1+(Y2-Y1)*nY/(NumY-1);
  9.        if Sqrt(Sqr(X)+Sqr(Y)) < 1 then begin
  10.           //do something
  11.           counter:=counter+1;
  12.        end;
  13.     end;
  14.  end;
  15.  
and the code
Code: Pascal  [Select][+][-]
  1. var
  2.  nX,nY,NumX,NumY : integer;
  3.  X,Y: double;
  4. ....
  5.   for nX:=0 to NumX-1 do begin
  6.     for nY:=0 to NumY-1 do begin
  7.        if Sqrt(Sqr(X1+(X2-X1)*nX/(NumX-1))+Sqr(Y1+(Y2-Y1)*nY/(NumY-1))) < 1 then begin
  8.           //do something
  9.           counter:=counter+1;
  10.        end;
  11.     end;
  12.  end;
  13.  
despite the computation is the same in Win32 (are you compiling for Win32 target ?) in the first case you are comparing "single" in the second case the comparison is done in "extended" so it may happen that for certain values of NumX or/and NumY the counter gives different results between the two routines because the single values are different wrt extended values. I see this many years ago in Delphi, I think you have similar situation.


« Last Edit: June 25, 2022, 07:11:24 pm by Paolo »

wp

  • Hero Member
  • *****
  • Posts: 10030
Re: StrToFloat - formatted
« Reply #14 on: June 25, 2022, 06:44:41 pm »
Code: Pascal  [Select][+][-]
  1. Good := (G1*Ratio) = Int(G1*Ratio);
I guess that this a test to determine whether the product of two numbers is an integer. The problem with the '=' operator has already be mentioned by others: always use SameValue() or equivalent, i.e.
Code: Pascal  [Select][+][-]
  1. Good := SameValue(G1*Ratio, Int(G1*Ratio), 1E-6);
I would like to point you also to the fact that the Int() function is highly problematic. Suppose the product of G1 and ratio should be 33, but the finite resolution of floating point values and accumulated rounding errors results in a true value of 33.000000012 - in this case Good is true because the difference between 33.000000012 and Int(33.000000012)=32 is less than 1E-6.

But, there is not guarantee that the computer value is always larger than the expected value; in fact with equal probability the value can be less than 33, e.g. 32.999999982. But now your expression using Int() causes a huge problem because Int(32.999999982) is 32, and the difference to 32.999999982 is almost 1, which is MUCH larger than 1E-6 --> Good = false!

What you can do against this, is to use Round() rather than Int(). This function always adds 0.5 to the value before truncating the decimals, and round(32.999999982) is 33 again. So, the correct check would be:
Code: Pascal  [Select][+][-]
  1. Good := SameValue(G1*Ratio, Round(G1*Ratio), 1E-6);

 

TinyPortal © 2005-2018