Author Topic: Calculation difference between QB64 64 and 32 bit versions  (Read 6963 times)

0 Members and 1 Guest are viewing this topic.

Offline zaadstra

  • Newbie
  • Posts: 78
    • View Profile
Calculation difference between QB64 64 and 32 bit versions
« on: September 30, 2021, 10:13:42 am »
Hi,

Writing a little epoch calculator, I ran into an issue which looked like rounding.  However, the suspected part was a muliplication of huge numbers.

Inspired by this topic https://www.qb64.org/forum/index.php?topic=4210.0 "Possible error in INTEGER64 calculations",  I changed a variable in the suspected calculation to _INTEGER64 and got it working.
When noticing that I still was in QB64-32 I switched to QB64-64 and recompiled. And there the bug was back. I got it woriking by DIMming all variables to _INTEGER64.

Code: QB64: [Select]
  1.  
  2. '  2019:02:05 17:50:30
  3. tm$ = "1978:09:30 15:00:00"
  4. 'tm$ = "1970:01:02 00:00:00"
  5.  
  6. 'DIM tm_sec AS _INTEGER64
  7. 'DIM tm_min AS _INTEGER64
  8. 'DIM tm_hour AS _INTEGER64
  9. 'DIM tm_day AS _INTEGER64
  10. 'DIM tm_month AS _INTEGER64
  11. DIM tm_year AS _INTEGER64
  12. 'DIM tm_yday AS _INTEGER64
  13.  
  14. tm_sec = VAL(MID$(tm$, 18, 2))
  15. tm_min = VAL(MID$(tm$, 15, 2))
  16. tm_hour = VAL(MID$(tm$, 12, 2))
  17. tm_day = VAL(MID$(tm$, 9, 2))
  18. tm_month = VAL(MID$(tm$, 6, 2))
  19. tm_year = VAL(MID$(tm$, 1, 4)) - 1900
  20.  
  21. tm_yday = INT(275 * tm_month / 9) - (INT((tm_month + 9) / 12) * (1 + INT((tm_year - 4 * INT(tm_year / 4) + 2) / 3))) + tm_day - 30
  22.  
  23. tm_epoch&& = tm_sec + tm_min * 60 + tm_hour * 3600 + (tm_yday - 1) * 86400 + (tm_year - 70) * 31536000 + INT((tm_year - 69) / 4) * 86400 - INT((tm_year - 1) / 100) * 86400 + INT((tm_year + 299) / 400) * 86400
  24.  
  25. PRINT tm$
  26. PRINT tm_yday ' check: https://www.esrl.noaa.gov/gmd/grad/neubrew/Calendar.jsp?view=DOY&year=2021&col=4
  27. PRINT tm_epoch&& ' check: https://www.epochconverter.com/
  28. 'PRINT
  29. 'PRINT (tm_year - 70) * 31536000&& ' this is the bastard
  30. 'PRINT INT((tm_year - 69) / 4) * 86400
  31. 'PRINT INT((tm_year - 1) / 100)
  32. 'PRINT INT((tm_year + 299) / 400) * 86400

Output QB64-32:
Code: Text: [Select]
  1. 1978:09:30 15:00:00
  2.  273
  3.  276015600

Output QB64-64:
Code: Text: [Select]
  1. 1978:09:30 15:00:00
  2.  273
  3.  276015616

Removing the REMarks on all DIM lines makes QB64-64 print the same correct result as QB64-32.
When all DIM's are REMmed and only the output var tm_epoch is _INTEGER64, both compilers calculate the result wrong by 16.
Both QB64 32 and 64 bit are version 1.5.

Is this some magic, or reversed restrictictions making the smaller bit compiler run more accurate?
What are the common rules and best practices here?

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #1 on: September 30, 2021, 10:16:00 am »
How can you use _Integer64 with only 32 bit version?

Offline zaadstra

  • Newbie
  • Posts: 78
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #2 on: September 30, 2021, 10:32:28 am »
How can you use _Integer64 with only 32 bit version?
Good question, just ask the complier.  It did not complain!  Maybe it works less efficient but it just works.

Offline zaadstra

  • Newbie
  • Posts: 78
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #3 on: October 01, 2021, 04:24:01 pm »
But .... any serious answer?

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #4 on: October 01, 2021, 04:37:48 pm »
Do any of the sub calculations take the number of digits over say 13 digits (conservative, maybe 14 digits for some numbers) that's when rounding errors for an _Integer64 number start screwing up a calculation). Rounding is a place where 32 bit compiler may differ with 64 bit.

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #5 on: October 01, 2021, 04:46:08 pm »
My bet would be if you ran a 32-bit system alongside a 64-bit system, you'd start seeing rounding differences between the two. Maybe we need an _INTEGER32 keyword. Ah screw it. Let's look to the future and create an _INTEGER128 keyword, instead.

