QB64.org Forum

Active Forums => QB64 Discussion => Topic started by: NOVARSEG on June 14, 2021, 12:01:35 am

Title: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 14, 2021, 12:01:35 am
This is run on the previous version of QB64

E prints out as 1983905792,   it should be 16000000000  (16 billion)

E is an _UNSIGNED _INTEGER64 variable, it should be able to store 16 billion easily, as it maxes out at 18 billion or so.

is this thread related  https://www.qb64.org/forum/index.php?topic=3636.msg129794#msg129794

and here more problems with  _INTEGER64

https://www.qb64.org/forum/index.php?topic=2070.msg112998#msg112998

and here even more problems   https://www.qb64.org/forum/index.php?topic=1237.msg104539#msg104539

so i'm not imagining it.  _INTEGER64  is broken!!!!

I imagine that some spagetti code needs to be cleaned up in the compiler.

Anyone on the thread run the code yet?

Quote
DIM A AS _UNSIGNED LONG
DIM b AS _UNSIGNED INTEGER
DIM c AS _UNSIGNED INTEGER

DIM E AS _UNSIGNED _INTEGER64
DIM F AS _UNSIGNED LONG
DIM G AS _UNSIGNED LONG

b = 40000
c = 40000

A = b * c

PRINT A

F = 4000000000
G = 4000000000
E = F * G

PRINT E
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: SMcNeill on June 14, 2021, 01:47:26 am
Quote
E prints out as 1983905792,   it should be 16000000000  (16 billion)

Shouldn’t it be 16,000,000,000,000,000,000?  That’s quite a bit larger than 16 billion…

The issue here, I imagine, is that you’re compiling with the 64-bit version.  32-bit QB64 uses 32-bit math registers, which gives us the limits posted in the wiki.  64-bit QB64 uses 64-bit math registers, which actually gives us lower limits of precision and a smaller variable range.

I’ve written about this before — it’s all in the way our gcc compiler does the math on 64-bit machines; it’s nothing in QB64 itself, nor any “glitch” which we can fix.  You might try ripping out the compiler we package and putting in a newer version, and see if the gcc guys have altered the behavior of the compiler, but this issue is simply out of our hands.

Easiest fix?  Just manually set your compiler to use the FPU math processors.  I’ve posted how a dozen times, so just search the forums for the code.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 14, 2021, 02:08:44 am
Steve

 you are correct (again)

18,446,744,073,709,551,615 max
16,000,000,000,000,000,000

So my comp is 32 bit.   OK  trying to simulate product of 2 _UNSIGNED LONG variables.

so the code  I posted should work on 64 bit machines.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: Richard on June 14, 2021, 02:27:28 am
@NOVARSEG
@SMcNeill

I think the code below speaks for itself

Code: QB64: [Select]
  1. f~& = 4000000000
  2. g~& = 4000000000
  3. e~&& = f~& * g~&
  4. PRINT USING "###,###,###,###,###,###,###"; e~&&
  5.  
  6. f~& = 4000000000
  7. g~& = 4000000000
  8. e~&& = 1~&& * f~& * g~&
  9. PRINT USING "###,###,###,###,###,###,###"; e~&&
  10.  
  11. f~&& = 4000000000
  12. g~&& = 4000000000
  13. e~&& = f~&& * g~&&
  14. PRINT USING "###,###,###,###,###,###,###"; e~&&
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: luke on June 14, 2021, 02:33:06 am
No, this has nothing to do with 32 vs 64 bit versions - you get the same result with either.

What you're seeing is a side-effect of the rules for evaluating expressions, namely that the type at which an expression is evaluated depends on the inputs only, not the type of the variable that will hold the result.

This means when you multiply two Longs, you're asking the computer to do a 32 x 32 bit multiplication, which gives a 32 bit result. There is no provision in the language for the compiler to infer that you wanted a 64 bit multiplication because you're assigning the result to a 64 bit variable. Indeed in some cases it would be impossible to make such as inference, e.g. PRINT F * G.

