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
PRINT 32767 AND 1 PRINT 32767 OR 1 PRINT NOT 32676
…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.
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…
I run the program in VCC and it gives me an:
FFFF AND FF00 =
?FC ERROR IN 30
I would not have expected VCC to behave any differently. I would have expected it to run everything the same?
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?
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?
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
110 REM XOR
111 DEF FN XR(X)=(C OR X)-(C AND X)
120 REM INTEGER CORRECTION (16-BIT SIGNED)
121 DEF FN IC(X)=X+(X>IP)*IA
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)
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.
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.
I haven’t ran that code yet – is it a checksum or a CRC?
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.