Pete

If you live in the past, we've already seen your future.
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #6 on: October 01, 2021, 05:12:37 pm »
What’s odd about a 32-bit system having 64-bit integers for math?  Doesn’t it also have 64-bit DOUBLE and 256-bit _FLOATs?

There’s no 64-bit OFFSETs in a 32-bit OS, but there’s no problem with data structures being larger than that.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline jack

  • Seasoned Forum Regular
  • Posts: 408
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #7 on: October 01, 2021, 05:42:20 pm »
don't variables default to single if not specified?
all your variables except tm$ and tm_year are whatever the default type is, not good practice in my opinion
btw, the correct result should be 276015600 so the 64-bit version as posted is wrong

Offline jack

  • Seasoned Forum Regular
  • Posts: 408
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #8 on: October 01, 2021, 06:01:28 pm »
correction, epoch is defined as integer64
but having all the other variable as single is a bad idea, if you put a DefInt T at the top then the 64-bit version reports the correct value

Offline zaadstra

  • Newbie
  • Posts: 78
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #9 on: October 02, 2021, 04:17:08 am »
Indeed 276015600 is correct so the 64 bit is wrong.  As the code is the same I'm surprised.  If 64 bit as correct and 32 wasn't, you could have blamed the bits....
As I'm using the 64 bit version most of the times, I'm worried there is a little quirk in that version.

Usually I'm using single so no need for a Dim.  Only when very large numbers (or fractions) are expected.
I can't imagine where rounding occurs when only multiplying integer numbers.

It's also possible that calculations with different data type variables behave odd, but I've never seen rules about that.
It may be that the 64 bit version is a bit more strict in mixing variable types?

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #10 on: October 02, 2021, 05:11:08 am »
There’s a lot of possible reasons why math can run into issues on various architecture.  Let me illustrate a very simple one:

Pretend you’re working on a system that uses 8-bit math registers.  Now, you want to find the center point between two numbers on a line.  (Point 100, Point 200, and Centerpoint is??)

A = 100
B = 200
C = (A + B) / 2

You run the above and it gives you an impossible answer like 23…

Now that’s obviously flipping wrong!!

So what happened??

Intermediary register overflow!!

You’re using 8-bit math registers, remember??  They store values from 0 to 255, and your answer is 150, so they should be fine.  Right?

WRONG!!

Your PC is processing things one step at a time, so it has to store the results in a temporary register to continue working on your multi-step math problem.

In this case, it does A + B first and stores the result…. 100 + 200 = 300…. INTERNAL OVERFLOW ERROR!!!  Before you can even get to divide by 2, you’ve already performed an overflow calculation!

Now, what’s the solution here??

Change your math formula: C = A + (B - A) / 2

B - A is 200 - 100,..  valid for 8-bit math registers.
100 / 2 = 50… valid
100 + 50 = 150… valid

YAYYY!!  We can calculate the answer without any overflow!!



Now, I *assume* you’re using the stable version of QB64.  V 1.5, I think it is?

In it:
QB64’s 32-bit version defaults to using x86 Floating point math registers.
QB64’s 64-bit version defaults to using 64-bit SSE math registers.

X86 FPU math is 80-bit precision.
64 bit SSE math is 64-bits precision.

And THAT explains the issue you’re seeing with the difference in the two systems.  Without changing your variable type, it defaults to SINGLE.

In 32-bit QB64, you have 80 bits for your math precision with floating point values.
In 64-bit QB64, you have 64 bits for your math precision with floating point values.

64-bit SSE registers won’t hold the value you’re calculating, so you get rounding issues.

Solution??

As you’ve found yourself — define your variables as INTEGER types, and the problem goes away.

And that’s basically the whole mystery behind the “glitch” explained.  It’s not a QB64 issue itself; it’s a basic difference in the amount of precision your PCs architecture provides.

(And the reason why I assume you’re using the stable V 1.5?  The Development build should now have both 32-bit and 64-bit versions defaulting to 80-bit FPU math.  It’s slower on 64-bit systems, basically doing your math in the CPU instead of the dedicated SSE math processors, but it *should* keep results consistent between 32-bit and 64-bit versions, and prevent issues like these.)
« Last Edit: October 02, 2021, 05:16:46 am by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline luke

  • Administrator
  • Seasoned Forum Regular
  • Posts: 324
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #11 on: October 02, 2021, 06:28:16 am »
Or to distill your issue down to a few lines:
Code: QB64: [Select]
  1. a = 23554800
  2. b = 252460800
  3. m&& = a + b
  4. Print m&&
  5.  

