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:
- To get the MSB of a 16-bit value, shift the 16-bit value right 8 bits (divide by 256).
- 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…














