BBC BASIC integer arithmetic

Discuss all aspects of programming here. From 8-bit through to modern architectures.
User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

BBC BASIC integer arithmetic

Post by Richard Russell » Sun Sep 02, 2018 2:31 pm

Over in another thread we have been discussing the characteristic of Acorn's BBC BASIC interpreters (shared also by Brandy) which gives rise to this 'interesting' behaviour:

Code: Select all

@% = &A0A
PRINT 2147483650 + 10 : REM prints 2147483660
PRINT 2147483640 + 20 : REM prints -2147483636
Although I have always disliked this feature, and have never been tempted to copy it in 'my' versions of BBC BASIC (Z80, MS-DOS, BB4W, BBCSDL) which all print 2147483660 for both calculations, clearly Acorn designed it that way quite deliberately because all their versions behave the same way. Can anybody offer an explanation for why Acorn/Sophie might have felt that such a (superficially) unhelpful behaviour is desirable?

User avatar
jgharston
Posts: 3566
Joined: Thu Sep 24, 2009 11:22 am
Location: Whitby/Sheffield
Contact:

Re: BBC BASIC integer arithmetic

Post by jgharston » Sun Sep 02, 2018 2:59 pm

That's what PRINT does, but what does A%= do?

I want A%=A%+1 to be able to scroll through all 32-bit values without error. I want
address1%=arbitaryintegervalue
address2%=address1%+arbitaryintegeroffset

to never error and always give address2% as the appropriate offset from address1%.

In other words, I want:
address%=&7FFFFFFE
REPEAT
PROCdo_something(address%)
address%=address%+1
UNTIL end_condition


to correctly go:
7FFFFFFE
7FFFFFFF
80000000
80000001
80000002
80000003
etc.

From memory, CPC BASIC uses 16-bit integers, and it *did* bomb out when attempting to walk through memory from 7Fxx to 80xx. Digging into distant memory I think it was exactly that continuous circularity property that was desired.

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.25
(C) Copyright J.G.Harston 1989,2005-2015
>_

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Sun Sep 02, 2018 3:16 pm

It's certainly a question about types, and type inference. Variables are either float or integer, hex constants are perhaps always integer, but (integer) decimal constants are promoted to float if they are too big for integers.

If you want 32 bit integers to behave themselves, perhaps you need some annotation for integer decimal constants to say what they should be. Or perhaps they should always be floats?? If you want integers, use hex, or stick to small numbers?

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Sun Sep 02, 2018 4:08 pm

BigEd wrote:
Sun Sep 02, 2018 3:16 pm
It's certainly a question about types, and type inference. Variables are either float or integer, hex constants are perhaps always integer, but (integer) decimal constants are promoted to float if they are too big for integers.
Other programming languages, and indeed other BASIC interpreters (including mine), manage to support different numeric types - converting between them when necessary - without exhibiting any similar anomalous behaviour. So whilst what you say is true, I don't think it explains or justifies how Acorn's BASICs behave.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Sun Sep 02, 2018 4:11 pm

jgharston wrote:
Sun Sep 02, 2018 2:59 pm
I want A%=A%+1 to be able to scroll through all 32-bit values without error. I want
address1%=arbitaryintegervalue
address2%=address1%+arbitaryintegeroffset

to never error and always give address2% as the appropriate offset from address1%.
It would hardly be appropriate to design a BASIC interpreter on the basis of the very specific needs of one individual! There may be rare occasions when one might want the kind of 'wrapping' behaviour you describe, but that's no problem because you can easily achieve it with a user-defined function (this is what you would have to do in my versions of BBC BASIC, and probably every other non-Acorn BASIC interpreter):

Code: Select all

A% = FNwrap(A%+1)
address2% = FNwrap(address1%+arbitraryintegeroffset)
DEF FNwrap(a) IF a < -2^31 THEN = a + 2^32 ELSE IF a >= 2^31 THEN = a - 2^32 ELSE = a
In summary:
(1) Wanting it to 'wrap' is surely very much the exception rather than the rule.
(2) Behaving in a way that no ordinary user would expect is an unacceptable price to pay.
(3) If you do want it to wrap, both the statements I listed should wrap.
(4) The wrapping behaviour is very easily achieved with a user-defined function.
Last edited by Richard Russell on Sun Sep 02, 2018 4:29 pm, edited 1 time in total.

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Sun Sep 02, 2018 4:26 pm

