# Lazarus

## Programming => General => Topic started by: J-G on June 25, 2022, 12:53:11 pm

Title: StrToFloat - formatted
Post by: J-G 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. :(

Title: Re: StrToFloat - formatted
Post by: Thaddy on June 25, 2022, 02:01:47 pm
Code: [Select]
`var s:string;begin    writestr(s, 2.5625:4);   writeln(s);end.`
Title: Re: StrToFloat - formatted
Post by: Bart 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
Title: Re: StrToFloat - formatted
Post by: Thaddy 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.

Title: Re: StrToFloat - formatted
Post by: J-G 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 :)

Title: Re: StrToFloat - formatted
Post by: MathMan 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
Title: Re: StrToFloat - formatted
Post by: J-G 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.
Title: Re: StrToFloat - formatted
Post by: J-G 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  :-[
Title: Re: StrToFloat - formatted
Post by: dseligo 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.
Title: Re: StrToFloat - formatted
Post by: J-G 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.#
Title: Re: StrToFloat - formatted
Post by: Paolo 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
Title: Re: StrToFloat - formatted
Post by: J-G 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.
Title: Re: StrToFloat - formatted
Post by: jamie 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

Title: Re: StrToFloat - formatted
Post by: Paolo 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.

Title: Re: StrToFloat - formatted
Post by: wp 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);
Title: Re: StrToFloat - formatted
Post by: Paolo on June 25, 2022, 07:28:10 pm
I see in the doc
Code: Pascal  [Select][+][-]
1. function Int(d: Extended):Extended;
2.

so if you do this

Code: Pascal  [Select][+][-]
1. Ratio : single or double;
2. ...
3. good:=(G1*Ratio) = Int(G1*Ratio)
4.

probably you are comparing "extended value" of Int e probably G1*Ratio expanded as Extended (it depends also the compiler target Win23, win64,..).

if you do that
Code: Pascal  [Select][+][-]
1. with A, B : single;
2. A := T1*Ratio;
3. B := Int(A);
4. Good := A = B;
5.

you are comparing two "single value" with Int value after trouncation at single

and as I said before the results can be different because single values are differenet wrt respective extended values.

very likely if you use Extended everywhere all is "good(true)".
Title: Re: StrToFloat - formatted
Post by: J-G on June 25, 2022, 07:31:53 pm
Well that was a short lived excursion into ReadStr . . . .

In an attempt to further understand the  - what I consider 'Weird'  -  behaviour, I've been trying to evaluate the value assigned to A & B   -  ie. the result of   (T1*Ratio)  -  by assigning it to a String Variable (TmpStr)  using ReadStr(TmpStr,A). This appeared to work - the project compiled and ran showing 'A' in a memo.

However I'd had a Compile OK but a 106 error at run-time indicating that the argument should be 'Char' ???  This was when I'd created a second 'String' variable - RStr : exactly the same as TmpStr - and using ReadStr(RStr,A) is not acceptable!   As I said  -  Weird.

Code: Pascal  [Select][+][-]
1.     Repeat
2.
3.       A := T1*Ratio;
4.       B := Int(A);
5.
6.       if T1< 22 then
7.         begin
8.           TmpStr := IntToStr(T1);
10.           WriteMemo('T1-  I2S '+TmpStr,false);
12.           WriteMemo('T1-  Read '+TmpStr,false);
13.
14.           TmpStr := FloatToStr(Ratio);
15.           WriteMemo('Ratio -  F2S '+TmpStr,false);
17.           WriteMemo('Ratio -  Read '+TmpStr,false);
18.
19.           TmpStr := FloatToStr(A);
20.           WriteMemo('  A-  F '+TmpStr,false);
22.           WriteMemo('  A-  R '+TmpStr,false);
23.
25.           TmpStr := FloatToStr(B);
26.           WriteMemo('  B-  F '+TmpStr+'  R - '+RStr,true);
27.         end;
28.
29.       Good := A = B;
30.

If I un-comment lines 9 or 24 then although it compiles the runtime error 106 always fires.

Whilst I've been evaluating and writing this, Paolo & WP have made further suggestions and I've now successfully incorporated WPs 'SameValue' suggestion - I'd tried many variants previously but always got something wrong!  :-[   often being warned that 'You can't do that' but never fully understanding why.

