double trouble
-
- Posts: 4313
- Joined: Jan 02, 2017 0:34
- Location: UK
- Contact:
Re: round to two digits?
@BasicCoder2
There is no need for the test 'If w1(i)<>0 Then'. If w1(i) = 0 then 'w1(i)=Int(w1(i))/100' will still be zero.
There is no need for the test 'If w1(i)<>0 Then'. If w1(i) = 0 then 'w1(i)=Int(w1(i))/100' will still be zero.
Re: round to two digits?
Ain't floating point fun? Edited this post to make it shorter and maybe less presumptuous.
But let's get back to binary. Binary has similar problems, but with different numbers since it's variations of 1/2^n. For example, here is a number that cannot be represented accurately in IEEE floating point: 0.2. No matter how you work it out as a series of binary fractions, the binary representation will always be slightly less than 0.2 (0.0011001100110011.. etc). Let's consider different "roudings" of the binary number:
0.0011 = 0.1875
0.00110011 = 0.19921875
0.001100110011 = 0.199951171
etc.
Let's consider 0.93:
0.93 = 0.1110 1110 0001 0100 0111 1010...
There isn't a good way of truncating (rounding) the binary digits to get 0.93.
FreeBASIC isn't getting it wrong per se. It's IEEE floating point getting it wrong. For example even simple math doesn't "work" in binary: 0.1 + 0.2 can never quite be equal to 0.3. At certain precisions it's close enough to not matter of course.
This problem cannot be completely removed, but you can certainly hide it. There are many articles on inaccuracies in MS Excel and how to deal with them. And there are techniques to use when writing code to convert a binary number to decimal that can help, such as setting a threshhold on the number of zeros in a row, or number of 9s in a row.
The only way to correct it is to use a different type that does not involve binary fractions. All numbers based on fractions can have this issue, regardless of the base. For example "1/3." In all cases no matter how many decimal places you work it out to, the answer will always be less than 1/3.deltarho[1859] wrote: ↑Oct 13, 2023 20:46There is no getting away from the fact that 93/100 is 0.93. With FreeBASIC 'Print 93/100' gives 0.9300000000000001. With PowerBASIC we get 0.93.
Whatever the reason for the difference, FreeBASIC is getting it wrong.
The question is: Should FreeBASIC be corrected?
But let's get back to binary. Binary has similar problems, but with different numbers since it's variations of 1/2^n. For example, here is a number that cannot be represented accurately in IEEE floating point: 0.2. No matter how you work it out as a series of binary fractions, the binary representation will always be slightly less than 0.2 (0.0011001100110011.. etc). Let's consider different "roudings" of the binary number:
0.0011 = 0.1875
0.00110011 = 0.19921875
0.001100110011 = 0.199951171
etc.
Let's consider 0.93:
0.93 = 0.1110 1110 0001 0100 0111 1010...
There isn't a good way of truncating (rounding) the binary digits to get 0.93.
FreeBASIC isn't getting it wrong per se. It's IEEE floating point getting it wrong. For example even simple math doesn't "work" in binary: 0.1 + 0.2 can never quite be equal to 0.3. At certain precisions it's close enough to not matter of course.
This problem cannot be completely removed, but you can certainly hide it. There are many articles on inaccuracies in MS Excel and how to deal with them. And there are techniques to use when writing code to convert a binary number to decimal that can help, such as setting a threshhold on the number of zeros in a row, or number of 9s in a row.
Last edited by caseih on Oct 14, 2023 19:26, edited 2 times in total.
-
- Posts: 1186
- Joined: May 08, 2006 21:58
- Location: Crewe, England
Re: round to two digits?
You have the same problem with pocket calculators; the more sophisticated ones get round it by holding your intermediate result as an (improper) fraction. Try doing 1/3+1/3+1/3 on older calculators and you will get 0.999999. A modern one will have internally calculated 3/3 and will give you the correct answer. At some point the calculator will have to convert to decimal or binary to perform an operation, eg square root or something.
In banking systems, where accuracy is important, currency amounts will be defined (in binary) internally in minor units (cents), or else the cents will be held in a separate field, so that some calculations, (e.g. charges) can easily be done on whole major units. Often the minor units are held as positive, and the major units can go negative - so if your account is 5 cents overdrawn it is held as (-1+.95) dollars. Before British currency went decimal in 1971, there wore 240 pence in a pound, which could under this system be stored in a byte.
If you need calculation to a known number of decimal places, you can calculate in binary in hundredths (or whatever) and when you need to print, convert to character and edit in the decimal point where it applies. Of course you still have to decide about rounding following a division, by examining the remainder. Different countries have different rules about rounding when calculating the percetage due as tax as well.
In banking systems, where accuracy is important, currency amounts will be defined (in binary) internally in minor units (cents), or else the cents will be held in a separate field, so that some calculations, (e.g. charges) can easily be done on whole major units. Often the minor units are held as positive, and the major units can go negative - so if your account is 5 cents overdrawn it is held as (-1+.95) dollars. Before British currency went decimal in 1971, there wore 240 pence in a pound, which could under this system be stored in a byte.
If you need calculation to a known number of decimal places, you can calculate in binary in hundredths (or whatever) and when you need to print, convert to character and edit in the decimal point where it applies. Of course you still have to decide about rounding following a division, by examining the remainder. Different countries have different rules about rounding when calculating the percetage due as tax as well.
-
- Posts: 4313
- Joined: Jan 02, 2017 0:34
- Location: UK
- Contact:
Re: round to two digits?
@caseih
OK, points taken.
If we look at BasicCoders' code the statements using arithmetic don't need double precision – single precision is more than adequate.
If we declared w1() as Single the problem ceases to exist. The underlying issues persist, but BasicCoders's concern doesn't.
OK, points taken.
If we look at BasicCoders' code the statements using arithmetic don't need double precision – single precision is more than adequate.
If we declared w1() as Single the problem ceases to exist. The underlying issues persist, but BasicCoders's concern doesn't.
Last edited by deltarho[1859] on Oct 14, 2023 19:28, edited 1 time in total.
Re: round to two digits?
Good point. There are lots of gotchas with IEEE floating point, and precision doesn't always give you accuracy. Perhaps with single precision, when printing out the number to the screen, PRINT stops far earlier, knowing that the number of decimal places possible are much less. So the extra .00000000000...1 is simply lost in the calculations, and the rules that PRINT uses (actually c library scanf) to generate the digits sees the zeros and truncates it. There are research papers published on how to best convert and print a floating point number.
Another thing I thought of was that maybe if you use double precision for the actual math, and then truncate the result to single precision, that might work better sometimes also. Calculators may use a certain number of bits precision, but truncate the decimal result to 10 decimal places.
Another thing I thought of was that maybe if you use double precision for the actual math, and then truncate the result to single precision, that might work better sometimes also. Calculators may use a certain number of bits precision, but truncate the decimal result to 10 decimal places.
Re: round to two digits?
IEEE actually defined a fractional type where numbers are stored as essentially two integers. I think this was sometimes called "floating slash." But it never caught on as far as hardware support goes. Theoretically it allows arbitrary decimal accuracy, obviously at the expense of needing a lot more bits to encode things.jevans4949 wrote: ↑Oct 14, 2023 16:19the more sophisticated ones get round it by holding your intermediate result as an (improper) fraction.
-
- Posts: 4313
- Joined: Jan 02, 2017 0:34
- Location: UK
- Contact:
Re: round to two digits?
That is what I was doing with my 'trick': Print Cast(Single,w1(i))caseih wrote:Another thing I thought of was that maybe if you use double precision for the actual math, and then truncate the result to single precision, that might work better sometimes also.
Re: round to two digits?
I would do the printout something like this:
I have done a little research into Quasi Monte Carlo Integration.
There it was important for me to consider the numerical instabilities, where rounding errors lead to disturbances of the result.
The 'trick' is a good idea here.
I can't try PowerBasic, but I would find it interesting how PowerBasic reacts to a program like this:
Code: Select all
Sub sPrint(n As Double, decimals As Byte)
Dim As Double a,b
a = Fix(n)
b = Abs(Frac(n))
b = Int(b*(10^decimals)+0.5)
Print a & "." & b 'Strangely enough, it works.
End Sub
Dim As Single a
Dim As Double b
b = 0.93
a = b
Print "single: ";a
Print "double: ";b
Print "shortened double: ";
sPrint b,2
Sleep
There it was important for me to consider the numerical instabilities, where rounding errors lead to disturbances of the result.
The 'trick' is a good idea here.
I can't try PowerBasic, but I would find it interesting how PowerBasic reacts to a program like this:
Code: Select all
Dim As Double a,s,n
a = 1/3
Do
s += a
n += 1
Print a,s/n
Loop
Re: round to two digits?
hhr
I changed your code a bit
only lines 42 thru 49 print .333333333333334 the rest print .333333333333333
I changed your code a bit
Code: Select all
#COMPILE EXE
#DIM ALL
FUNCTION PBMAIN () AS LONG
DIM a AS DOUBLE, s AS DOUBLE, n AS DOUBLE
DIM i AS LONG
a = 1/3
FOR i=1 TO 100
s += a
n += 1
PRINT i, a, s/n
NEXT i
WAITKEY$
END FUNCTION
Code: Select all
41 .333333333333333 .333333333333333
42 .333333333333333 .333333333333334
43 .333333333333333 .333333333333334
44 .333333333333333 .333333333333334
45 .333333333333333 .333333333333334
46 .333333333333333 .333333333333334
47 .333333333333333 .333333333333334
48 .333333333333333 .333333333333334
49 .333333333333333 .333333333333334
50 .333333333333333 .333333333333333
-
- Posts: 4313
- Joined: Jan 02, 2017 0:34
- Location: UK
- Contact:
Re: round to two digits?
@hhr
Your first code looked good, but we only need one failure to drag us back to the drawing board. I didn't examine the code.
Try b = 85.07. I get:
Your first code looked good, but we only need one failure to drag us back to the drawing board. I didn't examine the code.
Try b = 85.07. I get:
Code: Select all
single: 85.07
double: 85.06999999999999
shortened double: 85.7
Re: round to two digits?
@srvaldez
It seems PowerBasic prints out 15 digits, FreeBasic prints 16 digits.
@deltarho
Yes, you are right, the line with the comment is wrong. I have to use strings. I will try tomorrow.
It seems PowerBasic prints out 15 digits, FreeBasic prints 16 digits.
@deltarho
Yes, you are right, the line with the comment is wrong. I have to use strings. I will try tomorrow.
Re: round to two digits?
I said in a previous post that since double only holds about 15.95 digits that the default print precision should be 15
single is better off since it holds about 7.2 digits, so for single printing 7 digits should be ok
[edit] single has a problem in the seventh digit, the output is virtually the same in FB and PB, so PB's only advantage is to limit the output of double to 15 digits
but caseih already explained why, there's no point in this as it's part of using binary floating point
single is better off since it holds about 7.2 digits, so for single printing 7 digits should be ok
[edit] single has a problem in the seventh digit, the output is virtually the same in FB and PB, so PB's only advantage is to limit the output of double to 15 digits
Code: Select all
87 .3333333 .3333335
88 .3333333 .3333335
89 .3333333 .3333335
90 .3333333 .3333335
91 .3333333 .3333335
92 .3333333 .3333336
93 .3333333 .3333336
94 .3333333 .3333336
95 .3333333 .3333336
96 .3333333 .3333336
97 .3333333 .3333336
98 .3333333 .3333335
99 .3333333 .3333335
100 .3333333 .3333335
Re: round to two digits?
I suspect (know, actually) that PB implemented its own printing routine, and made a good choice to limit to 15 digits. Whereas FB's runtime uses the Standard C Library's sscanf() or similar to do the conversion to string, and I guess the C library decided on 16 digits for some reason. Would be interesting to see if the same choice is made in other C libraries. GLibC, uLibc, libc, newlib, the MSVCRT, etc. I'm not sure if the standard dictates this sort of thing or not.
Re: round to two digits?
I think the problem is that when looking at the fractional part of the number, leading zeros are important, but trailing zeros are not. However any INTEGER type is going to throwaway the leading zeros and keep the trailing zeros.deltarho[1859] wrote: ↑Oct 14, 2023 21:27 @hhr
Your first code looked good, but we only need one failure to drag us back to the drawing board. I didn't examine the code.
-
- Posts: 4313
- Joined: Jan 02, 2017 0:34
- Location: UK
- Contact:
Re: round to two digits?
This thread could rumble on for some time.
BasicCoder's opening issue is resolved by simply declaring w1() as Single.
BasicCoder's opening issue is resolved by simply declaring w1() as Single.