It might be interesting to see in a disassembly which parts of the code implement this policy decision. It seems like a decision to offer a 32 bit signed integer type which works somewhat like what you'd get from a 32 bit CPU. That is, it's a low level view, an engineer's view. It's not the view of mathematical integers embedded in real numbers. But then, floats don't quite work like real numbers either.

But it might not have been a policy decision. It might just have fallen out of the code which has to evaluate expressions with mixed types of variables and ambiguous constants. It might be that your exhibited behaviour, while fully determined, was not anticipated.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Sun Sep 02, 2018 5:02 pm

BigEd wrote:
Sun Sep 02, 2018 4:26 pm
That is, it's a low level view, an engineer's view.
You may be right, but I was an engineer too and it never seemed 'correct' to me. :wink:
But it might not have been a policy decision. It might just have fallen out of the code which has to evaluate expressions with mixed types of variables and ambiguous constants.
I wrote my Z80 BBC BASIC interpreter, from scratch, only about a year after Sophie wrote the 6502 version (of course I had the benefit of knowing what I needed to achieve, whilst she only had a vague requirements specification from the BBC). Had it been something that "fell out" of the expression evaluation one might expect it to have fallen out of mine too, but it didn't. :lol:

As I explained in the other thread, it isn't difficult to avoid. One simply has to test for an overflow after a 32-bit addition or subtraction (this is likely to come 'for free' as a CPU flag) and in that event promote the numbers to floats.

dominicbeesley
Posts: 794
Joined: Tue Apr 30, 2013 11:16 am
Contact:

Re: BBC BASIC integer arithmetic

Post by dominicbeesley » Mon Sep 03, 2018 10:07 am

It's not just PRINT that does it, it's the integer parser in the evaluator. I ran across this by accident when porting to the 6809, spent hours chasing "my" bug for it to turn out to be not a "bug" but a feature.

The hex parser doesn't do this, it overflows the integer! i.e.

Code: Select all

A=&80000002
P. A
gives a negative integer that is converted to a float _after_ it has already overflowed during parse/evaluate.

I can't remember the exact order of events but I think it is fairly simple and deterministic. i.e. when _parsing_ the decimal number if it doesn't fit in an integer promote to FP. Everything else is then determined by the type of the item returned by the parser.

What do you do in other versions Richard? If the result doesn't fit promote and try again?

I'd be with JGH in that I'd vote for anything that has both operands as integers _and_ the destination an integer should/must do integer wrapping for address arithmetic which is ubiquitous?

D

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Mon Sep 03, 2018 10:35 am

As a usability note, which is to say a way of documenting ourselves out of a problem, note that

Code: Select all

@% = &A0A
PRINT 2147483650 + 10 : REM prints 2147483660
PRINT 2147483640 + 20 : REM prints -2147483636
REM make numbers floats explicitly
PRINT 2147483650.0 + 10 : REM prints 2147483660
PRINT 2147483640.0 + 20 : REM prints 2147483660
REM or even
PRINT 2147483650 + 10.0 : REM prints 2147483660
PRINT 2147483640 + 20.0 : REM prints 2147483660
So: if you want "numbers" as opposed to "32 bit integers", that's one way to do it.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 11:22 am

BigEd wrote:
Mon Sep 03, 2018 10:35 am
As a usability note, which is to say a way of documenting ourselves out of a problem...
It's not difficult to work around if you know about it and realise that it might affect your particular program. But it's so (superficially) strange and unexpected that it's a lot to ask of an 'ordinary' BASIC programmer to have any awareness of it at all!

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Mon Sep 03, 2018 11:25 am

The direction I was previously heading in was some kind of annotation: like C's "L" suffix or C's casts. But it turns out we already have annotation: put in a decimal point to force a float.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 11:50 am