I'll not bother trying to understand why ReadStr seems to dislike my use of RStr any further and thank all contributors for their stirling efforts to solve my issues.

Title: Re: StrToFloat - formatted
Post by: J-G on June 25, 2022, 07:38:13 pm
... you are comparing two "single value" with Int value after trouncation at single

and as I said before the results can be different because single values are differenet wrt respective extended values.

very likely if you use Extended everywhere all is "good(true)".
I think that you are right on the money Paolo - to some extent I was only pursuing the issue out of curiosity, but hopefully I've learned a valuable lesson which may also be useful to others :)
Title: Re: StrToFloat - formatted
Post by: Thaddy on June 25, 2022, 08:28:49 pm
Just playing "Oh  well"
Why are you trying to mimic morons????
Title: Re: StrToFloat - formatted
Post by: J-G on June 25, 2022, 08:43:12 pm
Just playing "Oh  well"
Why are you trying to mimic morons????
I do appreciate your input Thaddy (though not your choice of music  ;D   -  I'm currently rehearsing Haydn's 'The Creation')  and I will evaluate ReadStr & WriteStr further - just not today.  :o

Title: Re: StrToFloat - formatted
Post by: winni on June 25, 2022, 08:58:58 pm
Just playing "Oh  well"

OH WELL!

One  of the worlds best blues guitar heroes.
Replaced Eric Claption at the Bluesbreakers.

Peter Green RIP
Title: Re: StrToFloat - formatted
Post by: J-G on June 25, 2022, 11:10:53 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

Thanks Jamie  -   your post nearly got lost in the crowd  -  but I do like to acknowledge all contributions to any threads that I initiate.

It doesn't help specifically for this project but it does show me other ways to format numbers where the container needs a string, so it does help to further my education :)   - - -   so many new 'methods' have become available since I started programming with TP and my 'GoTo' reference work is still TP 4.0 documentation - though I do have TP 6.0 as well - and of course I have many Lazarus Tutorial pages permanently open.  To some extent there is 'information overload' so I often miss very useful newer methods.
Title: Re: StrToFloat - formatted
Post by: jamie on June 26, 2022, 01:09:29 am
I've used the Currency type many times for Fixed point. the RTL seems to know how to handle it.

it gives you 4 places to the right and is all binary in a 64bit integer.

if your values can fit in that then maybe that is what you need to use.

you can just create a morphing type if you wish..

Type
TMyNumberType = Currency;

The Fix point type does not have the rounding issues etc.

Just a thought.
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 09:57:18 am
I've used the Currency type many times for Fixed point. the RTL seems to know how to handle it.
[...]
The Fix point type does not have the rounding issues etc.
It may have HUGE rounding issues if applied thoughtlessly...

Currency is usable only for elemental math operations such as addition, subtraction and multiplication. Even division, if done in the wrong order, may be seriously in error because of the few decimal places.
Code: Pascal  [Select][+][-]
1. var
2.   c1, c2, c3: Currency;
3. begin
4.   c1 := 1;
5.   c2 := 100000;
6.   c3 := c1 / 3 * c2;
7.   WriteLn(c1:0:6);
8.   WriteLn(c2:0:6);
9.   WriteLn(c3:0:6);
10. end.
The result is expected to be 33333.3333, but it turns out to be 33330.0000. (The correct result is obtained, when the division is performed at the end, i.e. c3 := c1*c2/3, but strictly speaking, the order of multiplication and division is not important in math).

And "real" math can be a huge problem:
Code: Pascal  [Select][+][-]
1. var
2.   c1, c2, c3: Currency;
3. begin
4.   c1 := 0.5;
5.   c2 := ln(c1);
6.   c3 := exp(c3);
7.   WriteLn(c1);
8.   WriteLn(c2);
9.   WriteLn(c3);
10. end.
This code calculates the log of 0.5 and then the exp function of the result. Since ln and exp are inverse functions the final result should be equal to the initial value (0.5) - it turns out the be 1.0. however.
Title: Re: StrToFloat - formatted
Post by: tetrastes on June 26, 2022, 11:00:23 am
Code: Pascal  [Select][+][-]
1. var
2.   c1, c2, c3: Currency;
3. begin
4.   c1 := 0.5;
5.   c2 := ln(c1);
6.   c3 := exp(c3);
7.   WriteLn(c1);
8.   WriteLn(c2);
9.   WriteLn(c3);
10. end.
This code calculates the log of 0.5 and then the exp function of the result. Since ln and exp are inverse functions the final result should be equal to the initial value (0.5) - it turns out the be 1.0. however.

