Color BASIC and 16-bits and AND and OR and NOT

Updates:

  • 2024-01-07 – Fixed a value in one of the examples.

During #SepTandy2020, I posted a CoCo-related video each day of the month. A few of them dealt with bit operations in Color BASIC. This was when I fist learned that Color BASIC could not deal with more than 15-bit values. You can do AND/OR/NOT operations

These work:

PRINT 32767 AND 1
PRINT 32767 OR 1
PRINT NOT 32767

…but these will return ?FC ERROR:

PRINT 32768 AND 1
PRINT 32768 OR 1
PRINT NOT 32678

8-bits before 16-bits

8-bits is handled just fine. Here is an example program that will display an 8-bit value (0-255) as binary. It is very slow:

0 ' slow8bit.bas
10 FOR Z=0 TO 255
20 GOSUB 1000:PRINT
30 NEXT
40 END
1000 REM SLOW: 8-BIT Z AS BINARY
1010 FOR BT=7 TO 0 STEP-1
1020 IF Z AND 2^BT THEN PRINT "1"; ELSE PRINT "0";
1030 NEXT:RETURN

If you looked at TIMER, that one would count to 7339 (about 122.2 seconds). And here is a version that is substantially faster because it pre-calculates the AND bit values in an array:

0 ' fast8bit.bas
5 DIM BT(7):FOR BT=0 TO 7:BT(BT)=INT(2^BT):NEXT
10 FOR Z=0 TO 255
20 GOSUB 1000:PRINT
30 NEXT
40 END
1000 REM FAST: 8-BIT Z AS BINARY
1010 FOR BT=7 TO 0 STEP-1
1020 IF Z AND BT(BT) THEN PRINT "1"; ELSE PRINT "0";
1030 NEXT:RETURN

TIMER for that one would count to 1372 (about 22.8 seconds).

When I was recording those videos, I decided to make versions that handled 16-bit values. That is when I discovered you could not to that in Color BASIC.

16-bit workaround

Color BASIC seems to treat bit values as signed, so instead of 0 to 65535, it is really -32768 to 32767. That led me to a workaround where, if the high bit was set, I would subtract 65536 from the value and do the AND/OR/NOT on a negative value. And it worked!

0 ' fast16bit.bas
5 DIM BT(15):FOR BT=0 TO 14:BT(BT)=INT(2^BT):NEXT:BT(15)=-32768
10 FOR Z=0 TO 65535
20 GOSUB 1000:PRINT
30 NEXT
40 END
1000 REM SLOW: 16-BIT Z AS BINARY
1005 IF Z>32767 THEN Z=Z-65536
1010 FOR BT=15 TO 0 STEP-1
1020 IF Z AND BT(BT) THEN PRINT "1"; ELSE PRINT "0";
1030 NEXT:RETURN

The above version uses powers-of-two for the first 15 bits (1, 2, 4, 8, 16, etc.) then for bit 16 it uses -32768, which will work. When the value is passed in, if it is larger than 32767, it is turned in to a negative value which would be a 16-bit number with the high bit set, indicating negative.

I have no idea if this is “supposed” to work, or even be allowed (AND/OR/NOT on a negative), but it does.

And what about AND? (And OR, and NOT?)

For something I am working on, I wanted to use AND to get the most significant and least significant bytes of a 16-bit value. I could not, so I came up with various workarounds:

  1. To get the MSB of a 16-bit value, shift the 16-bit value right 8 bits (divide by 256).
  2. To get the LSB of a 16- bit value, subtract the MSB*256 from the original 16-bit value.
10 W=&HFFEE
20 M=INT(W/256)
30 L=W-M*256
40 PRINT HEX$(W),HEX$(M);" ";HEX$(L)

This worked fine, but required swapping variables around to preserve the original values.

In my project, I then wanted to clear the top 5-bits of the word, so I would clear them in the MSB value:

M=M AND &H07

…and then I could re-build the word variable using “W=M*256+B”.

Then I wondered: Could I somehow use the same trick I used for 16-bit binary numbers to make AND, OR and NOT just work on the value?

It turns out, yes. Yes I could.

I started with a subroutine that would take a 16-bit value in in Z and convert it, as needed, to a negative value that represented the desired bits. I did this this by manually looping through the bits and adding the appropriate bit value from the same array I used in the fast 16-bit routine shown earlier:

5 DIM BT(15):FOR BT=0 TO 14:BT(BT)=INT(2^BT):NEXT:BT(15)=-32768
...
1000 ' CONVERT 16-BIT Z TO ZC
1010 ZC=0:IF Z>32767 THEN Z=Z-65536 ELSE ZC=Z:RETURN
1020 FOR BT=15 TO 0 STEP-1
1030 IF Z AND BT(BT) THEN ZC=ZC+BT(BT)
1040 NEXT:RETURN

I could then set a value to Z and call this subroutine and then use ZC with AND/OR/NOT:

10 V=65535:A=&HFF00
20 PRINT V;"AND";A;"=";
30 Z=V:GOSUB 1000:VC=ZC
40 Z=A:GOSUB 1000:AC=ZC
50 Z=VC AND AC

Yuck. But it worked. Except, after I AND the two converted values, the value I get would be a negative if it was using more than 15-bits. If I wanted to display that as HEX, I’d get another ?FC ERROR because you cannot HEX$ a negative.

Thus, I needed a routine to convert such a 16-bit negative value back to a normal positive number:

1100 ' CONVERT ZC BACK TO Z
1110 IF Z<0 THEN ZC=Z+65536 ELSE ZC=Z
1120 RETURN

The end result was a multistep routine I could use to AND, OR or NOT and 16-bit value:

0 ' AND16.BAS
5 DIM BT(15):FOR BT=0 TO 14:BT(BT)=INT(2^BT):NEXT:BT(15)=-32768

10 V=65535:A=&HFF00
20 PRINT V;"AND";A;"=";
30 Z=V:GOSUB 1000:VC=ZC
40 Z=A:GOSUB 1000:AC=ZC
50 Z=VC AND AC:GOSUB 1100:PRINT ZC

999 END
1000 ' CONVERT 16-BIT Z TO ZC
1010 ZC=0:IF Z>32767 THEN Z=Z-65536 ELSE ZC=Z:RETURN
1020 FOR BT=15 TO 0 STEP-1
1030 IF Z AND BT(BT) THEN ZC=ZC+BT(BT)
1040 NEXT:RETURN

1100 ' CONVERT ZC BACK TO Z
1110 IF Z<0 THEN ZC=Z+65536 ELSE ZC=Z
1120 RETURN

And it works!

But is there a better way?

A better way

To avoid all the swapping with temporary variables, it might be easier to just make subroutines for AND, OR and NOT that handled two passed-in variables and returned the results in another.

I created a generic subroutine that would take V1 and V2, and convert V1 (if negative) and create a new value based on the bits of V2:

4000 ' CONVERT V1 AND V2
4010 IF V1>32767 THEN V1=V1-65536
4020 IF V2>32767 THEN V2=V2-65536
4030 ZZ=0:FOR BT=15 TO 0 STEP-1
4040 IF V2 AND BT(BT) THEN ZZ=ZZ+BT(BT)
4050 NEXT:V2=ZZ:RETURN

The program could set V1 to a value and V2 to a value for the bit operation, then GOSUB 4000. Then the program could do “V=V1 AND V2” or “V=V1 OR V2” or “V=NOT V2” to perform the operation.

Then, a GOSUB to 5000 would convert V back to the positive value (if needed):

5000 ' CONVERT V BACK, IF NEEDED
5010 IF V<0 THEN V=V+65536
5020 RETURN

This allowed a slightly less ugly way to do AND, OR and NOT on 16-bit values:

0 ' ANDORNOT16.BAS
5 DIM BT(15):FOR BT=0 TO 14:BT(BT)=INT(2^BT):NEXT:BT(15)=-32768

10 V1=&HFFFF:V2=&HFF00
20 PRINT HEX$(V1);" AND ";HEX$(V2),"=";
30 GOSUB 4000:V=V1 AND V2:GOSUB 5000:PRINT HEX$(V)

40 V1=&H8000:V2=&H0001
50 PRINT HEX$(V1);" OR  ";HEX$(V2),"=";
60 GOSUB 4000::V=V1 OR V2:GOSUB 5000:PRINT HEX$(V)

70 V2=&HF00F
80 PRINT "     NOT ";HEX$(V2),"=";
90 GOSUB 4000:V=NOT V2:GOSUB 5000:PRINT HEX$(V)