dominicbeesley wrote:
Mon Sep 03, 2018 10:07 am
I can't remember the exact order of events but I think it is fairly simple and deterministic...
Certainly being "deterministic" is better than not being (one might like to think that everything in BASIC is deterministic!) but that's not much consolation if it breaks your program in such an unexpected way.
What do you do in other versions Richard? If the result doesn't fit promote and try again?
Effectively yes. If a 32-bit addition or subtraction overflows, I simply reverse it (either by restoring the original operands or 'undoing' the calculation by another subtraction/addition), promote the operands to floats and jump to the FP addition or subtraction code.
I'd be with JGH in that I'd vote for anything that has both operands as integers _and_ the destination an integer should/must do integer wrapping for address arithmetic which is ubiquitous?
If the destination is an integer variable, and the result of the calculation is too large to fit, then one has to make a decision how to proceed. One option would be to wrap; at least that is defensible because the integer variable forces something to happen. I chose not to do that in my BASICs because I consider it to be unfriendly to a beginner, who will typically have no understanding of binary numbers and modulo-arithmetic. Instead I issue a 'Number too big' error.

Whether or not wrapping for address arithmetic is "ubiquitous" (and I'm not sure that it is: since when does one allow a memory address to wrap in normal circumstances?) BASIC is very rarely going to be used to perform address arithmetic. In 37 years of programming in BBC BASIC, which must mean countless thousands of programs, I can barely remember ever wanting the 'wrap' behaviour. The only occasion I can think of wasn't anything to do with address arithmetic, but was in the calculation of a standard hash algorithm. In that case I used a FNwrap() function of the kind I listed.

I am personally of the unmovable opinion that when the result is simply PRINTed, or assigned to a floating-point variable, the normal laws of arithmetic must apply to BASIC's expression evaluation. There are simply no circumstances when I think '2147483650 + 10' and '2147483640 + 20' should ever give different results.

Phlamethrower
Posts: 103
Joined: Fri Nov 24, 2017 1:35 pm
Contact:

Re: BBC BASIC integer arithmetic

Post by Phlamethrower » Mon Sep 03, 2018 1:22 pm

Richard Russell wrote:
Sun Sep 02, 2018 2:31 pm
Can anybody offer an explanation for why Acorn/Sophie might have felt that such a (superficially) unhelpful behaviour is desirable?
The best I can think of is that it was born from a desire to emulate the behaviour of the CPU. The 6502 integer addition/subtraction instructions wrap, therefore BASIC's integer arithmetic should also wrap. Plus of course it's quicker to perform addition/subtraction of integer BASIC variables using a short sequence of integer CPU instructions, rather than convert to float, perform the arithmetic, and then (potentially) convert back again.

As BigEd has pointed out, the difference in behaviour between the two lines of code comes from whether the values are parsed as ints or floats (which then controls whether the integer or float arithmetic functions are used).

Wrapping integer arithmetic may be unexpected and potentially dangerous for those who aren't expecting it, but remember that there are a great many number of unexpected and potentially dangerous things that can go wrong with traditional floating point arithmetic as well. Sooner or later the programmer has to learn that these issues exist.

dominicbeesley
Posts: 794
Joined: Tue Apr 30, 2013 11:16 am
Contact:

Re: BBC BASIC integer arithmetic

Post by dominicbeesley » Mon Sep 03, 2018 1:27 pm

I do often do things such as

Code: Select all

X%=&FFFF1900
....
IF TUBE THEN X%=X%+&10000
but that might just be me. However, I don't think that that would be affected by anything you've said.

I bow to your superior judgement of proper behaviour. I suspect that the if overflow, promote both operands then if assigning to an integer truncate (and wrap to negative) would probably satisfy both worlds....checking in BBC Basic for Windows I can't make it NOT do what I want i.e.

Code: Select all

      A=4294908160
      PRINT~A
      A=A+65536
      PRINT~A
give "1900" as does

Code: Select all

      A=&FFFF1900
      PRINT~A
      A=A+&10000
      PRINT~A
However, getting A back into an integer requires some work as the usual assign/INT() give number too big but not a killer.

I just tried this in GCC to see what happens

Code: Select all

#include <stdio.h>
#include <stdlib.h>

int main(void) {

    float a = 4294908160.0;
    a = a + 65536.0;

    printf("%f\n", a);

    int32_t ai = (int)a;

    printf("%X\n", ai);
}
Which gives

Code: Select all

4294973440.000000
80000000
:?: yuk

I suppose in then end it probably boiled down to space/size/speed then. I'd have to dig back into the notes I made on the evaluator to see how/where the check and promote would go but I suspect it would be non-trivial to fit it in without speed/space overhead...or it may have just been an oversight? Or maybe its just the way my mind works I'm used to doing machine language and C and I'm used to the joys and pitfalls of wrapping. Whereas you see it (correctly) from the POV of a BASIC user.

D

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Mon Sep 03, 2018 1:42 pm

It's a nice idea to promote automatically to float, retrospectively, if a calculation overflows an integer. But it will be a little bit slower, and not compatible - so, one can imagine that the first Basic, with emphasis on speed, won't do it, and later revisions, with a need to be compatible, can't do it.

Having said that, I thought I understood what was going on, but now I notice that

Code: Select all

B%=B%+C% : REM can Too Big
B%=B%+4096  : REM can Too Big
B%=B%+&1000 : REM wraps
actually behave differently. It seems perhaps an expression has three possible types: integer which can Too Big, integer which wraps, and float.

Edit: doubt is cast! Perhaps I didn't see this.
Last edited by BigEd on Mon Sep 03, 2018 2:47 pm, edited 1 time in total.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 1:54 pm

Phlamethrower wrote:
Mon Sep 03, 2018 1:22 pm
there are a great many number of unexpected and potentially dangerous things that can go wrong with traditional floating point arithmetic as well. Sooner or later the programmer has to learn that these issues exist.
Can you give an example? If you're thinking of the well-known issues associated with (most) high-level languages using binary floating point rather than decimal, giving rise to small (usually very small) differences between the result and what the user expected, I don't really think that's comparable.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 2:13 pm

BigEd wrote:
Mon Sep 03, 2018 1:42 pm
But it will be a little bit slower, and not compatible
Acorn's BASICs haven't been slaves to compatibility in other respects. Not so long ago the various different implementations of the transcendental functions were discussed here, including some with significant bugs giving rise to much greater errors than would be expected. There were many changes between 6502 BASIC and ARM BASIC that might have had compatibility implications (e.g. switching to a single stack architecture).

I didn't hesitate to make BBC BASIC (Z80) work differently from 6502 BASIC in this respect, although I don't think I realised then just how problematical Acorn's implementation was; had I done so I would probably have suggested to Sophie that it should be changed in ARM BASIC. The BBC still had some influence then!
It seems perhaps an expression has three possible types: integer which can Too Big, integer which wraps, and float.
Oh dear!

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Mon Sep 03, 2018 2:19 pm

Hang on, I'm not so sure about B%=B%+C% now. It looks like it wraps. But I still think there might be three kinds of expressions.

User avatar
Rich Talbot-Watkins
Posts: 1471
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: BBC BASIC integer arithmetic

Post by Rich Talbot-Watkins » Mon Sep 03, 2018 2:38 pm

I don't get your results Ed. It just seems to me as if integer operations wrap when both operands are integers. An integer, in this case, being an integer variable or a numeric integer literal in the 32-bit signed integer range.

So:

Code: Select all

>@%=&A0A
>PRINT 2147483647+10
-2147483639
>A%=2147483647
>PRINT A%+10
-2147483639
>B%=10
>PRINT A%+B%
-2147483639
>PRINT A%+10.0
2147483657
>C%=A%+B%
>PRINT C%
-2147483639
>D%=A%+10
-2147483639
This behaviour makes perfect sense to me. I'm used to having to write floating point literals with a decimal point (after years of writing C/C++) and it seems a reasonable optimisation to have BBC BASIC assume that numeric literals without a decimal point should be treated as integers.

In the case where you try to write:

Code: Select all

>E%=A%+10.0

Too big
>
...that's reasonable as well, because you're trying to coerce a floating point value to an integer, but the value is out of 32-bit integer range.
Last edited by Rich Talbot-Watkins on Mon Sep 03, 2018 2:41 pm, edited 1 time in total.

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Mon Sep 03, 2018 2:46 pm

Drat. I might well not have tried or seen what I'd thought. Sorry about that.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 2:50 pm

dominicbeesley wrote:
Mon Sep 03, 2018 1:27 pm
I suspect that the if overflow, promote both operands then if assigning to an integer truncate (and wrap to negative) would probably satisfy both worlds...
It's noteworthy that a characteristic of all* implementations of BBC BASIC is that a 'floating point' number has an integer range at least twice that of an integer. For example in all versions up to and including ARM BASIC 5 an integer is 32 bits (with an integer range of -2^31 to +2^31-1) and a float is 40 bits (with an integer range of -2^32 to +2^32). In BB4W v6 and BBCSDL an integer is 64 bits (with an integer range of -2^63 to +2^63-1) and a float is 80 bits (with an integer range of -2^64 to +2^64). It's this characteristic that means there will be no loss of precision when promoting an integer to a float following an overflow.

Had BBC BASIC used a more conventional 32-bit float that strategy would not have worked. Promoting a 32-bit integer (actually 33-bit after the addition or subtraction) to a 32-bit float would have resulted in a loss of precision, making it impossible to implement a 'wrap' type behaviour on later assigning the result to an integer variable - by then the least-significant bits would have been lost. Of course even if floating point variables had been 32-bits there's no reason why the expression evaluator couldn't have used a 40 bit (or larger) temporary float, exactly as the x86 FPU uses an 80 bit temporary float for intermediate calculations even when the result is assigned to a 64 bit 'double'.

*There is an exception to this rule in the case of ARM editions of BBCSDL (such as for example the Android and Raspberry Pi editions). ARM CPUs sadly don't support 80-bit floats (at least, not the built-in hardware FPU) so in that case promoting an integer to a float results in a loss of precision from 64 bits to 53 bits. It's fortunate that I don't support wrapping 64-bit integers!

Phlamethrower
Posts: 103
Joined: Fri Nov 24, 2017 1:35 pm
Contact:

Re: BBC BASIC integer arithmetic

Post by Phlamethrower » Mon Sep 03, 2018 6:47 pm

Richard Russell wrote:
Mon Sep 03, 2018 1:54 pm
Phlamethrower wrote:
Mon Sep 03, 2018 1:22 pm
there are a great many number of unexpected and potentially dangerous things that can go wrong with traditional floating point arithmetic as well. Sooner or later the programmer has to learn that these issues exist.
Can you give an example? If you're thinking of the well-known issues associated with (most) high-level languages using binary floating point rather than decimal, giving rise to small (usually very small) differences between the result and what the user expected, I don't really think that's comparable.
Your example was of two numbers (which can be represented by BASIC) being added together and producing an incorrect result. Some similar examples for floating point:

Code: Select all

REM replace with 2^(N+1), where N is the number of fractional bits in the exponent
A=2^32

REM This will print the wrong value because the expression assigned to B
REM can't be represented by BASIC - but BASIC gives no warning of the failure.
B = A+1 : PRINT B-A

REM In these cases the result of the expression can be represented by BASIC,
REM but because the individual stages can't be represented you're likely to
REM get incorrect results for at least one case (and which case that is will
REM differ depending on whether the expression is evaluated left-to-right or
REM right-to-left)
B = A+1+1 : PRINT B-A
B = 1+1+A : PRINT B-A
B = 1+A+1 : PRINT B-A
Equivalent examples are possible for decimal floating point.

Then there's this old chestnut:

Code: Select all

N=10
A=1/N
B=0
FOR C=1 TO N
B=B+A
NEXT C
IF B<>1 THEN PRINT "Umm..."
That one will fail for binary floating point, but I'm sure you can get a similar failure for decimal floating point by picking a different value for N.

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 8:19 pm

Phlamethrower wrote:
Mon Sep 03, 2018 6:47 pm
Your example was of two numbers (which can be represented by BASIC) being added together and producing an incorrect result. Some similar examples for floating point:
I can't agree that they are "similar". Somebody knowledgeable about the limitations of floating-point arithmetic wouldn't find them surprising, but they ought to be very surprised by what Acorn's BASICs do with my example!

Code: Select all

B = A+1+1 : PRINT B-A
B = 1+1+A : PRINT B-A
B = 1+A+1 : PRINT B-A
These all print '2' in my (40-bit float) BASICs.

Phlamethrower
Posts: 103
Joined: Fri Nov 24, 2017 1:35 pm
Contact:

Re: BBC BASIC integer arithmetic

Post by Phlamethrower » Mon Sep 03, 2018 9:02 pm

Richard Russell wrote:
Mon Sep 03, 2018 8:19 pm
Somebody knowledgeable about the limitations of floating-point arithmetic wouldn't find them surprising, but they ought to be very surprised by what Acorn's BASICs do with my example!
Someone familiar with Acorn BASIC wouldn't be surprised by Acorn BASIC's behaviour either. Horses for courses :)
These all print '2' in my (40-bit float) BASICs.
True - I tested that in BASIC64, and didn't realise that 40-bit floats handle rounding differently. You just need to make the increments smaller in order to get a failure with 40-bit floats (for ARM BASIC, at least):