What you want, then, is a way to ask the computer to perform a 64 bit multiplication. There are two ways:

1) Make one (or both) of your F, G variables an unsigned integer64. You'll then be asking for a 64 x 32 bit multiplication, which will cause the 32 bit value to be extended to 64 bits for a 64 x 64 bit multiplication, giving the expected result.

2) Pre-multiply by 1~&&, i.e. E = 1~&& * F * G. Similar to above, 1~&& is a 64 bit value which means the first multiplication gets done with 64 bits to result in 64 bits, which in turn causes the same behaviour with the seconds multiplication. It is important to use a pre-multiplication not post-multiplication (E = F * G * 1~&&) because by then it will be too late, and your result will already have been truncated.

EDIT: Ninja'd by RIchard - his code shows the options described above.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: Richard on June 14, 2021, 02:46:19 am
OR

Code: QB64: [Select]
  1. f~& = 4000000000
  2. g~& = 4000000000
  3. e~&& = CDBL(f~&) * g~&
  4. PRINT USING "###,###,###,###,###,###,###"; e~&&
  5.  
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 14, 2021, 03:38:56 am
from https://fruttenboel.verhoeven272.nl/asm/a86man.html



F7 /4  MUL rmv   Unsigned multiply (eDXeAX = eAX * regmem vword)

The Intel opcodes for multiply show that when two longs are multiplied, the result is in EDX and EAX.  This is because the CPU knows the result won't fit in a 32 bit register.  I'm sure the compiler could store EDX and EAX in a 64 bit register if it were available.

Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: luke on June 14, 2021, 03:40:31 am
That jumps to floating-point operations though, which is a little annoying. It's a shame there's no _CINT64() function.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 14, 2021, 04:19:55 am
from https://www.felixcloutier.com/x86/mul

If the CPU has 64 bit registers  (RAX etc)

REX.W + F7 /4   MUL r/m64 Valid N.E.   Unsigned multiply (RDX:RAX ← RAX ∗ r/m64).

from https://www.cs.uaf.edu/2017/fall/cs301/lecture/09_11_registers.html
rax is the 64-bit,  (8 byte) size register.  It was added in 2003 during the transition to 64-bit processors.


 ( not tested)  Ok when the compiler sees E = F * G it should code

mov [temp +4],edx   ;most significant of F * G
mov [temp],eax   ;least significant of F * G
mov rax, [temp]  ;64 bit result.  E

but the compiler code  is ignoring edx and eax 

from above

RDX:RAX ← RAX ∗ r/m64).

this shows that if the CPU result of the multiply is in RDX:RAX then the multiplicands can not be 32 bit.  Is this rule compiler is following?  But two 32 bit value can be multiplied  by the CPU to give a 64 bit value.




Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 14, 2021, 09:45:40 pm
@luke

Quote
Indeed in some cases it would be impossible to make such as inference, e.g. PRINT F * G.

PRINT F * G should work.

both F and G are unsigned longs and  the CPU actually does the multiplication properly.
 The CPU puts the product in EDX:EAX.  The compiler is ignoring EDX:EAX.

When the compiler sees PRINT  F * G it could code

mov [temp +4],edx   ;most significant of F * G
mov [temp],eax   ;least significant of F * G
PRINT [temp]  ;64 bit result.  ;  code simplified

For PRINT F * G  QB64 looks to be reading the EAX register only
 which is the least significant 4 bytes of the product F * G
example

F = 65536
G = 65536

PRINT F * G = 0 ' the largest value EAX can hold is 2 ^ 32 - 1
but EAX wraps around to all zeros (showing a carry to EDX) when it try's to
store 2 ^ 32

F = 65536
G = 65537

PRINT F * G = 65536 '=  2 ^ 32 - F * G

If QB64 compiler read EDX:EAX it would get

PRINT F * G correct.

