According to this answer, Apple changed the AND, OR, NOT operators in later versions of BASIC to perform Boolean instead of bitwise logic, so for example (1 AND 2) = 1. Which raises the question, how does one perform bitwise logic in those BASICs?
3 Answers
Which raises the question, how does one perform bitwise logic in those BASICs?
Best would be not at all - or at least use an Assembly subroutine like Supercat suggests (*1).
Now if you really insist to use BASIC, it has to be programmed in painful subroutines by taking the variable in question apart using floating point operations, do whatever operation is required and building it up again, as costly as before.
BASIC functions to be used might look like this:
1000 REM Bit-Handling
1010 REM Common Interface:
1020 REM BI -> "Whole" Integer
1030 REM BP -> Bit-Position to be used, range 1..N
1040 REM BV -> Bit Value (0/1)
1100 REM Bit Extraction
1110 REM IN: BI, BP
1120 REM OUT: BV
1130 BT = 2 ^ BP
1140 BV = ((BI - ( INT (BI / BT)) * BT) >= (2 ^(BP - 1)))
1150 RETURN
1200 REM Set Bit
1210 REM IN: BI, BP
1220 REM OUT: BI (Additionaly previous value in BV)
1230 GOSUB 1100 : REM Get the bit value and set if not already set
1240 IF BV = 0 THEN BI = BI + (2 ^ (BP - 1))
1250 RETURN
1300 REM Clear Bit
1310 REM IN: BI, BP
1320 REM OUT: BI (Additionaly previous value in BV)
1330 GOSUB 1100 : REM Get the bit value and clear if not already set
1340 IF BV = 1 THEN BI = BI - (2 ^ (BP - 1))
1350 RETURN
These functions can now be used to read any single bit (to BV) of an Applesoft variable (in BI), to be used in a test or any boolean expression:
- And:
R = B1 AND B2
- Or:
R = B1 OR B2
- XOR:
R = B1 <> B2
- NAND:
R = NOT B1 AND B2
- NOR:
R = NOT B1 OR B2
- NOT:
R = NOT B1
(Unary)
The set/clear functions can in turn be used to manipulate any bit according.
Together they may as well be used in a loop (*2) to handle whole bytes (or more), for example to XOR two byte values:
100 REM Operation on two byte values
110 V1 = 255 : REM $FF or %1111 1111
120 V2 = 165 : REM $A5 or %1010 0101
130 V3 = 0
190 PRINT "BEFORE: V1=";V1;" V2=";V2;" V3=";V3
200 REM Loop across V1/V2 perform XOR and store result to V3
210 FOR BP = 1 TO 8 : REM Just a byte
220 BI = V1 : GOSUB 1100 : B1 = BV
230 BI = V2 : GOSUB 1100 : B2 = BV
240 R = B1 <> B2 : REM B1 XOR B2
250 IF R = 1 THEN BI = V3 : GOSUB 1200 : V3 = BI : REM Set if 1
290 NEXT BP
300 PRINT "AFTER : V1=";V1;" V2=";V2;" V3=";V3
390 END
Result should be of course 90/$5A/%01011010.
This can of course be optimized if the value is to be stored in the first parameter by replacing 220..250 by the following lines:
220 BI = V2 : GOSUB 1100 : B2 = BV
230 BI = V1 : GOSUB 1100 : B1 = BV
240 R = B1 <> B2 : REM B1 XOR B2
250 IF R = 1 THEN GOSUB 1200 : REM Set if 1
260 IF R = 0 THEN GOSUB 1300 : REM Clear clear if 0
270 V1 = BI
And so on. I guess that code can be improved a bit by using DEF FN
(*3).
*1 - To lake it (a bit) less hard, one could cover it as well with some BASIC code.
*2 - That's why BP is defined as Bit-Position 1..n, despite being slower than using binary values (2,4,8,...) direct.
*3 - Might be hard as FN allows only a single argument and we need at least two.
-
I don't see how a loop is a reason use 1..n instead of 0..n-1; you can do
FOR BP=0 TO 7
just as easily asFOR BP=1 TO 8
... Commented Sep 12, 2022 at 21:23 -
1@MarkReed Well, sure, can be modified that way, when taking care about the -1 part. - wouldn't change speed in any way. I like to see it as a Bit POSITION value, not a power of two value.– RaffzahnCommented Sep 12, 2022 at 21:27
-
1Oh, I see. I you meant 1..N instead of 1,2,4,8.... Gotcha. I'm just used to seeing bit N meaning 2^N instead of 2^(N-1). Commented Sep 12, 2022 at 21:31
-
1If you don't mind taking up a large amount of memory, you could make a 16x16 integer array to look up e.g. the NAND result of a nibble and use that as a building block for your other bitwise operations.– JeremyPCommented Sep 14, 2022 at 15:24
-
2So the answer to my question seems to be "painfully". No native bitwise ops in later A$ BASIC, so either roll your own out of other (slow) arithmetic, or use assembly subroutines. Got it. :) Commented Sep 14, 2022 at 16:06
The most efficient way of performing many kinds of bitwise operations would probably be to use POKE
to put a small machine-language stub at address 768 or other such location to perform the desired computation, and then use USR
or CALL
to invoke it. For example, after:
POKE 768,169 : REM A9=LDA immediate
POKE 770,41 : REM 29=AND immediate
POKE 772,73 : REM 49=EOR immediate
POKE 774,141 : REM 8D=STA abs
POKE 775,1 : REM LSB of address $0301
POKE 776,3 : REM MSB of address $0301
POKE 777,96 : REM 60=RTS
the POKE
ing values X
, Y
, and Z
into addresses 769, 771, and 773, would compute (X AND Y) EOR Z
and store the result into address 769. Doing all of those pokes every time one wanted to compute something would be slow, but everything other than the writes to 769, 771, and 773 would only need to be done once, and the fact that the result is written back to address 769 would allow computations to be easily chained.
-
13...And all I ask is a little endian cpu and an assembler to steer her by... - with apologies to John Masefield...– Geo...Commented Sep 12, 2022 at 21:35
-
13
Here some basic BASIC extension to Supercat's Answer. If you like it, please consider his answer as well. With this extension every basic 6502 operation can be used, including shift and arithmetic.
First that routine (*1) needs to be installed
1900 REM Install Machine Code Routine
1910 READ K,L
1920 FOR I = K to L : READ J : POKE I,J : NEXT I
1930 RETURN
1940 REM Start; End; CLC; LDA <val1>; <op> <val2>; STA <val1>;
1950 DATA 768, 783, 24, 169, 0, 41, 0, 141, 1, 3
1960 REM PHP; PLA; AND #01; STA <val2>; RTS; Terminator
1970 DATA 8, 104, 41, 1, 141, 3, 3, 96, 999
Now the routine can be used with various instructions:
1000 REM Access Low Level Functions
1010 REM Install Routine first by GOSUB 1900
1020 REM Common Interface:
1030 REM B1 -> First Byte IN/OUT
1040 REM B2 -> Second Byte IN
1050 REM BC -> Carry IN/OUT
1060 REM BF -> Function to Perform
1070 REM 1: AND, 2: OR, 3: XOR, 4: ROL,
1080 REM 5: ROR, 6: ASL, 7: LSR, 8: NOT (XOR #$FF)
1100 REM Function Dispatcher
1110 POKE 768, 24 : REM Clear Carry
1120 IF BC THEN POKE 768, 56 : REM Set Carry
1130 POKE 770, B1 : REM First Value
1140 POKE 772, B2 : REM Second Value
1150 ON BF GOTO 1310, 1230, 1330, 1340, 1350, 1360, 1370, 1380
1160 PRINT : PRINT "FUNCTION CODE OUTSIDE 1..8"
1170 STOP
1200 REM Function Return
1210 CALL 768
1220 B1 = PEEK(770) : REM Result
1230 BC = PEEK(772) : REM Carry
1240 RETURN
1300 REM Functions
1310 POKE 771, 41 : GOTO 1200 : REM AND
1320 POKE 771, 9 : GOTO 1200 : REM OR
1330 POKE 771, 73 : GOTO 1200 : REM XOR
1340 POKE 771, 42 : POKE 772, 234 : GOTO 1200 : REM ROL + NOP
1350 POKE 771, 106 : POKE 772, 234 : GOTO 1200 : REM ROR + NOP
1360 POKE 771, 10 : POKE 772, 234 : GOTO 1200 : REM ASL + NOP
1370 POKE 771, 74 : POKE 772, 234 : GOTO 1200 : REM LSR + NOP
1370 POKE 771, 73 : POKE 772, 255 : GOTO 1200 : REM NOT (XOR #$FF)
(*2)
Now one can do the same XOR as before with out a loop and (hopeful) faster execution:
100 REM V3 = V1 XOR V2
110 V1 = 255 : REM $FF or %1111 1111
120 V2 = 165 : REM $A5 or %1010 0101
130 V3 = 0
190 PRINT "BEFORE: V1=";V1;" V2=";V2;" V3=";V3
200 B1 = V1 : B2 = V2 : BF = 3 : GOSUB 1100
300 PRINT "AFTER : V1=";V1;" V2=";V2;" V3=";V3
390 END
Looks simple. could be further staightened.
And yes, I was bored...
*1 - I did take the freedom to improve it a bit by also returning the the carry bit enabling the use of carry related instructions like rotate and arithmetic.
CLC ; Set or clear carry (see lines 1110/1120)
LDA <val1> ; Load first value - <val1> will be poked (1130)
<op> <val2> ; Operation and second value - both to be poked (1140)
STA <val1> ; Save result (retrieved in 1220)
PHP ; Get Flags
PLA ;
AND #01; ; Extract Carry
STA <val2> ; Save Carry (retrieved in 1230)
RTS ; Done
*2 - Return by GOTO was used to employ the fall thru side effect of ON-GOTO - GOSUB could be faster but would need prior range check.
-
You seem to have a combination of versions of your code here. The comment and the set of calls list 7 function values, but the ON GOTO only has 6, and the error message says that the valid range is 1..5. Commented Sep 13, 2022 at 2:09
-
1@MarkReed Well, happens :)) Added ASL/LSR. Wouldn't claim it being error free.– RaffzahnCommented Sep 13, 2022 at 2:14
-
Bit manipulation can nicely be done with a combination of HPLOT and PEEK (HPLOT lets you set and clear bits, PEEK retrieves a value)– tofroCommented Sep 13, 2022 at 13:28
-
-
@DrSheldon Of course it is, I also like it as it handles code as data - something I always love. Still, the Question was about doing it in Applesoft, so this one is slightly out of scope. If I had to choose which answer to accept, I'd take the other, despite being rather wasteful (can be improved).– RaffzahnCommented Sep 13, 2022 at 19:05