Since a & b are both Single, the addition is done to Single precision. The correct answer is 276015600, but that number can't be represented as a Single: the nearest possible values are 276015584 and 276015616. The FPU rounds up*, and finally the value is cast to an Integer64 for storage in m&&. That all makes sense, and fully explains why we're getting the wrong result.

The question, then, is why does QB64-32 get the answer right?

* Technically it rounds to nearest, preferring the value with an even mantissa to break ties

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #12 on: October 02, 2021, 06:37:47 am »

The question, then, is why does QB64-32 get the answer right?

80-bit FPU math precision vs 64-bit SSE math precision.  Those extra 16-bits are enough to store the value without rounding.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline zaadstra

  • Newbie
  • Posts: 78
    • View Profile
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #13 on: October 02, 2021, 07:26:46 am »
Thanks for all the insights!

Indeed I'm using the v1.5 release.

Now let's see.... that big number in bytes (by google):
276 015 600 =
0b10000011100111010100111110000

That is 'only' 29 bits so easily should fit in all scenario's!

Looking back in the old code,  I suspected this part "(tm_year - 70) * 31536000" which gave as result (separated printed out):
 2.52288E+08
The scientific notation did raise my eyebrows so I started changing the prog in the first place.

Checking that out, 1978 * 31536000 = 62.378.208.000, which is
0b111010000110000001111110101100000000

Now we're talking of 36 bits.  Bigger than 32 bits but it still fits in 64 bit FP or 80 bit SSE!

Looking at the storage for a Single var, which is 4 bytes which is 32 bits.  Sure that doesn't fit.

Here's the part where I probably went wrong.  I assumed that the calculation registers are big enough for 'everything' and the var to be stored in limits you to good or overflowed storage.  The _Integer64 would be fine.
@SMcNeill 's  explanation of 80bit FP for x86 and 64bit for x64 calculations confirms this idea.  But the dark magic under the hood thinks different ;-)

So is the compiiler breaking up calculatings, and does it execute calcultations with Single var's in a smaller register?  Hence the rounding as @luke illustrates?  Just trying to understand, to be able to prevent these nasty coding errors.

Some tests:

Code: QB64: [Select]
  1. DIM tm_year AS _INTEGER64
  2. tm_year = 1978
  3. a = (tm_year - 70) * 31536000
  4. PRINT a ' prints scientific number
  5. PRINT (tm_year - 70) * 31536000 ' correct (only if DIM is present)
  6. a&& = (tm_year - 70) * 31536000
  7. PRINT a&& ' correct
  8. b&& = a
  9. PRINT b&& ' prints rounded a
  10.  

Output:

Code: Text: [Select]
  1.  6.017069E+10
  2.  60170688000
  3.  60170688000
  4.  60170686464

Note: Hopefully the new version gets a switch to force use of the SSE instructions, because "We have a need, a need for speed!" :-)

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Calculation difference between QB64 64 and 32 bit versions
« Reply #14 on: October 02, 2021, 08:06:36 am »
A quick example of how to change floating point precision in QB64. This swaps between quick math (which uses hardware math processors) to extended precision math (which uses software processing). Note that the default qbfpu is quite a bit faster, and for most folks this should be more than sufficient for your needs, as it tracks precision down to about the 15th decimal point. IF, however, you absolutely have to have greater levels of precision, you can now swap over to extended precision and have about 20 decimal points worth of precision, at a significant reduction of speed.

And, if you need more than 20 decimal points of precision, you're just shit out of luck. Find a math library for that, or else write a string math handling routine -- your CPU isn't equipped to handle anything more than this, natively.


FPU_Precision.h
Code: [Select]
void set_dpfpu() { unsigned int mode = 0x37F; asm ("fldcw %0" : : "m" (*&mode));}
void set_qbfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode));}


QB64 Code:

Code: QB64: [Select]
  1. ' FPU_Precision.h needs to be in QB64 folder
  2. DECLARE CUSTOMTYPE LIBRARY ".\FPU_Precision"
  3.     SUB set_dpfpu 'to toggle to double precision floating point math
  4.     SUB set_qbfpu 'to toggle back to what most folks will see with QB64 64-bit default math
  5.  
  6.  
  7.  
  8. 'Let's print our results without screwing with anything first.
  9. x = 5##
  10. y = x / 9##
  11. PRINT USING "QB64 division       #.####################"; y
  12.  
  13.  
  14. 'Set the double precision math
  15. set_dpfpu
  16. x = 5##
  17. y = x / 9##
  18. PRINT USING "QB64 division       #.####################"; y
  19.  
  20. 'Set the QB64 precision math
  21. set_qbfpu
  22. x = 5##
  23. y = x / 9##
  24. PRINT USING "QB64 division       #.####################"; y
  25.  

With the above header in your QB64 directory, you can use DECLARE LIBRARY to toggle between FPU and SSE processing as neded.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!