So what we have now is work arounds, kind of a band-aid approach to programming.

Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: luke on June 14, 2021, 09:48:20 pm
I think you misunderstand me: this is by design. There is nothing to fix.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 14, 2021, 09:50:59 pm
The code was deliberately designed to give the wrong answer.

The print out of the product of 2 unsigned longs should be trivial.   How is anyone new to Qb64 (or any other basic with this problem) to know that there are reasons why they can't program in the way that seems most logical?  Computers are logical machines.

The CPU puts the product of 2 unsigned longs in EDX:EAX.   That is what QB64 should print out.  If it can't do that then the compiler needs to be fixed.

Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: NOVARSEG on June 16, 2021, 12:04:44 am
DIM c AS _UNSIGNED INTEGER

c = 65537
PRINT c

= 1

It is NOT possible to store 65536 or greater in an _UNSIGNED INTEGER. I think this was the case with QB45.   Looks like there is some compiler code that does a subtraction first and then the difference is put into c.

65537 - 65536 = 1

Well, is this an advantage or disadvantage?  For a student learning computer science this is a disadvantage because it is hiding the real operation of a 16 bit register.  The compiler should NOT allow c = 65536 or greater

Basically the compiler is 2nd guessing what the programmer thinks should happen.  Ok you have a value of 65535 in a 16 bit register, now add 1
65535 + 1 = 0     you don't need the compiler to 2nd guess what you already know will happen.

What about things like "subscript out of range" error?  The error is there to alert the programmer the code is incorrect.

c = 65536 is also "out of range" 
****************************************
Ok I'm taking a break from computing for a while

I'm too grumpy right now.





Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: bplus on June 16, 2021, 11:36:56 am
Code: QB64: [Select]
  1.  
  2. For c = 65534 To 65536
  3.     Print c
  4.  
  5.  
  6.  

LOL
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: FellippeHeitor on June 16, 2021, 11:46:03 am
From "Unsigned integers, and why to avoid them" - https://www.learncpp.com/cpp-tutorial/unsigned-integers-and-why-to-avoid-them/
Quote
Any number bigger than the largest number representable by the type simply “wraps around” (sometimes called “modulo wrapping”, or more obscurely, “saturation”)

From "Integer overflow" - https://en.wikipedia.org/wiki/Integer_overflow
Quote
In C, unsigned integer overflow is defined to wrap around, while signed integer overflow causes undefined behavior

You guys have a point there?
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: SMcNeill on June 16, 2021, 10:50:35 pm
This is expected behavior, and it’s really simple to understand, if you look at binary numbers.

1 is 00000001 in binary.
255 is 11111111 in binary.

Let’s do the math and add those:

 00000001
+11111111
—————————
100000000

Now, if we’re storing the answer in an _UNSIGNED BYTE, we take 8 bits for it.  The last 8 bits are 00000000.

1 + 256 = 0 (If we’re using bytes to hold the answer.)

The process is just that simple.

Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: RhoSigma on June 17, 2021, 02:35:23 am
There's definetly something wrong with _UNSIGNED _INTEGER64, I already noticed it 2 years back here https://www.qb64.org/forum/index.php?topic=1084.msg102794#msg102794

I did also stumble over it when making the &B additions to the last two realease versions, but didn't change anything on it, as the problem seems to be scattered across several functions and not easy to spot entirely.

In some situations, and only with _INTEGER64, the _UNSIGNED condition seemes to be simply ignored and so it will only allow the signed range, loosing half of the possible unsigned values.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: SMcNeill on June 17, 2021, 05:20:50 am
There's definetly something wrong with _UNSIGNED _INTEGER64, I already noticed it 2 years back here https://www.qb64.org/forum/index.php?topic=1084.msg102794#msg102794

I did also stumble over it when making the &B additions to the last two realease versions, but didn't change anything on it, as the problem seems to be scattered across several functions and not easy to spot entirely.

In some situations, and only with _INTEGER64, the _UNSIGNED condition seemes to be simply ignored and so it will only allow the signed range, loosing half of the possible unsigned values.

