14

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 3

7

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.

7
  • 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 as FOR BP=1 TO 8...
    – Mark Reed
    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.
    – Raffzahn
    Sep 12, 2022 at 21:27
  • 1
    Oh, 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).
    – Mark Reed
    Sep 12, 2022 at 21:31
  • 1
    If 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.
    – JeremyP
    Sep 14, 2022 at 15:24
  • 2
    So 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. :)
    – Mark Reed
    Sep 14, 2022 at 16:06
14

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 POKEing 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.

2
  • 13
    ...And all I ask is a little endian cpu and an assembler to steer her by... - with apologies to John Masefield...
    – Geo...
    Sep 12, 2022 at 21:35
  • 13
    @Geo... -- "I must go down to the C again" ? Sep 13, 2022 at 0:29
6

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.

5
  • 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.
    – Mark Reed
    Sep 13, 2022 at 2:09
  • 1
    @MarkReed Well, happens :)) Added ASL/LSR. Wouldn't claim it being error free.
    – Raffzahn
    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)
    – tofro
    Sep 13, 2022 at 13:28
  • This is a more elegant solution than your other answer.
    – DrSheldon
    Sep 13, 2022 at 16:00
  • @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).
    – Raffzahn
    Sep 13, 2022 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.