Code: Select all

B = A+0.5+0.5+0.5+0.5 : PRINT B-A : REM gives 0
B = 0.5+0.5+0.5+0.5+A : PRINT B-A : REM gives 2

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Mon Sep 03, 2018 9:40 pm

Phlamethrower wrote:
Mon Sep 03, 2018 9:02 pm
Someone familiar with Acorn BASIC wouldn't be surprised by Acorn BASIC's behaviour either.
Really? I have been "familiar" with Acorn's BBC BASICs since 1981! Although I knew their expression evaluation worked differently from mine, I had no idea until a few days ago that the value of a decimal numeric constant could determine whether the result of a calculation would 'wrap' or not.

If you look back at the other thread, my initial example of the 'anomaly' was to demonstrate that a hexadecimal constant (&80000000) behaved differently from the equivalent decimal constant (-2147483648) which is also something that I had no clue about previously. As I said before, if I had known about that behaviour in the 1980s I would have tried hard to persuade Sophie to change it in ARM BASIC.

So if you already knew about those features congratulations, but I would guess very few people "familiar" with Acorn's BASICs did. To be honest, even if you did know about them beforehand I still think you should find them surprising! Nobody, so far, has argued that the behaviour is desirable (wrapping when assigning the result to an integer variable, perhaps, but not part way through the expression evaluation!).