There is typo in your code:
Code: Pascal  [Select][+][-]
1. c3 := exp(c3);
The result of
Code: Pascal  [Select][+][-]
1. c3 := exp(c2);
is correct.

Though I agree with you about rounding issues and the usability of Currency type in general. Currency is for currency.
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 12:12:45 pm
Oh...

But then I don't understand this data type at all. I thought it is stored as an Int64 (https://www.freepascal.org/docs-html/ref/refsu5.html: "The currency type is a fixed-point real data type which is internally used as an 64-bit integer type (automatically scaled with a factor 10000)"). How can it yield the exact result when all decimals after the 4th are truncated in the first calculation? Well, maybe, when the exp is calculated based on the rounded value the error is after the 4th decimal again and does not appear after truncating the final result. But such a coincidence is hard to believe. I am sure that there must be better examples...
Title: Re: StrToFloat - formatted
Post by: dseligo on June 26, 2022, 12:30:23 pm
Oh...

But then I don't understand this data type at all. I thought it is stored as an Int64 (https://www.freepascal.org/docs-html/ref/refsu5.html: "The currency type is a fixed-point real data type which is internally used as an 64-bit integer type (automatically scaled with a factor 10000)"). How can it yield the exact result when all decimals after the 4th are truncated in the first calculation? Well, maybe, when the exp is calculated based on the rounded value the error is after the 4th decimal again and does not appear after truncating the final result. But such a coincidence is hard to believe. I am sure that there must be better examples...

ln(0,5) is rounded to -0,6931

exp(-0,6931) is 0,50002359083648271601454137665035 (Windows calc).
That is rounded to 0,5000, so the result is correct.

I use currency a lot, and didn't encounter any significant problems with it.

It is great for me e.g. when I have to sum many financial values which have 2 decimal places. When I used double I always have differences with final sum. With currency I always have correct result.
It would be great to have more decimal places though, I would find it even more useful.
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 01:30:02 pm
Here is a variant of the exp/ln sample with shows a difference although not as large as I had written. The difference becomes evident when an intermediate currency result gets close to 0.0000. In this particular example there will even be a runtime error when c1 is -9.9 or smaller so that the intermediate result is 0.0000 in the currency case because the ln(0.0000) cannot be calculated.
Code: Pascal  [Select][+][-]
1. Program Test;
2. uses
3.   Math;
4. var
5.   c1, c2, c3: Currency;
6.   d1, d2, d3: Double;
7. begin
8.   WriteLn('Calculation with double:');
9.   d1 := -9.8;
10.   d2 := exp(d1);
11.   d3 := ln(d2);
12.   WriteLn('d1 = ', d1:0:9);
13.   WriteLn('d2 = ', d2:0:9);
14.   WriteLn('d3 = ', d3:0:9, ' (should be equal to d1)');
15.
16.   writeln;
17.
18.   WriteLn('Calculation with currency:');
19.   c1 := -9.8;
20.   WriteLn('c1 = ', c1:0:4);
21.   c2 := exp(c1);
22.   if c2 = 0 then
23.     WriteLn('c2 = (Cannot calculate ln(0.0000))')
24.   else
25.   begin
26.     WriteLn('c2 = ', c2:0:4);
27.     c3 := ln(c2);
28.     WriteLn('c3 = ', c3:0:4, ' (should be equal to c1)');
29.   end;
30.
32. end.

Output:
Code: [Select]
`Calculation with double:d1 = -9.800000000d2 = 0.000055452d3 = -9.800000000 (should be equal to d1)Calculation with currency:c1 = -9.8000c2 = 0.0001c3 = -9.2103 (should be equal to c1)`
Title: Re: StrToFloat - formatted
Post by: J-G on June 26, 2022, 01:54:27 pm
To help me better understand not only the potential problems with choice of 'Type' to use (Currency, Double, Extended) but also the use of ReadStr & WriteStr, I've knocked up a quick project (.Zip attached) to take an argument (default at 0.5 as suggested by WP) and show these results.

It's interesting to see that each 'type' has issues with certain inputs, (the obvious ones being numbers ending in a 5 - [1.25] say) though D & E seem to have the same issue - try 5.125 . . . .

