Author Topic: How do you deal with floating point rounding errors ?  (Read 12442 times)

0 Members and 1 Guest are viewing this topic.

Offline CharlieJV

  • Newbie
  • Posts: 89
    • View Profile
How do you deal with floating point rounding errors ?
« on: April 01, 2022, 12:06:14 am »
Programming since 1984, and I can't believe I'm only running into this now.

Lucky?  Meh.

Code: QB64: [Select]
  1. for i -1 to 1 step 0.1

How do you get around this rounding error?

Is it best to avoid floating point numbers, and divide final results by whatever to get right decimal points?

ie. should the code above be changed to:

Code: QB64: [Select]
  1. for i -10 to 10 step 1
  2. print i/10,


Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #1 on: April 01, 2022, 12:11:14 am »
There is no single right answer.

You'll have to find the way that best works with what your trying to accomplish.

divide as you have shown there, or

z! = INT(I!*1000)\1000  say if you wanted 3 points of precision.

Use that string math thingy thats floating around here somewhere.

Or just work something out that gives you the results that you can work with.

Just the nature of the floating point beast.
Granted after becoming radioactive I only have a half-life!

Offline CharlieJV

  • Newbie
  • Posts: 89
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #2 on: April 01, 2022, 12:27:59 am »
2 years in a programming course at college, and 5 years at university to get a computer science degree, and I can't believe this never came up, especially in the two digital design courses at university.

This is pretty fundamental stuff, now that I've read a few things about it.

I have a few choice words for my educators from back then...

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #3 on: April 01, 2022, 12:43:14 am »
Its all relative, if thats not an over used phrase.

How much precision do you really need?

If your trying to pinpoint a single snow flake on the surface of Pluto then you probably need some pretty sharp precision.

If your trying to put a golf ball in a 55 gallon drum sitting next to you.. probably not.

They're not going to waste a lot of time on something like this unless your going into a field that requires accuracy on the sub-atomic scale.

(why am I double spacing all my posts?)
Granted after becoming radioactive I only have a half-life!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: How do you deal with floating point rounding errors ?
« Reply #4 on: April 01, 2022, 12:45:56 am »
You're simply dealing with the immutable nature of floating point values.

In decimal form, what is 1/3? 

It's 0.33333333333333333*** onto an infinite number of 3s.  It's impossible to perfectly represent 1/3 in decimal format, so eventually you reach a point where you say, "It's close enough for my needs!"  That might be 0.33, or 0.33333333, or 0.333333333333333333333, but at some point you just stop pasting on 3s and you call it "good enough".

The problem, however, comes when you start adding those decimal values together.  Add your number three times and you get 0.99, 0.99999999, or 0.999999999999999999999, but you'll never exactly add up to one!

Rounding errors are just part of the nature of the beast, and you have to figure out how to deal with them.

One of the simplest ways, is to do like banks do: Eliminate decimals entirely!  Your bank doesn't track your account as having $123.45 in it.  They track it as you having 12,345 PENNIES in it!  Integer values instead of decimal values, and it's only when displaying the amount that they convert down to decimals for the user.  (Much like your second code box does.)

Another simple way, in this case, is to limit results via rounding, since you only want a single digit decimal value.  Something like the following should work:

i = INT(10 * i + .5) /10   

Another solution is to use PRINT USING to format the results to single decimal precision:

PRINT USING "###.#"; i

Those tend to be your basic 3 ways of dealing with the issue.
1) Convert to integers and don't deal with floating points at all.
2) Manually round to a lower level of precision than the variable type normally holds.
3) Format the print to account for the issue, without altering those floating point values.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #5 on: April 01, 2022, 03:17:39 am »
The need for "String Math" strikes again. Bruhahahahaha!

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline jack

  • Seasoned Forum Regular
  • Posts: 408
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #6 on: April 01, 2022, 07:07:30 pm »
string-math may be the solution for you, it's a pity QB64 doesn't have a decimal type
I know that some people use integers as a means to simulate fixed-point decimals but it's a far cry from having true decimal-floating point
you could use one of the decimal floating point libraries like Intel's library https://www.intel.com/content/www/us/en/developer/articles/tool/intel-decimal-floating-point-math-library.html but it's use would be cumbersome

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #7 on: April 02, 2022, 10:32:46 am »
but it's use would be cumbersome

Hence why QB64 would not have one. BASIC is not meant to be cumbersome. Which of course may leave some wanting, but if they need

something like that they probably need to step away from BASIC languages.