User avatar
Rich Talbot-Watkins
Posts: 1471
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: BBC BASIC integer arithmetic

Post by Rich Talbot-Watkins » Tue Sep 04, 2018 8:45 am

I suspect the reason BBC BASIC works like this is to do with how unary minus is handled. Ideally, the unary minus would be parsed as part of a numeric literal, and hence &80000000 and -2147483648 would both represent the same integer literal. However, in fact what happens is the unary minus is a special case which expects an expression afterwards and then evaluates the complement of that expression. This means that it's actually treated as -(2147483648), and this value is too big to fit into an (unsigned) 31 bit integer, hence it's interpreted as a float literal instead. This is why there's a discrepancy in how the two expressions are interpreted. We can see that if we use &80000001 / -2147483647, this works consistently.

In my view, this is just a difficult and unfortunate edge case. BBC BASIC's behaviour otherwise seems totally reasonable to me, and there is no other value which results in this discrepancy.

Changing the interpretation of literals depending on the type being assigned to seems nasty. I can't think of any languages which work like this. Normally assignment is just another thing you can do with the result of an expression; it shouldn't determine how the expression should be evaluated, unless the language is designed with that exact behaviour in mind (and, if so, how should expressions be evaluated if they are not assigned to anything?).

User avatar
Richard Russell
Posts: 804
Joined: Sun Feb 27, 2011 10:35 am
Location: Downham Market, Norfolk
Contact:

Re: BBC BASIC integer arithmetic

Post by Richard Russell » Tue Sep 04, 2018 12:10 pm

Rich Talbot-Watkins wrote:
Tue Sep 04, 2018 8:45 am
Changing the interpretation of literals depending on the type being assigned to seems nasty.
Absolutely, but that's not what Acorn's BASICs are doing based on the behaviour I observe. The failure mode seems to be that if adding (or subtracting) two 32-bit integers results in an overflow they allow the result to wrap (e.g. a positive value becomes a negative value) even if a subsequent term in the expression would restore the value to being 'in range' and even if the final result of the expression evaluation gets assigned to a float (or simply PRINTed). I don't know of any other BASIC interpreter that works that way and it seems plain wrong to me.

The only sensible strategy, as far as I am concerned, is that if an overflow occurs the numbers are promoted to a data type which can contain the result of the calculation (particularly when, which is the case in point, the new data type has at least as good a precision so that no loss of accuracy results from that promotion). That way the final result of the expression evaluation will either be 'correct' (within the limitations of the data types available) or an error will be reported. Anomalies such as '2147483650 + 10' giving a completely different result from '2147483640 + 20' won't occur.

Allowing a 'wrap' to take place in the heart of the expression evaluator risks getting a completely incorrect result even if none of the operands nor the final value are anywhere near the magnitude that might cause concern over possible overflows. Not only that but since it's a data-dependent behaviour you may fail to spot the problem in testing. It reminds me of an attempted Ariane launch (was it?) which suffered an unpredicted numeric overflow in the guidance system and had to be aborted!

