Author Topic: Maths accuracy  (Read 4848 times)

0 Members and 1 Guest are viewing this topic.

Offline JohnUKresults

  • Newbie
  • Posts: 40
    • View Profile
Maths accuracy
« on: May 26, 2021, 07:18:59 am »
Hi all

Trying to do this (although with figures from an array rather than directly programmed numbers - getting the same result):
Code: QB64: [Select]
  1. finishseconds! = 39818.38
  2. startseconds! = 34200.77
  3. PRINT finishseconds! - startseconds!

I should get 5617.61 but for some reason I'm getting 5617.609.

Why and how can I be sure to get the exact result I need? Should I use a different type of number?

I'm using type ! because the numbers could possibly go up to 86400 (number of seconds in 24 hours) and I need 2 decimal point accuracy (as that's what my timing system outputs).

Is there a better option, or alternatively, how can I make sure the answer always comes back rounded up to 5617.61?

Thanks

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: Maths accuracy
« Reply #1 on: May 26, 2021, 08:11:36 am »
I'm of the opinion that QB64 does have some sort of rounding error.

In limiting things to 2 decimal places I generally use:
Code: QB64: [Select]
  1. rounded_result = INT(number * 100) / 100
  2.  

but even that doesn't work for some computations in QB64. Which I find inconceivable. How could INT not work to limit decimal places?

When it really shows its tail is when it resorts to exponential notation, at least you're just getting a third decimal place.

I hope someone figures it out and fixes it one day.

Offline JohnUKresults

  • Newbie
  • Posts: 40
    • View Profile
Re: Maths accuracy
« Reply #2 on: May 26, 2021, 08:59:23 am »
Hi. Thanks for the reply.

For the moment I've had to resort to a somewhat Heath Robinson method to get this sorted, as this 3 decimal place issue could cause cumulative timing issues when fractions get added together (or subtracted)

This is what I'm currently doing. There must be a more elegant solution out there!

Firststartsec$ is a variable used to store the seconds equivalent of the first runner's time of day start time crossing the start line.

Subtracting a runner's finish time from that first start gives a gun elapsed time (as opposed to their personal time which is from when they crossed the start line until they finished)

Code: QB64: [Select]
  1.                finishseconds& = finishseconds! * 100
  2.                firststartsecs& = VAL(firststartsecs$) * 100
  3.                guntime& = finishseconds& - firststartsecs&
  4.                result$ = _TRIM$(STR$(guntime&))
  5.                length = LEN(result$)
  6.                sec1$ = LEFT$(result$, length - 2)
  7.                sec2$ = RIGHT$(result$, 2)
  8.                elapsed$ = sec1$ + "." + sec2$

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: Maths accuracy
« Reply #3 on: May 26, 2021, 09:25:15 am »
I'm wondering if it's related to a problem that was discussed here:

https://www.qb64.org/forum/index.php?topic=3852.0

In that case, I believe, it was concerning printing values, and not the values themselves.

PRINT USING "#####.##" might help with outputting results, but I'm not sure it will help with data accuracy

EDIT:
You might also try:
Code: QB64: [Select]
  1. rounded_result = _ROUND(number * 100) / 100
  2.  
« Last Edit: May 26, 2021, 09:32:17 am by OldMoses »

FellippeHeitor

  • Guest
Re: Maths accuracy
« Reply #4 on: May 26, 2021, 10:56:50 am »
I'm of the opinion that QB64 does have some sort of rounding error.

Floating point math is at play, guys. Please look it up (I'd explain it further, but I don't know it as deeply. What I do know is that we, as a whole, do not easily grasp what is going on when floating point math is at play, but the answer is always: read up on floating point math).

For reference (mandatory read, everyone... I post it often): https://0.30000000000000004.com

  [ You are not allowed to view this attachment ]  

Please do not limit yourselves to the screenshot above. Do actually go to https://0.30000000000000004.com, read it up, follow the links in there, read them up.
« Last Edit: May 26, 2021, 10:58:28 am by FellippeHeitor »

Offline JohnUKresults

  • Newbie
  • Posts: 40
    • View Profile
Re: Maths accuracy
« Reply #5 on: May 26, 2021, 11:19:22 am »
Thanks Felippe

That's all very interesting, but it doesn't answer the question as to whether anyone has a solution that they have tried and works :)

If it can't be done then I'll stick with my Heath Robinson fix. Just slows things down a bit when doing 5000 to 6000 calculations, but it's only a few seconds in the grand scheme of things!

Cheers

Offline jack

  • Seasoned Forum Regular
  • Posts: 408
    • View Profile
Re: Maths accuracy
« Reply #6 on: May 26, 2021, 12:24:11 pm »
you could use a decimal math library but it's use would be cumbersome without function/operator overloading

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Maths accuracy
« Reply #7 on: May 26, 2021, 12:30:02 pm »
@JohnUKresults,