The ReadStr/WriteStr also threw up some unexpected issues. ReadStr accepted the use of a TLable.Caption but WriteStr does not, it demands an intermediary 'String' variable which is currently beyond my understanding.

Title: Re: StrToFloat - formatted
Post by: tetrastes on June 26, 2022, 02:07:57 pm
I am sure that there must be better examples...

The most obvious is trigonometry:
Code: Pascal  [Select][+][-]
1. uses math;
2.
3. var
4.   f1, f2, f3: single;     // Not very precise, but proper, not to say about double
5.   c1, c2, c3: Currency;
6. begin
7.   f1 := 0.01;
8.   f2 := cos(f1);
9.   f3 := arccos(f2);
10.   WriteLn(f1);
11.   WriteLn(f2);
12.   WriteLn(f3);
13.   f1 := f1+2*PI;
14.   f2 := cos(f1);
15.   f3 := arccos(f2);
16.   WriteLn(f1);
17.   WriteLn(f2);
18.   WriteLn(f3);
19.   writeln;
20.
21.   c1 := 0.01;
22.   c2 := cos(c1);       // Improper
23.   c3 := arccos(c2);    // Improper (not equals 0.01)
24.   WriteLn(c1);
25.   WriteLn(c2);
26.   WriteLn(c3);
27.   c1 := c1+2*PI;
28.   c2 := cos(c1);       // Not equals cos(0.01)
29.   c3 := arccos(c2);     // Improper (not equals 0.01)
30.   WriteLn(c1);
31.   WriteLn(c2);
32.   WriteLn(c3);
34. end.
35.
Title: Re: StrToFloat - formatted
Post by: jamie on June 26, 2022, 02:57:46 pm
here is an old test project, I use this helper for a few things and the values are based from Excel etc.
Title: Re: StrToFloat - formatted
Post by: dseligo on June 26, 2022, 03:06:59 pm
Here is a variant of the exp/ln sample with shows a difference although not as large as I had written. The difference becomes evident when an intermediate currency result gets close to 0.0000. In this particular example there will even be a runtime error when c1 is -9.9 or smaller so that the intermediate result is 0.0000 in the currency case because the ln(0.0000) cannot be calculated.

If you need more than 4 decimal places then use single or double, not currency. You could still use single or double for intermediate calculation and use currency for final value if that fits your need.
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 03:29:42 pm
If you need more than 4 decimal places then use single or double, not currency. You could still use single or double for intermediate calculation and use currency for final value if that fits your need.
Why should I switch number type for intermediate calculations? I never use Currency because I want the full accuracy of the calculation. I don't care about the appended '0's or '9's - even if this looks ugly on a quick and dirty output, its the highest accuracy than can be achieved on a computer with the finite resolution of single, double or extended. When it comes to display the results the numbers must be converted to strings anyway, and for this there are a lot of float-to-string conversion functions which can round to any number of decimals. Currency poses the risk of enhanced error propagation when multiple operations are chained. Even the requirement to have multiplication and division in a given order is against all that I am used to.

Currency is good only for adding monetary values, but even this can be done with single/double/extended since the error in them is way beyond the 4 decimals of Currency and never shows up in a string converted to 2 decimals.