There's also the issue of how things behave depending on which set of math registers are used -- the x64 ones or the x86 set.  You might always try swapping over to the x86 set and see if it gives you the results you're expecting on your 64-bit machine.

Code: QB64: [Select]
  1.     SUB set_dpfpu 'to toggle to double precision floating point math
  2.     SUB set_qbfpu 'to toggle back to what most folks will see with QB64 64-bit default math
  3. z = 9223372036854775807 ' This is the biggest allowed number of this type.
  4. '                         (Only here for reference.)
  5.  
  6. a = (2 ^ 55) - 1 '          This number should be odd.
  7. b = 36028797018963967 '   The number above should equal this one.
  8. PRINT "-------------------------"
  9.  
  10. set_dpfpu
  11. a = (2 ^ 55) - 1 '          This number should be odd.
  12. b = 36028797018963967 '   The number above should equal this one.
  13. PRINT "-------------------------"
  14.  
  15. set_qbfpu 'Just to showcase that this is what we get in "normal" QB64-mode math, on a windows 64-bit machine.
  16. a = (2 ^ 55) - 1 '          This number should be odd.
  17. b = 36028797018963967 '   The number above should equal this one.
  18.  

  [ This attachment cannot be displayed inline in 'Print Page' view ]  

As you can see from the image above, the answer we get is different solely based on which math units we're running it on.

And honestly, I don't understand this...  I can see why floating point math is affected the way it is, but INTEGER math?  This one was a total shock to me, when STx first showcased code which highlighted the issue.  Now, there might be some simple trick like making that a 2~&& ^ 55~&&, or such, but at the end of the day, just changing from normal precision to double precision can make a large difference, as shown above.
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: luke on June 17, 2021, 10:11:58 am
And honestly, I don't understand this...  I can see why floating point math is affected the way it is, but INTEGER math?  This one was a total shock to me, when STx first showcased code which highlighted the issue.  Now, there might be some simple trick like making that a 2~&& ^ 55~&&, or such, but at the end of the day, just changing from normal precision to double precision can make a large difference, as shown above.

This should be fixed in the latest development build.

What's going on here? There's multiple factors at play. First that that pow() is inherently a 'floaty" function. It always returns a floating point number. In QB64 we use the version that returns a long double (113 bits of precision), which should be enough to hold even a 64 bit integer perfectly. Because pow() is floaty, it means the subtraction is actually a floating-point operation. The resulting floating-point number is then rounded to fit into a 64 bit integer variable.

The x87 FPU can be in several rounding modes. The two of interest are double mode (53 bits of precision) and extended mode (64 bits of precision). This effectively controls how many bits of precision are available in the result of any floating point operation. If the FPU were in double mode, 2 ^ 55 is beyond the range (note it needs 55 bits, we only have 53) in which you can represent each integer (with floating point the available numbers become less dense as you get bigger), so subtracting 1 has no effect on the value. In extended mode all is well because we have the extra bits.

QB64 used what appeared to be a Windows-specific function _controlfp to set the FPU mode. From a brief read of the documentation it appears the extended mode is not supported on x86_64, which doesn't make much sense but is consistent with this working in 32-bit QB64 but not 64-bit QB64. I have changed it to use the fldcw instruction directly.

Side note: Linux by default sets extended mode, which is why this error wasn't visible there. Apparently BSD uses double mode by default, so I guess this is a win for BSD users too?
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: SMcNeill on June 17, 2021, 03:31:00 pm
I have changed it to use the fldcw instruction directly.

This should fix a lot of the precision issues which people like jack have highlighted several times in his work.  In Windows, folks were seeing two different sets of results between the QB64-32bit version and the QB64-64bit version.  It’s nice to see that we’re going back to producing consistent programs again.  ;)
Title: Re: _UNSIGNED _INTEGER64 bug?
Post by: jack on June 17, 2021, 03:42:52 pm
This should fix a lot of the precision issues which people like jack have highlighted several times in his work.
I was hoping for that, but the problem with _Float is still there