User avatar
Rich Talbot-Watkins
Posts: 1471
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: BBC BASIC integer arithmetic

Post by Rich Talbot-Watkins » Tue Sep 04, 2018 12:37 pm

There are a few rules:
  1. Integer arithmetic wraps around. This is even the case when it's a subexpression in a larger expression.
  2. If both operands to an overloaded operator (+,-,*,/) are integers, then an integer operation is performed and an integer result returned. (Note that some operators always coerce the operands to integers, such as AND, OR, DIV).
  3. Hex literals are always parsed as integers.
  4. Decimal literals are parsed as integers if they fall within the range 0...231-1.
  5. Unary minus does not form part of a numeric literal, but is treated as an operator which acts on the expression which follows.
So the fault here is that -231 can't be represented as a decimal integer literal, because writing 231 in decimal exceeds the allowed range and will be parsed as a float. This is the only value for which this problem occurs! This should be fixed by parsing negative numeric literals differently: specifically, treating a substring like "-12345" as a single literal, rather than as "12345" operated on by a unary minus.

But the real problem here, as you say, is the integer wrap around behaviour. I don't think any other BASICs do that, and I'm not sure why BBC BASIC went for it (surely not for optimisation reasons, as the overflow flag denotes this exact case!). Even the few BASICs which supported a separate integer type (normally only 16 bits long) most likely promoted the result to floating point if it were to overflow, as you suggest. If BBC BASIC were to do that, none of this would be problematic; but instead I guess it's important to be aware of this quirk and handle it explicitly if necessary by adding decimal points to numeric literals to force them to be interpreted as floating point values.

Obviously, promoting to a larger type and wrapping integer results are not mutually compatible. I guess in hindsight it's a shame BBC BASIC opted for the latter!

User avatar
Rich Talbot-Watkins
Posts: 1471
Joined: Thu Jan 13, 2005 5:20 pm
Location: Palma, Mallorca
Contact:

Re: BBC BASIC integer arithmetic

Post by Rich Talbot-Watkins » Tue Sep 04, 2018 12:39 pm

Incidentally I got these insights after rewriting the parser for BeebAsm 2.0 a while back, which supports and distinguishes a number of types. I too parse numeric literals like BBC BASIC, not making a special case for negative numbers, and it results in the same trouble. Unlike BBC BASIC, I don't wrap integers (I hadn't even realised it did that until this thread) so pretty much all the problems disappear.

User avatar
BigEd
Posts: 2517
Joined: Sun Jan 24, 2010 10:24 am
Location: West
Contact:

Re: BBC BASIC integer arithmetic

Post by BigEd » Tue Sep 04, 2018 1:05 pm

Integers do seem to wrap with addition, but for multiplication I'm getting 'too big'. This seems good to me.

Post Reply