What is the Heath Robinson fix?

Is it like the @SMcNeill fix for scientific notation? Removes it!
https://www.qb64.org/forum/index.php?topic=1511.msg122690#msg122690
« Last Edit: May 26, 2021, 12:34:21 pm by bplus »

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Maths accuracy
« Reply #8 on: May 26, 2021, 12:31:52 pm »
Hi all

Trying to do this (although with figures from an array rather than directly programmed numbers - getting the same result):
Code: QB64: [Select]
  1. finishseconds! = 39818.38
  2. startseconds! = 34200.77
  3. PRINT finishseconds! - startseconds!

I should get 5617.61 but for some reason I'm getting 5617.609.

Why and how can I be sure to get the exact result I need? Should I use a different type of number?

I'm using type ! because the numbers could possibly go up to 86400 (number of seconds in 24 hours) and I need 2 decimal point accuracy (as that's what my timing system outputs).

Is there a better option, or alternatively, how can I make sure the answer always comes back rounded up to 5617.61?

Thanks

As Fellippe says, what you're seeing is the natural effect of binary math.  We see the same result with decimal math (what we normally use, which is base-10), but folks don't quite comprehend why it pops up like it does in binary math.   Let me break down what's happening for you, really simply:

In decimal-math, what is 1/3?  It's 0.333333333333333333333333333333333..... with more threes to infinity.  There is no perfect decimal representation of 1/10.  At some point, we have to have a break off point for precision -- whatever we decide we need it to be, whether that's 2 digits(.33), 6 digits (.333333), or 12 digits (.333333333333).   We can NEVER truly represent 1/10 as a decimal, so for practicality's sake, we break it off at some point.

But what happens when we take that 1/10, store it as a decimal, and then multiply it by 3?   1/3 * 3 = 1, and everyone knows that.  But if we were to write it down on paper, we'd work the math right to left and get .333333 * 3 = .999999...  The more precision we have, the closer we come to the proper answer of 1, but we'll always be just a weeee bit short from it.

Which is why computers tend to calculate figures to one place past their precision level and then round.  .333333 (if we use a 6 digit precision limit) * 3 actually becomes .9999999, with 7 digits of precision, and then it rounds up to 1, to try and reduce errors as much as possible.  Many times, it works just fine, but sometimes it doesn't and values get off by a small fraction -- especially when calculating values inside loops.  A .000001 difference looped 100000 times could make your answer be off by several points.

And this is why, when coding with real precision numbers, you shouldn't make IF checks absolute. 

IF x / y = 0.1 THEN...    This is very likely going to fail for you, as x / y might end up being 0.09999999999874 instead of 0.1.

The best way to do these type of checks with real variable types is to do them based upon a natural level of variance:

If ABS(x / y - 0.1) < 0.00001 THEN...   Here we're saying that as long as our value is *almost* 0.1, then we're going to allow it and just work with it as a rounding error from the floating point math.



Now, why do we see this type of error when we do with binary math?   Even something as simple as 1/10 can end up glitching out on us, with our PC!!

Think of binary math, which is base-2 values.  Let's count from 0 to 10 in binary:

0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010

So that's 0 to 10, represented in binary values.  But what are fractional values?

Just like with decimal math, they're represented on the right side of the period:

0000.1000 = 1/2
0000.0100 = 1/4
0000.0010 = 1/8
0000.0001 = 1/16

But the problem here is that just like how we can't represent 1/3 in decimal math, there's no perfect representation of 1/10.  The more precision we use, the closer we can come to representing 1/10, but we'll never be able to do it.  You just can't represent 1/10 with binary numbers.



So what's the work around?

If one is talking about two digit values, such as with banks and other organizations which deal with money, they simply NEVER use decimals.  The bank doesn't count the number of dollars you have with them; they count the number of pennies!!  Your cash isn't stored as $123.32; it's stored as 12332 pennies and then always processed as INTEGER values.  Integer math is limited in scope, with much smaller numbers, but it doesn't have a lack of precision.  As long as the values you're dealing with are less than 10^15th power, or whatever, you shouldn't have any type of issues calculating them.

Swapping over to integer math might be the answer you seek.

From the minor example you posted though, I'd think the easiest solution in this case is simply to use PRINT using to force a rounding to 2-digit precision.

Code: [Select]
finishseconds! = 39818.38
startseconds! = 34200.77
PRINT USING "#####.##"; finishseconds! - startseconds!

If that doesn't work for you, then you can always swap over to using string math for your calculations, and then you can set the level of precision to whatever point you need it to become, but that's a complete different topic for another day....