The only problem with single/double/extended is in comparison operations. As main rule, never use the =, <, <= etc operators, use SameValue of the math unit (or combinations with it) instead. The reason why currency comparisons are always (?) exact is that they basically do nothing else than SameValue(a, b, 1E-4).
Title: Re: StrToFloat - formatted
Post by: BrunoK on June 26, 2022, 03:54:59 pm
Currency is good only for adding monetary values, but even this can be done with single/double/extended since the error in them is way beyond the 4 decimals of Currency and never shows up in a string converted to 2 decimals.
t is that they basically do nothing else than SameValue(a, b, 1E-4).
Code: Pascal  [Select][+][-]
1. program Project1;
2.
3. var
4.   i : integer;
5.   d1, d2 : double;
6.   sd1, sd2 : double;
7.
8.   c1, c2 : currency;
9.   sc1, sc2 : currency;
10. begin
11.
12.   d1 := 0.1; d2 := 0.2;
13.   sd1 :=0; sd2 := 0;
14.   for I := 1 to 10 do
15.     sd1 := sd1 + d1;
16.   for I := 1 to 5 do
17.     sd2 := sd2 +d2;
18.   WriteLn('sd1 = sd2 : ', sd1 = sd2);
19.
20.   c1 := 0.1; c2 := 0.2;
21.   sc1 :=0; sc2 := 0;
22.   for I := 1 to 10 do
23.     sc1 := sc1 + c1;
24.   for I := 1 to 5 do
25.     sc2 := sc2 +c2;
26.   WriteLn('sc1 = sc2 : ', sc1 = sc2);
27.
29. end.
30.
Title: Re: StrToFloat - formatted
Post by: dseligo on June 26, 2022, 03:58:36 pm
If you need more than 4 decimal places then use single or double, not currency. You could still use single or double for intermediate calculation and use currency for final value if that fits your need.
Why should I switch number type for intermediate calculations? I never use Currency because I want the full accuracy of the calculation. I don't care about the appended '0's or '9's - even if this looks ugly on a quick and dirty output, its the highest accuracy than can be achieved on a computer with the finite resolution of single, double or extended. When it comes to display the results the numbers must be converted to strings anyway, and for this there are a lot of float-to-string conversion functions which can round to any number of decimals. Currency poses the risk of enhanced error propagation when multiple operations are chained. Even the requirement to have multiplication and division in a given order is against all that I am used to.

Currency is good only for adding monetary values, but even this can be done with single/double/extended since the error in them is way beyond the 4 decimals of Currency and never shows up in a string converted to 2 decimals.

You said: "I want the full accuracy of the calculation", "Currency poses the risk of enhanced error propagation" and "even this can be done with single/double/extended since the error in them is way beyond the 4 decimals of Currency and never shows up in a string converted to 2 decimals".
Look at this example:

Code: Pascal  [Select][+][-]
1. var cX: Currency = 0.0;
2.     dX: Double = 0.0;
3.     i: Integer;
4.     s: String;
5. begin
6.   For i := 1 to 10000 do begin
7.     cX := cX + 10000000.07;
8.     dX := dX + 10000000.07;
9.   end;
10.
11.   WriteLn(FormatFloat('0.0000', cX));
12.   WriteLn(FormatFloat('0.0000', dX));
13. end.

Result of double isn't accurate for me, there is error propagation and error is in 2nd decimal place already. Exactly opposite of what you said for single and double types.

If you think I consider that single/double isn't good and currency type is the best, you are wrong (it feels like that from tone of your post). You have to know advantages and limits of both type when you use them, and there are cases when one type is better than the other.

P.S.: Try single instead of double in my example, LOL
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 04:02:29 pm
Currency is good only for adding monetary values, but even this can be done with single/double/extended since the error in them is way beyond the 4 decimals of Currency and never shows up in a string converted to 2 decimals.
t is that they basically do nothing else than SameValue(a, b, 1E-4).
Code: Pascal  [Select][+][-]
1. program Project1;
2.
3. var
4.   i : integer;
5.   d1, d2 : double;
6.   sd1, sd2 : double;
7.
8.   c1, c2 : currency;
9.   sc1, sc2 : currency;
10. begin
11.
12.   d1 := 0.1; d2 := 0.2;
13.   sd1 :=0; sd2 := 0;
14.   for I := 1 to 10 do
15.     sd1 := sd1 + d1;
16.   for I := 1 to 5 do
17.     sd2 := sd2 +d2;
18.   WriteLn('sd1 = sd2 : ', sd1 = sd2);
19.
20.   c1 := 0.1; c2 := 0.2;
21.   sc1 :=0; sc2 := 0;
22.   for I := 1 to 10 do
23.     sc1 := sc1 + c1;
24.   for I := 1 to 5 do
25.     sc2 := sc2 +c2;
26.   WriteLn('sc1 = sc2 : ', sc1 = sc2);
27.
29. end.
30.
Did you read my post? I wrote: "never use the =, <, <= etc operators, use SameValue of the math unit (or combinations with it) instead."
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 04:13:53 pm
Code: Pascal  [Select][+][-]
1. var cX: Currency = 0.0;
2.     dX: Double = 0.0;
3.     i: Integer;
4.     s: String;
5. begin
6.   For i := 1 to 10000 do begin
7.     cX := cX + 10000000.07;
8.     dX := dX + 10000000.07;
9.   end;
10.
11.   WriteLn(FormatFloat('0.0000', cX));
12.   WriteLn(FormatFloat('0.0000', dX));
13. end.
Good example. But I stay with my opinion that currency is not good for anything else than addition and subtraction and multiplication. Division already may go wrong if the programmer is not careful.
Title: Re: StrToFloat - formatted
Post by: BrunoK on June 26, 2022, 04:21:09 pm
In accounting :
sum of debits must be exactly equal to sum of credit.
OutOfTaxAmount + TaxAmount = TotalAmount must be exact.