999 END

1000 ' AND 16-BIT V1 WITH V2
1010 GOSUB 4000:V=V1 AND V2:GOSUB 5000:RETURN

2000 ' OR 16-BIT V1 WITH V2
2010 GOSUB 4000:V=V1 OR V2:GOSUB 5000:RETURN

3000 ' NOT 16-BIT V1
2010 GOSUB 4000:V=NOT V2:GOSUB 5000:RETURN

4000 ' CONVERT V1 AND V2
4010 IF V1>32767 THEN V1=V1-65536
4020 IF V2>32767 THEN V2=V2-65536
4030 ZZ=0:FOR BT=15 TO 0 STEP-1
4040 IF V2 AND BT(BT) THEN ZZ=ZZ+BT(BT)
4050 NEXT:V2=ZZ:RETURN

5000 ' CONVERT V BACK, IF NEEDED
5010 IF V<0 THEN V=V+65536
5020 RETURN

It still ain’t pretty, but it still works. (Though has not been extensively tested, so your milage may vary.)

Until next time…

10 thoughts on “Color BASIC and 16-bits and AND and OR and NOT

      1. Allen Huffman Post author

        I don’t know how VCC works. With XROAR I can just save that in to a text file and CLOAD it as if it was from a tape. Maybe some conversion to put it on a disk image or whatever you need for VCC is messing up something in the program?

        Reply
    1. Allen Huffman Post author

      Good morning. I just copied the text from this blog entry in to a text editor, saved it out as “test.bas” as a text file, then loaded XROAR and did a CLOAD of that text file. That worked, so I think there might be a problem in how the file gets in to VCC. How do you get program listings in to VCC?

      Reply
  1. Johann Klasek

    Oh yeah, a common ugliness of MS BASIC … integer handling and bit operations. Commodore’s BASIC variant provides an additional integer variable type which takes only 2 bytes (just in array allocation). This forces a hard limit for the value range. Some BASIC extensions are not very consistent with additional logic operators/functions like XOR. Often the arguments are expected to be in range 0 to 65535 which is not compatible with other operators or the integer type. Not easy to get bitwise operations in a row.

    Just as a suggestion for handling integers and doing operations upon it. For my CRC implementation (just to prove how the implementation performs) I used the following very small framework …
    The integer conversion function is factored out as DEF FN function. The larger constants needed for calculation are stored in (early defined) variables just for speed.

    100 DIM C,IM,IP,IA: REM EARLY DECLERATIONS
    101 :
    110 REM XOR
    111 DEF FN XR(X)=(C OR X)-(C AND X)
    112 :
    120 REM INTEGER CORRECTION (16-BIT SIGNED)
    121 DEF FN IC(X)=X+(X>IP)*IA
    122 :
    190 IM=-32768:IP=32767:IA=65536

    1000 REM POLYNOM $1021, TO 16-BIT INTEGER RANGE:
    1010 P=14096+216+1: P=FN IC(P)

    1100 REM C = C XOR V
    1110 C=FN XR(V)

    Reply
    1. Allen Huffman Post author

      I am hopping to have a final #SepTandy article on the last day of this month to discuss all the great comments (though most of them are to fix issues in my stuff ;). I want to dig in to this, since it ties in to the DEF FN stuff I also tried to figure out. If you pass in X, where is the C being set? The first time I dug in to DEF FN on the CoCo mailing list (years ago) I was trying to figure out how to use multiple parameters, which the manual said you could do. You can’t, but anything global still works, so you could do stuff like: X=100:Z=FNA(100) and have FNA be something that uses the 100 that was passed in, and the global X. It’s not elegant, but it does allow some interesting things.

      Reply
      1. Johann Klasek

        The example used is taken from my CRC routine which accumulates the CRC value into the (global) variable C. This is a degenerated case resulting from C = C XOR X. As you said, DEF FN can’t handle more than one argument, but as a tool to factor out some more or less complex formulas it fits well.

        Reply
          1. Johann Klasek

            The provided lines are just fragments from my CRC code? Do you want the whole routine?

            Maybe some won’t agree, but often CRC is a specific method to calculate a checksum (like just sum up bytes into 8 bit or larger sized sum). Another variant is counting one bits of all data bytes in – just saying – a 16 bit value.

Leave a Reply to Allen HuffmanCancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.