The overall end result of what you're seeing is just the natural state of computers and binary math.  If precision has to be absolute, use INTEGER values instead of REAL values -- count pennies instead of dollars.    If it's just a matter of, "...BUT IT LOOKS FUNKY!!", then fix that with PRINT USING and format it to a point where you find it visually pleasing and suitable for your needs.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline JohnUKresults

  • Newbie
  • Posts: 40
    • View Profile
Re: Maths accuracy
« Reply #9 on: May 26, 2021, 12:39:06 pm »
Thanks

Appreciate all of that detailed explanation, but it's not for printing (hat would be simple), it's for storage and then manipulation, so I'll have to work with the integer method and string slicing I outlined earlier in my post:

Code: QB64: [Select]
  1.                finishseconds& = finishseconds! * 100
  2.                firststartsecs& = VAL(firststartsecs$) * 100
  3.                guntime& = finishseconds& - firststartsecs&
  4.                result$ = _TRIM$(STR$(guntime&))
  5.                length = LEN(result$)
  6.                sec1$ = LEFT$(result$, length - 2)
  7.                sec2$ = RIGHT$(result$, 2)
  8.                elapsed$ = sec1$ + "." + sec2$

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: Maths accuracy
« Reply #10 on: May 26, 2021, 12:50:06 pm »
OK, it is the 'nature of the beast' as they say.

So if I try to force a decimal place with:

INT(number * 100) / 100     ' or _ROUND, etc.

then am I just reintroducing the same floating point error again when dividing by 100?

It would seem then that PRINT USING is the only answer.


Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Maths accuracy
« Reply #11 on: May 26, 2021, 01:15:22 pm »
Oh Heath Robinson is UK's version of Rube Goldberg LOL!

Hey accountants don't even use decimal point everything is in pennies, don't know what Heath Robinson uses for pennies in UK but you could just do everything in Long 100th's of a Second and forget about Floating Point Math or whatever they call that in UK LOL!

What's more elegant than no bloody dots! or do you guys use commas?
« Last Edit: May 26, 2021, 01:18:09 pm by bplus »

Offline moises1953

  • Newbie
  • Posts: 55
    • View Profile
Re: Maths accuracy
« Reply #12 on: May 26, 2021, 01:17:58 pm »
As FellippeHeitor has brilliantly put it, the precision of binary floating point representation has its limitations.

For the precise representation of times it is better to use double precision or if it is enough milliseconds in LONG, which lasts for tens of years.

Test this:
Code: QB64: [Select]
  1. sectimer# = TIMER(0.001)
  2. milisectimer& = sectimer# * 1000
  3.  
  4. secini# = 34200.77
  5. secfin# = 39818.38
  6.  
  7. milisecini& = secini# * 1000
  8. milisecfin& = secfin# * 1000
  9.  
  10. PRINT USING " #####.###    "; secini#, secfin#, sectimer#
  11. PRINT milisecini&, milisecfin&, milisectimer&
  12.  
  13.  
« Last Edit: May 26, 2021, 01:19:12 pm by moises1953 »

Offline George McGinn

  • Global Moderator
  • Forum Regular
  • Posts: 210
    • View Profile
    • Resume
Re: Maths accuracy
« Reply #13 on: May 26, 2021, 02:03:32 pm »
About 3 years back (maybe more) I had this issue on mobile devices, except it was worse. Instead of accuracy to 1^308 I was getting 2^59! I was developing Apps for the space sciences, and I ran afoul of the very limitations on mathematical precision.

So a friend and I developed string math routines, that were accurate to 100 decimal places! For division, which was one of the routines I wrote, it's accurate to infinity (can be, but you can specify any number of decimal places).

I can ask my friend if it is OK to convert these routines to either QB64 or create C++ code that may be included in either a header file for use when needing precision beyond the limitations of the math hardware co-processor. Also, we did benchmark time tests, and we found that the division runs faster than the software/hardware combo developed by Intel (division is done on the system level using software and the math co-processor chip).

Since it is written in SmartBASIC on mobile devices, it will take some effort to convert it so it works here.

If interested, let me know.
____________________________________________________________________
George McGinn
Theoretical/Applied Computer Scientist
Member: IEEE, IEEE Computer Society
Technical Council on Software Engineering
IEEE Standards Association
American Association for the Advancement of Science (AAAS)

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Maths accuracy
« Reply #14 on: May 26, 2021, 02:26:44 pm »
Hey @George McGinn

I have String Math here:
https://www.qb64.org/forum/index.php?topic=2921.msg121886#msg121886

Somewhere in toolbox here:
https://www.qb64.org/forum/index.php?topic=1511.msg107143#msg107143

And my oh Interpreter uses it:
https://www.qb64.org/forum/index.php?topic=3723.msg130797#msg130797

Basic +-*/ and Inverse to get 1 / any Integer to how many ever places you like, default for divide is 100 digits but  mult numerator by inverse of denominator gives that precision at least.

Can work out powers and trig but no one has called for it.