Sum(OutOfTaxAmount) + Sum(TaxAmount)  = Sum(TotalAmount)

Sum(OutOfTaxAmount) * (1 + TaxRate) must, with small divergence be equal to Sum(TotalAmount). Thus well defined rounding strategy must be defined, especially when the total amount of, invoices for example, are rounded to 5 cents.

That's the most basic thing an auditor will check.

Title: Re: StrToFloat - formatted
Post by: BrunoK on June 26, 2022, 04:28:28 pm
Just a small correction, if I would have to write an accounting system for a country that has an hyper inflating currency,  then I would do it all with the double/extended type.
Title: Re: StrToFloat - formatted
Post by: BrunoK on June 26, 2022, 04:40:37 pm
And with SameValue :
Code: Pascal  [Select][+][-]
1. program Project1;
2.
3. uses
4.   math;
5. var
6.   i : integer;
7.   d1, d2 : double;
8.   sd1, sd2 : double;
9.
10.   c1, c2 : currency;
11.   sc1, sc2 : currency;
12. begin
13.
14.   d1 := 0.1; d2 := 0.15;
15.   sd1 :=0; sd2 := 0;
16.   for I := 1 to 75000 do
17.     sd1 := sd1 + d1;
18.   for I := 1 to 50000 do
19.     sd2 := sd2 +d2;
20.   WriteLn('sd1 = sd2 : ', sd1 = sd2);
21.   WriteLn('SameValue(sd1 = sd2) : ', SameValue(sd1, sd2));
22.
23.   c1 := 0.1; c2 := 0.15;
24.   sc1 :=0; sc2 := 0;
25.   for I := 1 to 75000 do
26.     sc1 := sc1 + c1;
27.   for I := 1 to 50000 do
28.     sc2 := sc2 +c2;
29.   WriteLn('sc1 = sc2 : ', sc1 = sc2);
30.
32. end.
Output :
sd1 = sd2 : FALSE
SameValue(sd1 = sd2) : FALSE
sc1 = sc2 : TRUE
Title: Re: StrToFloat - formatted
Post by: wp on June 26, 2022, 05:04:24 pm
Code: Pascal  [Select][+][-]
1.   WriteLn('SameValue(sd1 = sd2) : ', SameValue(sd1, sd2, 1e-7));  // ---> TRUE

Of course this kind of calculation is a bit unfair because it adds always the same value and the same error value. Thus it causes maximum error propagation; and therefore, the relatively high tolerance value is needed to achieve a TRUE comparison (Note that Currency with 4 decimals is equivalent to a tolerance of 1E-4, though). If the accumulated addition is replaced by a single multiplication, the tolerance can be much smaller. And if different values were added, each having a different float error, positive or negative, the tolerance would be smaller either.
Title: Re: StrToFloat - formatted
Post by: BrunoK on June 26, 2022, 05:30:35 pm
Of course this kind of calculation is a bit unfair ...
Agreed ;-)
But I could expose cases about bean counters making difficulties with small imbalances or also excessively exact balance where an small error (not imbalance) should logically have been found due to rounding.
Title: Re: StrToFloat - formatted
Post by: J-G on June 26, 2022, 05:47:58 pm
That's quite a flurry of posts while I had my back turned! --  mostly trying to dis @WPs opinion on the use of Currency.

I've never used Currency in the past - and I've written two accounting suites - I have attempted to use 'SameValue' on occasion but not been successful  ---  until educated in its proper use by WP in post #15, where he pointed out the anciliary issues of Int().

Essentially this discussion has been very useful (to me anyway) in bringing to the fore the importance of choosing the appropriate 'Type' to use, depending upon the particular application.  I used to use 'Real' for any float until I realized that its size and significance was platform dependant and then switched to 'Single' as my default  - -  Now I have a better understanding of the 'Pros' & 'Cons' of any 'Type' and can make an informed decision in future.