But then it comes down to how much actual precision do you really need?
Granted after becoming radioactive I only have a half-life!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #8 on: April 02, 2022, 11:01:46 am »
Steve said it some time ago, I paraphrase: Separate the calculations with variables function from the displaying of the results function.

You just need a good format routine to adjust for garbage that enters into calculations with floats. Probably would include rounding to the precision of decimals you desire in the output (display) format.

For CharlieJV this seems to work:
Code: QB64: [Select]
  1. For i = -1 To 1 Step .1
  2.     Print i, format$("##.#", Str$(i))
  3.  
  4. Function format$ (template As String, Source As String)
  5.     Dim d, s, n, i, t$
  6.     d = _Dest: s = _Source
  7.     n = _NewImage(80, 80, 0)
  8.     _Dest n: _Source n
  9.     Print Using template; Val(Source)
  10.     For i = 1 To 79
  11.         t$ = t$ + Chr$(Screen(1, i))
  12.     Next
  13.     If Left$(t$, 1) = "%" Then t$ = Mid$(t$, 2)
  14.     format$ = _Trim$(t$)
  15.     _Dest d: _Source s
  16.     _FreeImage n
  17.  
  18.  
 [ You are not allowed to view this attachment ]  

Well shoot, seems to be stopping at .9 not 1, dang!

OK this fixes that!
Code: QB64: [Select]
  1. For i = -1 To 1.0001 Step .1
  2.     Print i, format$("##.#", Str$(i))
  3. Function format$ (template As String, Source As String)
  4.     Dim d, s, n, i, t$
  5.     d = _Dest: s = _Source
  6.     n = _NewImage(80, 80, 0)
  7.     _Dest n: _Source n
  8.     Print Using template; Val(Source)
  9.     For i = 1 To 79
  10.         t$ = t$ + Chr$(Screen(1, i))
  11.     Next
  12.     If Left$(t$, 1) = "%" Then t$ = Mid$(t$, 2)
  13.     format$ = _Trim$(t$)
  14.     _Dest d: _Source s
  15.     _FreeImage n
  16.  
« Last Edit: April 02, 2022, 11:30:30 am by bplus »

Offline jack

  • Seasoned Forum Regular
  • Posts: 408
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #9 on: April 02, 2022, 11:32:53 am »
gcc runtime library has decimal arithmetic functions https://gcc.gnu.org/onlinedocs/gccint/Decimal-float-library-routines.html so it may be possible to use them in QB64
@Cobalt I think that you may have misunderstood what I meant when I said that using a decimal library would be cumbersome, for example you would have call functions for the basic arithmetic operators but if the decimal type was built-into QB64 then you would use as any other supported type

Offline CharlieJV

  • Newbie
  • Posts: 89
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #10 on: April 02, 2022, 11:38:19 am »
(SNIP!)
Code: QB64: [Select]
  1. For i = -1 To 1.0001 Step .1
  2.     Print i, format$("##.#", Str$(i))
  3. Function format$ (template As String, Source As String)
  4.     Dim d, s, n, i, t$
  5.     d = _Dest: s = _Source
  6.     n = _NewImage(80, 80, 0)
  7.     _Dest n: _Source n
  8.     Print Using template; Val(Source)
  9.     For i = 1 To 79
  10.         t$ = t$ + Chr$(Screen(1, i))
  11.     Next
  12.     If Left$(t$, 1) = "%" Then t$ = Mid$(t$, 2)
  13.     format$ = _Trim$(t$)
  14.     _Dest d: _Source s
  15.     _FreeImage n
  16.  

I'm pretty sure I'll be leaning this way going forward:

Code: QB64: [Select]
  1. For i = -10 To 10 Step 1
  2.     rem i / 10 wherever needed

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: How do you deal with floating point rounding errors ?
« Reply #11 on: April 02, 2022, 11:48:20 am »
Yeah it works for Doppler's problem too.
https://qb64forum.alephc.xyz/index.php?topic=4753.msg141737#msg141737

That does not guarantee a panacea, still have rounding errors when garbage builds up with adding and multiplying but this covers allot of problems.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: How do you deal with floating point rounding errors ?
« Reply #12 on: April 02, 2022, 01:09:48 pm »
Personally, here's the general method I use when I have to deal with this type of thing:

Code: QB64: [Select]
  1. For i = -1 To 1 Step 0.1
  2.     Print i
  3.  
  4. For i = -1 To 1 Step 0.1
  5.     Locate , 20: Print i;
  6.     i = Int(i * 10 + .5) / 10
  7.     Locate , 40: Print i

Run this and you'll first see the garbled up mess that adding floating point values give naturally.