Hmm . . .   I started this post to raise the point about pBCD but got distracted!

I know (well think) that Packed Binary Coded Decimal gets around all these issue caused by not being able to store decimal numbers accurately in binary format so why isn't pBCD used - or have I missed some 'small but important' point?

Title: Re: StrToFloat - formatted
Post by: jamie on June 26, 2022, 05:54:28 pm
https://community.atmel.com/projects/afp-arbitrary-fixed-point-lib

I guess I am not the only one in town that knows floats are a problem when you need exact values.

In my line of work I try not to use floats because of the unexpected fractional results that leads of confusion.

The link above is another contributor that thinks the say way and their LIB has some trig in it.
Title: Re: StrToFloat - formatted
Post by: PascalDragon on June 26, 2022, 11:01:02 pm
It's interesting to see that each 'type' has issues with certain inputs, (the obvious ones being numbers ending in a 5 - [1.25] say) though D & E seem to have the same issue - try 5.125 . . . .

As written by various others already: some numbers can not be expressed exactly as floating point numbers. So 5.125 can only be approximated and the math system can't simply show 5.125, because what if you did try to use the number that 5.125 is approximated as? You'd simply change the numbers that are approximated. This is something you need to live with when dealing with floating point numbers. If you don't and you know the number of digits beyond the decimal point you need to deal with then go with a fixed point library.

The ReadStr/WriteStr also threw up some unexpected issues. ReadStr accepted the use of a TLable.Caption but WriteStr does not, it demands an intermediary 'String' variable which is currently beyond my understanding.

Same reason you can't take an address of a property (it could be a method call after all resulting in a temporary variable). For ReadStr the value of the string only needs to be read, thus it's perfectly find to pass a property. For WriteStr that's different however as the contents of the string are modified with each variable that is written to the string. Thus you can't use properties here.
Title: Re: StrToFloat - formatted
Post by: SymbolicFrank on June 27, 2022, 12:58:32 pm
The basic assumption here is, that a binary value is not exact, when it isn't equal to a decimal value. Or, more broadly: you cannot simply translate any floating point number into a different numeral system, with the same accuracy. Obvious example: 1/3 is 0.1 in base 3, or 0.4 in base 12, but it has no accurate representation in base 2 or 10. That's why early computers used BCD to calculate in base 10, because that's the norm.

But it's probably missing the point, as fractions crop up during math in either case. So it's useless trying to keep things "exact". It's far more practical to look at the amount of significant digits you need. And that's where floating-point math excels. In fixed-point math (currency), as shown you lose accuracy fast, while with floating-point the amount of significant figures stays as high as will fit the container size.

In short: for math, always use the largest format floating-point number available, which is extended (x86/AMD64 only), or double (everything else). And only compare your chosen amount of significant figures.

Well, ok, perhaps not if you're calculating 3D geometry. Although Kerbal Space Program doesn't agree ;)
Title: Re: StrToFloat - formatted
Post by: PascalDragon on June 27, 2022, 01:18:13 pm
Don't underestimate fixed point types. Depending on your use case they might be better (https://web.archive.org/web/20110710010949/https://facepunch.com/content/119-Why-floats-suck.). ;)
Title: Re: StrToFloat - formatted
Post by: BrunoK on June 27, 2022, 06:07:12 pm
... So 5.125 can only be approximated and the math system can't simply show 5.125, because what if you did try to use the number that 5.125 is approximated as?
Well, as a special case 5.125 is exactly stored in double as 0.640625 × (2 ^ 3)
Title: Re: StrToFloat - formatted
Post by: J-G on June 27, 2022, 06:21:31 pm
... So 5.125 can only be approximated and the math system can't simply show 5.125, because what if you did try to use the number that 5.125 is approximated as?
Well, as a special case 5.125 is exactly stored in double as 0.640625 × (2 ^ 3)
My point was that using my test program, all three 'types' return an incorrect value -
Currency = 5.1248,
Double &
Extended =  5.12499999999999
Title: Re: StrToFloat - formatted
Post by: BrunoK on June 27, 2022, 08:06:00 pm
This bit of code should be able to test for GOOD or BAD using the caption but there is no way to guarantee an exactly double. If 4 decimal digits are all you need, use a currency, otherwise design you own fixed point.
Code: Pascal  [Select][+][-]
1. procedure TForm1.TPI_ValEditingDone(Sender: TObject);
2. type
3.   TDecMulFact = 0..3;
4. const
5.   cMultFact : array[TDecMulFact] of integer =
6.                 (1,
7.                  10,
8.                  100,
9.                  1000);
10. var
11.   lUInt64 : UInt64;
12.   lDecimals : integer;
13.   lStr : ShortString;
14.   i : integer;
15. begin
16.   if not TryStrToFloat(TPI_Val.Caption, TPI) then begin
17.     OK.Caption:='Invalid entry';
18.     Exit;
19.   end;
20.   lStr := TPI_Val.Caption;
21.   lDecimals := 0; // Init
22.   for i:=1 to length(lStr) do begin
23.     if lStr[i]='.' then begin
24.       lDecimals := length(lStr) - i;
25.       lStr := Copy(lStr, 1, i-1) + Copy(lStr, i+1, length(lStr) - i);
26.       break;
27.     end;
28.   end;
29.   if not (lDecimals in [low(TDecMulFact)..High(TDecMulFact)]) then begin
30.     OK.Caption:='Too many decimals';
31.     Exit;
32.   end;
33.
34.   lUInt64 := StrToInt(lStr); // Note lDecimals has nb of digits after '.'
35.   Good := ((lUInt64 * G1) mod cMultFact[lDecimals]) = 0;
36.
37.   if good then
38.     OK.Caption:='GOOD'
39.   else
Title: Re: StrToFloat - formatted
Post by: SymbolicFrank on June 28, 2022, 12:45:41 pm
... So 5.125 can only be approximated and the math system can't simply show 5.125, because what if you did try to use the number that 5.125 is approximated as?
Well, as a special case 5.125 is exactly stored in double as 0.640625 × (2 ^ 3)
My point was that using my test program, all three 'types' return an incorrect value -
Currency = 5.1248,
Double &
Extended =  5.12499999999999

The thing is, as soon as you start doing math to it, you get rounding errors. When you take a decimal number and convert that to binary, you're doing math to it. And the other way around, of course. It is exactly the same as with integer math, only the fractions (rounding errors) are a lot smaller.

If you don't want any rounding errors: use Planck units.
Title: Re: StrToFloat - formatted
Post by: winni on June 28, 2022, 12:52:09 pm

If you don't want any rounding errors:

Use an analogue computer, not a binary one.

Title: Re: StrToFloat - formatted
Post by: PascalDragon on June 28, 2022, 01:24:33 pm
Don't underestimate fixed floating point. Depending on your use case they might be better (https://web.archive.org/web/20110710010949/https://facepunch.com/content/119-Why-floats-suck.). ;)
Did you mean fixed _decimal_ point ?

I meant fixed point types. But yeah, thank you for pointing that out... :-[
Title: Re: StrToFloat - formatted
Post by: SymbolicFrank on July 03, 2022, 10:49:09 pm

If you don't want any rounding errors:

Use an analogue computer, not a binary one.
That doesn't really make a difference. It's about resolution. Even if you have an ultimately exact analog computer, you have to feed it with starting values, and at the end you have to extract the answer. Analog-to-Decimal-converters don't have infinite precision. That's the whole point.

Even more so: that's also the main problem with quantum computers. Even if we ignore the fact, that they aren't real quantum computers until they can use quantum entanglement, which they can't, the output contains all the possible solutions at the same time. Every time you calculate and read it, each bit has the value of one of the possible solutions, within the error margins...

They would work better when analog, but unfortunately in quantum mechanics everything is an integer.

So, until they can synchronize turning the entangled particles into a wave function totally synchronously and resolve that perfectly into binary words, as many times as there are solutions, it is just noise.

So, even on that scale it's integers all the way down :D
Title: Re: StrToFloat - formatted
Post by: PascalDragon on July 04, 2022, 01:33:17 pm

If you don't want any rounding errors:

Use an analogue computer, not a binary one.
That doesn't really make a difference. It's about resolution. Even if you have an ultimately exact analog computer, you have to feed it with starting values, and at the end you have to extract the answer. Analog-to-Decimal-converters don't have infinite precision. That's the whole point.

But you wouldn't have the rounding errors during the computation which is a real issue (no pun intended) with floating point maths.