Press any key to break sleep, and then you'll see how we're rounding i to our desired precision, and how it affects things.  In the middle column, we see that i keeps to a much smaller level of variance since we're always rounding it to the desired level of precision, keeping the glitches to a bare minimum.  On the right hand column, we see that the results are as we expect them to be after we round them.

The absolute easiest way to deal with this type of issue is to just add a simple function into your code to Fix_Precision, like so:

Code: QB64: [Select]
  1. For i = -1 To 1 Step 0.1
  2.     i = Fix_Precision(i)
  3.     Print i
  4.  
  5. Function Fix_Precision## (value##)
  6.     Fix_Precision = Int(value## * 10 + .5) / 10
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: How do you deal with floating point rounding errors ?
« Reply #13 on: April 02, 2022, 01:14:15 pm »
Notice how the fix can also be applied for Doppler's problem as well: 
Code: QB64: [Select]
  1. _Title "Total it" ' from Doppler "defsng math going off the rails, creating a total from list of numbers in file"
  2. ' ref: https://qb64forum.alephc.xyz/index.php?topic=4753.msg141698#msg141698
  3. total = 0
  4. t = 0
  5.  
  6.     Read t$
  7.     If t$ = "EOD" Then Exit Do
  8.     t = Fix_Precision(Val(t$))
  9.     Print t
  10.     Print Str$(total) + " plus " + t$ + " equals ";
  11.     total = Fix_Precision(total + t)
  12.     Print total;
  13.     Print " press any to contiue..."
  14.     Sleep
  15. Data -750,21.71,13.67,22.92,91.25,119.63,10.84,18.46,40.44,171.73,3.08,92.39,57.20,22.79,-2.80,43.87,34.81,83.82,10.85,EOD
  16.  
  17.  
  18. Function Fix_Precision## (value##)
  19.     Fix_Precision = Int(value## * 100 + .5) / 100

https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: How do you deal with floating point rounding errors ?
« Reply #14 on: April 02, 2022, 01:24:43 pm »
Steve said it some time ago, I paraphrase: Separate the calculations with variables function from the displaying of the results function.

You just need a good format routine to adjust for garbage that enters into calculations with floats. Probably would include rounding to the precision of decimals you desire in the output (display) format.

For CharlieJV this seems to work:
Code: QB64: [Select]
  1. For i = -1 To 1 Step .1
  2.     Print i, format$("##.#", Str$(i))
  3.  
  4. Function format$ (template As String, Source As String)
  5.     Dim d, s, n, i, t$
  6.     d = _Dest: s = _Source
  7.     n = _NewImage(80, 80, 0)
  8.     _Dest n: _Source n
  9.     Print Using template; Val(Source)
  10.     For i = 1 To 79
  11.         t$ = t$ + Chr$(Screen(1, i))
  12.     Next
  13.     If Left$(t$, 1) = "%" Then t$ = Mid$(t$, 2)
  14.     format$ = _Trim$(t$)
  15.     _Dest d: _Source s
  16.     _FreeImage n
  17.  
  18.  
 [ You are not allowed to view this attachment ]  

Well shoot, seems to be stopping at .9 not 1, dang!

OK this fixes that!
Code: QB64: [Select]
  1. For i = -1 To 1.0001 Step .1
  2.     Print i, format$("##.#", Str$(i))
  3. Function format$ (template As String, Source As String)
  4.     Dim d, s, n, i, t$
  5.     d = _Dest: s = _Source
  6.     n = _NewImage(80, 80, 0)
  7.     _Dest n: _Source n
  8.     Print Using template; Val(Source)
  9.     For i = 1 To 79
  10.         t$ = t$ + Chr$(Screen(1, i))
  11.     Next
  12.     If Left$(t$, 1) = "%" Then t$ = Mid$(t$, 2)
  13.     format$ = _Trim$(t$)
  14.     _Dest d: _Source s
  15.     _FreeImage n
  16.  

The one issue with using format$ (or Print Using) to display your results is the simple fact that:

1) You're still going to be dealing with actually incorrect values.  If you have a line of code in that IF block that read something like IF i = 0.9 THEN...   you'd have a false result as I would be 0.90000002 and not 0.9.
2) Since i holds those incorrect rounding errors, the error is going to keep growing ever larger over time.  Notice in your screenshot where it's 0.80000001 -- a variance of 0.0000001, and then the next value is 0.90000002...  The glitch grows over time!

Round to the desired precision as you go and your problem goes away without accumulating and adding up over time to become a major issue for you.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!