Apple Assembly Line
Volume 4 -- Issue 8May 1984

In This Issue...

This month we are beginning a series of articles describing a double-precision decimal arithmetic package for Applesoft. Imagine 18-digit arithmetic with none of the screwy rounding errors we are used to seeing in Applesoft's binary arithmetic.

You will also find quick looks at the new Apple //c and a forthcoming set of revised ROMs for the //e. We finally have the solution to a three-year-old mystery! You old-timers might remember that in August of 1981 we published a peculiar little "what does this code do?" item from John Broderick. Well he has revealed answer at long last.

Oops!

There are a couple of bugs in the Intellec Hex Converter we published last month. To correct the program you should delete line 2240 (the INY) and add a LDA #0 at line 2285. That will take care of it! Our thanks to Chaim Palman, of Calcomp, for pointing out the problems.


Random Numbers for ApplesoftBob Sander-Cederlof

The RND function in Applesoft is faulty, and many periodicals have loudly proclaimed its faults. "Call APPLE", Jan 83, pages 29-34, tells them in "RND is Fatally Flawed", and presents an alternative routine which can be called with the USR function.

First, the flaws: 1) the initialization code fails to preset all five bytes of the seed value (only the first four of five are loaded); 2) the RND code uses a poor algorithm, and depends on "tweaks" to make the numbers more random; 3) the RND code does not properly implement the algorithm it appears to be aiming at.

BAD INITIALIZATION. The initialization code is at $F150 in the Applesoft ROMs. This loop moves the CHRGET subroutine down to $B1-C8, and is also supposed to copy the random number seed into $C9-CD. The last byte does not get copied, due to a bug. Changing $F151 from $1C to $1D would fix it. Most of us don't really care about this bug, because we are trying to get random numbers for games and the like, and the more random the better: not copying the last byte could make the numbers generated a little more random from one run to the next. However, some applications in simulation programs require REPEATABLE sequences of random numbers, so the effect of model changes can be seen independent of the random number generator.

POOR ALGORITHM. Most generators use an algorithm which makes the next random number by multiplying the previous one by a constant, and adding another constant. The result is reduced by dividing by a third constant and saving the remainder as the next random number. More on this later. The proper choice of the three constants is critical. I am not sure whether the Applesoft authors just made poor choices, or whether the bugs mentioned below drove them to tweaking. Tweaking the generated value is often thought to produce even more random results. In fact, according to authorities like Donald Knuth, they almost always ruin the generator. Applesoft tweaks the generated value by reversing the middle two bytes of the 32-bit value. Guess what: it ruins the generator, assuming it was good to start with.

BUGGY ALGORITHM. The congruency algorithm described in words above will only work properly when integer arithmetic is used. Applesoft uses floating point arithmetic. Further, Applesoft arithmetic routines expect five-byte operands. For some reason the constants used in RND are only four bytes long each. It appears that the exponents may have been omitted, in the expectation that integer arithmetic was going to be used. You can see the code for RND at $EFAE.

If you want to see some non-random features using RND, type in and RUN the following program:

       10 HGR:HCOLOR=3
       20 X=RND(1)*280:Y=RND(1)*160
       30 HPLOT X,Y
       40 GO TO 20

You will see the Hi-Res screen being sprinkled with dots. After about seven minutes, but long before the screen is full, new dots stop appearing. RND has looped, and is replotting the same sequence of numbers. Another test disclosed that the repetition starts at the 37,758th "random" number.

Mathematicians have developed many sophisticated tests for random number generators, but Applesoft fails even these simple ones! Depending on the starting value, you can get the Applesoft generator in a loop. You never get anywhere near the theoretically possible 4 billion different values.

The Call APPLE article proposes a new algorithm. It comes with impressive claims and credentials, but I have not found it to be better than a properly implemented congruential algorithm. The algorithm multiplies the previous seed by 8192, and takes the remainder after dividing by 67099547. This is a congruency algorithm:

       X(n+1) = ( a * X(n) + c ) mod m

       with a=8192, c=0, m=67099547

I re-implemented the Call APPLE algorithm, and my listing follows. The Call APPLE version would not quite fit in page 3, but mine does with a little room to spare. I also dug into some other references and came up with another algorithm, from Knuth. It is also a congruency, but with a=314159269, c=907633386, and m=2^32. This turns out to be easier to compute, and according to Knuth it should be "better". "Better" is in quotes because it is really hard to pin down what are the most important properties. Anyway this one should have very good characteristics.

The RND function does three different things, depending on the argument. You write something like R=RND(X). If X=0, you get the same number as the previous use of RND produced. If X<0, the absolute value of X becomes the new seed value. This allows you to control the sequence when you wish, and also to randomize it somewhat by using a "random" seed. If X>0, you get the next random number. The value will always be a positive number less than 1. If you want to generate a number in a range, you multiply by the width of the range and add the starting value. For example, to generate a random integer between 1 and 10:

       R = INT( RND(1)*10 ) + 1

The programs I have written build a little on the options available with RND. They all begin with a little routine which hooks in the USR vector. After executing this, you can write R=USR(X), in other words substitute USR(X) anywhere you would have used RND(X). But I have added, following the Call APPLE article, the option to automatically generate integers in a range based at 0. If 0<X<2, you will get the next random fraction. If X is 2 or greater than 2, you will get a random integer between 0 and X-1. Thus you can make a random integer between 1 and 10 like this:

       R = USR(10) + 1

as well as with:

       R = INT (USR(1)*10) + 1

I wrote a third program which makes a 16-bit random value. This one uses the seed at $4E and $4F which the Apple increments continuously whenever the standard monitor input loop is waiting for an input keystroke. Integer BASIC uses this seed, and as a result is quite valuable in writing games. My new program gives you all the options stated above, and is significantly quicker than any of the others. It uses a=19125, c=13843, and m=2^16 in a standard congruency algorithm.

If you are seriously interested in random numbers, you need to read and study Donald Knuth. Volume 2 of his series "The Art of Computer Programming" is called "Seminumerical Algorithms". Chapter 3, pages 1-160, is all about random numbers. (There is only one other chapter in this volume, all about arithmetic in nearly 300 pages!) Knuth started the series back in the 60's, with the goal of seven volumes covering most of what programmers do. He finished the first three by 1972, went back and revised the first one, and then evidently got sidetracked into typesetting (several books around a typesetting language he calls "Tex").

Speaking of being sidetracked...!

Knuth ends his chapter with a list of four rules for selecting a, c, and m for congruency algorithms. Let me summarize those rules here:

1. The number m is conveniently taken as the word size. In Applesoft, the floating point mantissa is 32 bits; hence, I chose m=2^32.

2. If m is a power of 2 (and mine is), pick "a" so that "a mod 8 = 5". This, together with the rules on choosing c below, ensure that all m values will produced before the series repeats.

3. Pick "a" between m/100 and m-sqrt(m). The binary digits should NOT have a simple, regular pattern. Knuth recommends taking some haphazard constant, such as a=3131492621.

4. "c" should be odd, and preferably near "m*.2113248654".

Now for the program listings.

The first listing is for my rendition of Call APPLE's algorithm. Lines 1220-1280 link in the USR vector. Lines 1370-1450 branch according to the value of the argument of the USR function. If the argument is negative, lines 1550-1620 set up its absolute value as the new seed. If the argument is zero, the old seed is used without change, lines 1420-1450. If positive non-zero, lines 1470-1490 set up the argument as the RANGE.

Lines 1640-1690 calculate the new seed, which will be 8192 times the old seed, modulo 67099547. 8192 is 2^13, so we can multiply be 13 left shifts. After each shift, if the result is bigger than 67099547, we subtract that value and keep the remainder. The final result will be some number smaller than 67099547.

Lines 1700-1770 save the new seed, and then divide it by 67099547 to get a fraction for the USR function result. Lines 1780-1860 check the initial argument to see if you wanted a fraction between 0 and 1, or an integer between 0 and arg-1. If the latter, the fraction is multiplied by the range and reduced to an integer.

The subroutine named MODULO subtracts 67099547 from the seed value if it would leave a positive remainder, and then renormalizes the result into floating point.

Line 2270 defines the initial seed after loading the program to be 1.0. If you want some other seed, change this line or be sure to seed it with R=USR(-seed) in your Applesoft program.

  1000 *--------------------------------
  1010 *SAVE S.USRND S-C
  1020 *--------------------------------
  1030 *      FROM CALL APPLE, JAN 1983, PAGE 29-34
  1040 *--------------------------------
  1050        .OR $300
  1060        .TF B.USRND
  1070 *--------------------------------
  1080 NORMALIZE.FAC       .EQ $E82E
  1090 FMUL.FAC.BY.YA      .EQ $E97F
  1100 LOAD.ARG.FROM.YA    .EQ $E9E3
  1110 FDIV.ARG.BY.YA      .EQ $EA5C
  1120 LOAD.FAC.FROM.YA    .EQ $EAF9
  1130 STORE.FAC.AT.YX.ROUNDED .EQ $EB2B
  1140 COPY.FAC.TO.ARG     .EQ $EB66
  1150 AS.INT              .EQ $EC23
  1160 *--------------------------------
  1170 USER.VECTOR         .EQ $0A THRU $0C
  1180 FAC                 .EQ $9D THRU $A2
  1190 FAC.SIGN            .EQ $A2
  1200 CNTR                .EQ $A5
  1210 *--------------------------------
  1220 LINK   LDA #$4C     "JMP" OPCODE
  1230        STA USER.VECTOR
  1240        LDA #RANDOM
  1250        STA USER.VECTOR+1
  1260        LDA /RANDOM
  1270        STA USER.VECTOR+2
  1280        RTS
  1290 *--------------------------------
  1300 *      R = USR (X)
  1310 *      IF X < 0 THEN RESEED WITH ABS(X)
  1320 *      IF X = 0 THEN R = REPEAT OF PREVIOUS VALUE
  1330 *      IF 0 < X < 2 THEN GENERATE NEXT SEED AND RETURN
  1340 *                    0 <= R < 1
  1350 *      IF X >= 2 THEN R = INT(RND*X)
  1360 *--------------------------------
  1370 RANDOM
  1380        LDA FAC.SIGN CHECK FOR RESEEDING
  1390        BMI .2       ...YES
  1400        LDA FAC      CHECK FOR X=0
  1410        BNE .1       ...NO, X=RANGE
  1420        LDA #SEED
  1430        LDY /SEED
  1440        JSR LOAD.ARG.FROM.YA
  1450        JMP .5
  1460 *---X --> RANGE------------------
  1470 .1     LDX #RANGE
  1480        LDY /RANGE
  1490        JSR STORE.FAC.AT.YX.ROUNDED   $EB2B
  1500 *---SEED --> FAC-----------------
  1510        LDA #SEED
  1520        LDY /SEED
  1530        JSR LOAD.FAC.FROM.YA  $EAF9
  1540 *---PREPARE SEED-----------------
  1550 .2     LDA #0       MAKE SEED POSITIVE
  1560        STA FAC.SIGN
  1570        LDA FAC      LIMIT SEED TO 67099547
  1580        CMP #$9A
  1590        BCC .3
  1600        LDA #$9A
  1610        STA FAC
  1620        JSR MODULO
  1630 *---(8192*SEED) MOD 67099547-----
  1640 .3     LDA #13
  1650        STA CNTR
  1660 .4     INC FAC
  1670        JSR MODULO
  1680        DEC CNTR
  1690        BNE .4
  1700 *---SEED/67099547----------------
  1710        LDX #SEED
  1720        LDY /SEED
  1730        JSR STORE.FAC.AT.YX.ROUNDED
  1740        JSR COPY.FAC.TO.ARG  $EB66
  1750 .5     LDA #FLT67
  1760        LDY /FLT67
  1770        JSR FDIV.ARG.BY.YA $EA5C
  1780 *---SCALE TEST-------------------
  1790        LDA RANGE
  1800        CMP #$82     IS RANGE BETWEEN ZERO AND ONE?
  1810        BCC .6       ...YES
  1820 *---SCALE------------------------
  1830        LDA #RANGE
  1840        LDY /RANGE
  1850        JSR FMUL.FAC.BY.YA   $E97F
  1860        JSR AS.INT  $EC23
  1870 *---RETURN-----------------------
  1880 .6     RTS
  1890 *--------------------------------
  1900 MODULO
  1910        LDY #0
  1920        LDA FAC
  1930        CMP #$9A
  1940        BCC .3       < 67099547
  1950        BEQ .1       67099547...
  1960        LDY #4
  1970 .1     SEC
  1980        LDA FAC+4    LSB
  1990        SBC MAN67+3,Y
  2000        PHA
  2010        LDA FAC+3
  2020        SBC MAN67+2,Y
  2030        PHA
  2040        LDA FAC+2
  2050        SBC MAN67+1,Y
  2060        PHA
  2070        LDA FAC+1
  2080        SBC MAN67+0,Y
  2090        PHA
  2100        BCC .2       <67099547
  2110        PLA
  2120        STA FAC+1
  2130        PLA
  2140        STA FAC+2
  2150        PLA
  2160        STA FAC+3
  2170        PLA
  2180        STA FAC+4
  2190        JMP NORMALIZE.FAC  $E82E
  2200 .2     PLA
  2210        PLA
  2220        PLA
  2230        PLA
  2240 .3     RTS
  2250 *--------------------------------
  2260 RANGE  .HS 81.00000000
  2270 SEED   .HS 81.00000000
  2280 FLT67  .HS 9A.7FF6E6C0    = 67,099,547
  2290 MAN67  .HS FFF6E6C0
  2300        .HS 7FFB7360
  2310 *--------------------------------

The second listing is for my 32-bit algorithm based on Knuth's rules. Again, lines 1210-1270 set up the USR linkage. Lines 1360-1400 decide what kind of argument has been used. If negative, lines 1470-1590 prepare a new seed value. If zero, the previous value is re-used. If positive, the argument is the range.

In this version the seed is maintained as a 32-bit integer. Lines 1470-1590 convert from the floating point form of the argument in FAC to the integer form in SEED. If the argument happens to be bigger than 2^32, I simply force the exponent to 2^32.

Lines 1600-1690 form the next seed by multiplying by 314159269 and adding 907633386. The calculation is done in a somewhat tricky way. Essentially it involves loading 907633386 into the product register, and then adding the partial products of 314159269*seed to that register. The tricks allow me to do all that with a minimum of program and variable space, and I hope with plenty of speed. I understood it all this morning, but it is starting to get hazy now. If you really need a detailed explanation, call me some day. The modulo 2^32 part is automatic, because bits beyond 32 are thrown away.

Lines 1700-1780 load the seed value into FAC and convert it to a floating point fraction.

Lines 1790-1870 check the range requested. If less than 2, the fraction is returned as the USR result. If 2 or more, the fraction is multiplied by the range and integerized.

  1000 *--------------------------------
  1010 *SAVE S.RANDOM KNUTH
  1020 *--------------------------------
  1030 *      FROM KNUTH'S "THE ART OF COMPUTER PROGRAMMING"
  1040 *                   VOLUME 2, PAGES 155-157.
  1050 *--------------------------------
  1060        .OR $300
  1070        .TF B.RANDOM KNUTH
  1080 *--------------------------------
  1090 NORMALIZE.FAC       .EQ $E82E
  1100 FMUL.FAC.BY.YA      .EQ $E97F
  1110 STORE.FAC.AT.YX.ROUNDED .EQ $EB2B
  1120 AS.QINT             .EQ $EBF2
  1130 AS.INT              .EQ $EC23
  1140 *--------------------------------
  1150 USER.VECTOR         .EQ $0A THRU $0C
  1160 FAC                 .EQ $9D THRU $A2
  1170 FAC.SIGN            .EQ $A2
  1180 FAC.EXTENSION       .EQ $AC
  1190 AS.SEED             .EQ $CA THRU $CD
  1200 *--------------------------------
  1210 LINK   LDA #$4C     "JMP" OPCODE
  1220        STA USER.VECTOR
  1230        LDA #RANDOM
  1240        STA USER.VECTOR+1
  1250        LDA /RANDOM
  1260        STA USER.VECTOR+2
  1270        RTS
  1280 *--------------------------------
  1290 *      R = USR (X)
  1300 *      IF X < 0 THEN RESEED WITH ABS(X)
  1310 *      IF X = 0 THEN R = REPEAT OF PREVIOUS VALUE
  1320 *      IF 0 < X < 2 THEN GENERATE NEXT SEED AND RETURN
  1330 *                    0 <= R < 1
  1340 *      IF X >= 2 THEN R = INT(RND*X)
  1350 *--------------------------------
  1360 RANDOM
  1370        LDA FAC.SIGN CHECK FOR RESEEDING
  1380        BMI .1       ...YES
  1390        LDA FAC      CHECK FOR X=0
  1400        BEQ .6       ...YES, REUSE LAST NUMBER
  1410 *---X --> RANGE------------------
  1420        LDX #RANGE
  1430        LDY /RANGE
  1440        JSR STORE.FAC.AT.YX.ROUNDED   $EB2B
  1450        JMP .4
  1460 *---PREPARE SEED-----------------
  1470 .1     LDA #0       MAKE SEED POSITIVE
  1480        STA FAC.SIGN
  1490        LDA FAC      LIMIT SEED TO 2^32-1
  1500        CMP #$A0
  1510        BCC .2
  1520        LDA #$A0
  1530        STA FAC
  1540 .2     JSR AS.QINT   $EBF2
  1550        LDX #3       COPY FAC INTO SEED
  1560 .3     LDA FAC+1,X
  1570        STA SEED,X
  1580        DEX
  1590        BPL .3
  1600 *---SEED*314159269+907633386-----
  1610 .4     LDX #0
  1620 .5     LDA SEED,X
  1630        STA MULTIPLIER
  1640        LDA C,X
  1650        STA SEED,X
  1660        JSR MULTIPLY
  1670        INX
  1680        CPX #4
  1690        BCC .5
  1700 *---LOAD SEED INTO FAC-----------
  1710 .6     LDX #5
  1720 .7     LDA FLT.SEED,X
  1730        STA FAC,X
  1740        DEX
  1750        BPL .7
  1760        LDA #0
  1770        STA FAC.EXTENSION
  1780        JSR NORMALIZE.FAC
  1790 *---SCALE TEST-------------------
  1800        LDA RANGE
  1810        CMP #$82     IS RANGE BETWEEN ZERO AND ONE?
  1820        BCC .8       ...YES
  1830 *---SCALE------------------------
  1840        LDA #RANGE
  1850        LDY /RANGE
  1860        JSR FMUL.FAC.BY.YA   $E97F
  1870        JSR AS.INT  $EC23
  1880 *---RETURN-----------------------
  1890 .8     RTS
  1900 *--------------------------------
  1910 MULTIPLY
  1920        STX BYTE.CNT
  1930        LDY #3
  1940 .1     LDA A,Y
  1950        STA MULTIPLICAND,X
  1960        DEY
  1970        DEX
  1980        BPL .1
  1990        LDY #8
  2000        BNE .2       ...ALWAYS
  2010 *--------------------------------
  2020 .5     CLC          DOUBLE THE MULTIPLICAND
  2030 .6     ROL MULTIPLICAND,X
  2040        DEX
  2050        BPL .6
  2060 .2     LSR MULTIPLIER
  2070        BCC .4
  2080        LDX BYTE.CNT
  2090        CLC
  2100 .3     LDA MULTIPLICAND,X
  2110        ADC SEED,X
  2120        STA SEED,X
  2130        DEX
  2140        BPL .3
  2150 .4     LDX BYTE.CNT
  2160        DEY
  2170        BNE .5
  2180        RTS
  2190 *--------------------------------
  2200 RANGE          .HS 81.00000000
  2210 FLT.SEED       .HS 80
  2220 SEED           .HS 00.00.00.00
  2230                .HS 00        SIGN
  2240 A              .HS 12.B9.B0.A5   314159269
  2250 C              .HS 36.19.62.EB   907633386
  2260 MULTIPLIER     .BS 1
  2270 MULTIPLICAND   .BS 4
  2280 BYTE.CNT       .BS 1
  2290 *--------------------------------

The third listing is cut down from the second one, to produce a 16-bit random number. The code is very similar to the program above, so I will not describe it line-by-line. If you want an optimized version of this, the multiply especially could be shortened.

  1000 *--------------------------------
  1010 *SAVE S.RANDOM KEYIN
  1020 *--------------------------------
  1030 *      ALLOWS ACCESS TO THE KEYIN RANDOM VALUE
  1040 *--------------------------------
  1050        .OR $300
  1060        .TF B.RANDOM KEYIN
  1070 *--------------------------------
  1080 NORMALIZE.FAC       .EQ $E82E
  1090 FMUL.FAC.BY.YA      .EQ $E97F
  1100 STORE.FAC.AT.YX.ROUNDED .EQ $EB2B
  1110 AS.QINT             .EQ $EBF2
  1120 AS.INT              .EQ $EC23
  1130 *--------------------------------
  1140 USER.VECTOR         .EQ $0A THRU $0C
  1150 FAC                 .EQ $9D THRU $A2
  1160 FAC.SIGN            .EQ $A2
  1170 FAC.EXTENSION       .EQ $AC
  1180 KEY.SEED            .EQ $4E,4F
  1190 *--------------------------------
  1200 LINK   LDA #$4C     "JMP" OPCODE
  1210        STA USER.VECTOR
  1220        LDA #RANDOM
  1230        STA USER.VECTOR+1
  1240        LDA /RANDOM
  1250        STA USER.VECTOR+2
  1260        RTS
  1270 *--------------------------------
  1280 *      R = USR (X)
  1290 *      IF X < 0 THEN RESEED WITH ABS(X)
  1300 *      IF X = 0 THEN R = REPEAT OF PREVIOUS VALUE
  1310 *      IF 0 < X < 2 THEN GENERATE NEXT SEED AND RETURN
  1320 *                    0 <= R < 1
  1330 *      IF X >= 2 THEN R = INT(RND*X)
  1340 *--------------------------------
  1350 RANDOM
  1360        LDA FAC.SIGN CHECK FOR RESEEDING
  1370        BMI .1       ...YES
  1380        LDA FAC      CHECK FOR X=0
  1390        BEQ .6       ...YES, REUSE LAST NUMBER
  1400 *---X --> RANGE------------------
  1410        LDX #RANGE
  1420        LDY /RANGE
  1430        JSR STORE.FAC.AT.YX.ROUNDED   $EB2B
  1440        JMP .4
  1450 *---PREPARE SEED-----------------
  1460 .1     LDA #0       MAKE SEED POSITIVE
  1470        STA FAC.SIGN
  1480        LDA FAC      LIMIT SEED TO 2^16-1
  1490        CMP #$90
  1500        BCC .2
  1510        LDA #$90
  1520        STA FAC
  1530 .2     JSR AS.QINT   $EBF2
  1540        LDA FAC+3
  1550        STA KEY.SEED
  1560        LDA FAC+4
  1570        STA KEY.SEED+1
  1580 *---SEED*19125+13843-------------
  1590 .4     LDX #0
  1600 .5     LDA KEY.SEED,X
  1610        STA MULTIPLIER
  1620        LDA C,X
  1630        STA KEY.SEED,X
  1640        JSR MULTIPLY
  1650        INX
  1660        CPX #2
  1670        BCC .5
  1680 *---LOAD SEED INTO FAC-----------
  1690 .6     LDA #0
  1700        STA FAC+3
  1710        STA FAC+4
  1720        STA FAC.SIGN
  1730        STA FAC.EXTENSION
  1740        LDA #$80
  1750        STA FAC
  1760        LDA KEY.SEED
  1770        STA FAC+1
  1780        LDA KEY.SEED+1
  1790        STA FAC+2
  1800        JSR NORMALIZE.FAC
  1810 *---SCALE TEST-------------------
  1820        LDA RANGE
  1830        CMP #$82     IS RANGE BETWEEN ZERO AND ONE?
  1840        BCC .8       ...YES
  1850 *---SCALE------------------------
  1860        LDA #RANGE
  1870        LDY /RANGE
  1880        JSR FMUL.FAC.BY.YA   $E97F
  1890        JSR AS.INT  $EC23
  1900 *---RETURN-----------------------
  1910 .8     RTS
  1920 *--------------------------------
  1930 MULTIPLY
  1940        STX BYTE.CNT
  1950        LDY #1
  1960 .1     LDA A,Y
  1970        STA MULTIPLICAND,X
  1980        DEY
  1990        DEX
  2000        BPL .1
  2010        LDY #8
  2020        BNE .2       ...ALWAYS
  2030 *--------------------------------
  2040 .5     CLC          DOUBLE THE MULTIPLICAND
  2050 .6     ROL MULTIPLICAND,X
  2060        DEX
  2070        BPL .6
  2080 .2     LSR MULTIPLIER
  2090        BCC .4
  2100        LDX BYTE.CNT
  2110        CLC
  2120 .3     LDA MULTIPLICAND,X
  2130        ADC KEY.SEED,X
  2140        STA KEY.SEED,X
  2150        DEX
  2160        BPL .3
  2170 .4     LDX BYTE.CNT
  2180        DEY
  2190        BNE .5
  2200        RTS
  2210 *--------------------------------
  2220 RANGE          .HS 81.00000000
  2230 A              .DA /19125,#19125
  2240 C              .DA /13843,#13843
  2250 MULTIPLIER     .BS 1
  2260 MULTIPLICAND   .BS 2
  2270 BYTE.CNT       .BS 1
  2280 *--------------------------------

What do you do if you want even more randomness than you can get from one generator? You can use two together. The best way (for greatest randomness) is to use one to select values from a table produced by the other. First generate, say 50 or 100, random values with one generator. The generate a random value with the second generator and use it to pick one of the 50 or 100 values. That picked value is the first number to use. Then replace the picked value with a new value from the first generator. Pick another value randomly using the second generator, and so on. This is analogous to two people working together. The first person picks a bowlful at random from the universe. The second person picks items one at a time from the bowl. The first person keeps randomly picking from the universe to replace the items removed from the bowl by the second person.

You could use the 16-bit generator to pick values from a "bowl" kept full by my 32-bit generator.

Now back to those tests mentioned at the beginning. I am happy to report that all three of the algorithms listed above completely fill the hi-res screen, no holes left, eventually.

By the way, the August 1981 AAL contained an article about the Integer BASIC RND function, and how to use it from assembly language.


The Apple //cBob Sander-Cederlof

In August 1977 I walked into CompuShop with checkbook in hand, hoping to fill a void in my life by (finally) buying my own personal computer. I didn't know one brand from another, but there was a 4K Apple II running a color demo in lo-res graphics that caught my eye. I bought it. My toy, because I certainly could think of no possible way to consider it more than a toy. The serial number is 219, and I am using it to write this article. By the way, the other brands that were at CompuShop in 1977 are now all out of business.

The price for 4K was $1298; I got 4K extra RAM and paid $1348 plus sales tax. No software. No CRT. No floating point BASIC. No slick manuals. About 45 pages of mimeographed notes was the total documentation package. I had to build a modulator kit that afternoon so I could hook it up to my TV set. The only other connection which seemed of any use was the cassette tape, which several hundred of you may remember. The store gave me a cassette containing the color demo and Woz's Breakout game. That was all there was! Eight empty slots, and absolutely nothing on the market to plug into them. Not even enough memory for hi-res graphics, which I did not even know existed. Absolutely no software for sale from any vendor.

I have spent a lot of time on this Apple. And money. And it is not JUST a toy any more! It has Applesoft on the motherboard, with 48K RAM. Slot 0 has an STB 128K RAM card (the best, in my opinion). All the other slots are full, but with what depends on the work for the day.

Now there is the Apple //c. $1295 buys you 128K RAM, Applesoft BASIC, a disk drive, and ProDOS! Probably over 10,000 programs on the market which will run in it, and many more to come. Built-in interfaces including two serial ports, mouse, disk controller, 80-columns, many video options, and more. The most often purchased interfaces are all there, enough to fill five slots in an older Apple. They added a headphone jack and volume control, too; it is recessed under the left edge. Using it will let you work later at night without disturbing light sleepers. You still get a "game" port, but it is a 9-pin D-socket and doubles as the mouse port. Sorry, no more Cassette port. A second disk drive can be added, and it costs significantly less than a second //e drive.

There are two new switches beside the RESET switch, labeled 40/80 and Keyboard. The first switches between 40 and 80 columns. The second selects QWERTY or Dvorak keyboard arrangement. Think a while of the implications to future generations of including THAT switch. The 40/80 switch is really just connected to what used to be cassette input $C060. You can read the switch position like the firmware does, by looking at the sign bit of that byte.

Until now all Apple game ports had four analog inputs, four switch outputs, and three switch inputs. The //c has only two analog inputs, and no switch outputs. The three switch inputs remain, with switch two dedicated to the mouse button. The other two analog input addresses are used as single bits to read the mouse X and Y direction. The four output bits are now used to control various interrrupt modes.

An interesting new softswitch input is at $C077. If bit 7 of the byte is 1, the current line being stroked on the screen is graphics; if 0, it is text. People like Bob Bishop, Don Lancaster, and Bill Budge probably already have figured out fantastic new tricks using this bit.

The power supply is now in a little box that is part of the power cord. 115 volts AC in, 12 volts DC out. The rest of the supply voltages derived inside the case. There will be a battery pack option later. And how about an adapter for running in the car?

The video output capability is phenomenal. Now you get all the American and European options built in. One connector gives you the NTSC we are all used to. Another gives you RF-modulated form for an American TV set. You also get RGB and various European standards. The 15-pin video connector also gives you an audio signal and various timing signals.

The ROM in the //c is VERY different. The differences include serial port and mouse firmware, better interrupt handling, the improvements made in the new //e ROMs, no more self-test program, and extensions to the disassembler (monitor L-command) for the 65C02 chip.

It is getting to be quite a chore for software to distinguish which kind of Apple II it is in. Here is a chart showing Apple's official ID bytes:

     $FBB3  $FB1E  $FBC0    Environment
     ---------------------------------------------------
      $38                   Old (Original) Apple ][
      $EA    $AD            Apple ][ Plus Autostart
      $EA    $8A            Apple /// Emulation
      $06           $EA     Apple //e
      $06           $E0     New Apple //e ROM
      $06           $00     Apple //c

Interrupts are used extensively by the mouse firmware. A keyboard interrupt plus firmware implements a 128-character type-ahead buffer.

All this talk about mouse support leads me to make one clarification. You don't get a mouse unless you pay an extra $100. The firmware and interface are built-in, but the actual device is optional.

By the way, besides the 16 memory chips there are only 21 other chips. More special chips, including IWM (Integrated Woz Machine, the disk controller); GLU (General Logical Unit); and TMG (Timing Generator). Compare the total 37 chips with about 50 in the Macintosh, and more than 90 in the IBM PCjr. Most of the chips are soldered in, but a few still sit in sockets.


News from Roger WagnerBob Sander-Cederlof

Roger Wagner: well known to most of us as owner of Southwestern Data Systems, author of "Assembly Lines: The Book", author of several popular programs in early Apple days, speaker at AppleFests, and so forth. Roger is branching out.

Last month he incorporated and changed the name of SDS to Roger Wagner Publishing. Along with the name change, the product packaging has been changed. After a poll of dealers, they decided to replace the plush padded binders with a new package design which allows customers to browse through the manuals, while the diskette and other package contents are securely kept intact. No more shrink wrap! Simpler packaging is less expensive, so the prices of some products have been lowered. And one step further, even more significant: no more copy protection!

We applaud Roger for taking this step. As I remember it, Roger was one of the first publishers to use any kind of software protection, back in the 70's. His scheme included a program on the master disk which allowed you to make a limited number of back up copies. Now Roger joins us and a handful of other publishers who refuse to shackle users with protected software.

Roger has also joined forces with Val Golding (founder and long-time editor of Call-A.P.P.L.E.) to form Emerald City Publishing, Inc. Their first project is "The Apple's Apprentice", a magazine aimed at Apple-teens.


Apple //e ROM RevisionBob Sander-Cederlof

Dated March 21, 1984, I received a pair of 2764 ROMs and 12-page writeup. These are preliminary versions of a new set predicted to be in general distribution by early next year.

The new //e ROMs are substantially better than the current ones. Changes include:

Applesoft: modified to work in 80-column mode, and with lower case.

Monitor:

Video Firmware (after PR#3):

The new IRQ handler should finally make interrupts actually usable on the Apple. The old problem with location $45 is fixed. The settings of the various soft-switches which control memory mapping are saved and the machine is put into a cononical state. The standard IRQ return sequence will restore the interrupted state of all those switches.

The total overhead from IRQ-event to your IRQ-subroutine will run from 250 to 300 microseconds, depending on the soft-switch settings. If you are in a ProDOS environment, you will have to add all the overhead caused by ProDOS.

Of course, there will be new problems. ProDOS bent over backwards in a very strange way to solve the $45 problem with interrupts. Now that it is not necessary, ProDOS should be changed. But it can't be changed for the new and still work in the old, so.... The new IRQ and BRK handler also clobbers locations $100 and $101, which is BAD! Both those locations are used by Applesoft and many other programs!

If you think these changes will impact your work, or want to be involved in shaking out bugs, you might contact Developer Relations at Apple (408) 996-1010 and discuss the Certified Apple Developer program. I think it is because I am one of those that I received this material.


65C02 vs the older ApplesBob Sander-Cederlof

A few months ago we reported that apparently 2-MHz versions of the 65C02 chip worked in Apple IIs and II Plusses. (Even 1-MHz versions work in //e's.) Bob Stout was our source: he tried it, it worked, and he told us so.

Based on Bob's good luck, Stephen Bach tried it, it did not work, and he told us so. Steve and Bob got together, and it seems that the 2-MHz parts work in some IIs and II Plusses, but not all. "Try it and see" seems to be the only definitive answer.

By the way, you can get the 65C02 from Hamilton/Avnet and several other distributors for under $15 each. The 1MHz version is under $10 from Western Design Center. There is no incentive for dealers to get into the distribution of chips like this, because quantity price breaks depend on volumes in the thousands.

If you are having trouble finding a distributor, call Rockwell International's sales office; they might sell to you directly, point you to a distributor, or even give you a free sample. If not Rockwell, then try GTE or NCR, who also manufacture the 65C02, albeit without the extra 32 instructions Rockwell inserted. Here are some phone numbers for Rockwell:

       California:  (714) 833-4655
       Texas:       (214) 996-6500
       Illinois:    (312) 297-8862
       New Jersey:  (609) 596-0090
       Tokyo:       (03)  265-8806
       West Germany:(089) 857-6016
       England:     (01)  759-9911

You might possibly find these chips at Apple dealers or repair centers in the near future, because it is being used in the Apple //c. Apple is apparently not using the Rockwell version, because the BYTE article about the //c says the chip has 27 new opcodes. This is the total count of new opcodes including the new addressing modes added by the 65C02 offered by NCR, GTE, Western Design, and others. The Rockwell version adds an additonal 32. Those 32 are NOT in the 65802 or 65816, so chasing after them will lead you into dead-end streets.

If you are able to wait, the 65802 and 65816 far surpass the 65C02. You can order samples from Western Design Center, (602)962-4545, at $95 each. Originally expected in January, they are now targeting June 15th.


Decimal Floating Point ArithmeticBob Sander-Cederlof

Perhaps you have wondered why PRINT INT(14.9 * 10) in Applesoft prints 148. This and many other such seeming bugs are a very common idiosyncrasy in the computer world.

Applesoft uses binary floating point format for storing numbers and doing arithmetic. The number 14.9 is very clean in decimal, but it is an awful mess in binary. If you look at what is stored in RAM after doing X=14.9, you will find 84 6E 66 66 66. The first byte, 84, means the remaining four should be understood as four bits of binary integer (the "14" of "14.9") and 28 bits of binary fraction (the ".9" part). The first bit of the second byte is zero, which means the number is positive. Applesoft stores the sign in this bit position, knowing that ALL values other than 0.0 will have a 1-bit in this position of the magnitude.

Just before doing any arithmetic on the value above, Applesoft will unpack it, separating the sign, binary exponent, and the rest. The fancy name for the rest is the "mantissa". Writing out the mantissa for 14.9 we see EE 66 66 66. The first "E" means 14, and the .E666666 is APPROXIMATELY equal to .9. It is actual less than .9 by .000000066666666...forever. Since the number is not quite 14.9, multiplying by 10 gives not quite 149. And taking the INT of not-quite-149 gives the CORRECT answer of 148.

CORRECT, but not what you WANTED or EXPECTED. Right, Ethan? That is why you will find business software written in Applesoft is full of little fudge factors. We always need to multiply by enough 10's to make all pennies into integers, and then round up, and then truncate.

An alternative is to use DECIMAL arithmetic. And guess what: the 6502 has built-in decimal arithmetic. The only trouble is that Applesoft does not know about it.

I wrote an Applesoft extension package called DPFP which gives Applesoft 21-digit precision, rather than the normal 9. But it is still binary, so you still get those round-off and truncation problems with clearcut decimal fractions. About two and a half years ago I wrote another Applesoft extension package called DP18. This one is DECIMAL, and gives 18-digit precision. Bobby Deen helped me flesh it out with full support for arithmetic expressions and all the math functions.

Well, it has been hiding on my shelf long enough! I am going to start publishing it in AAL, a piece at a time. In this issue you will find the routines for addition and subtraction.

First a word about the way DP18 stores numbers. Since Applesoft uses five bytes for each floating point value, and since it is relatively easy to connect to Applesoft using multiples of five bytes, I use ten bytes for each DP18 value. The first byte holds the sign and exponent for the value. The remaining nine bytes hold 18 decimal digits, in BCD format. That is, each digit takes four bits.

The first bit of the first byte is the sign bit. Zero means plus, one means minus. If the whole first byte is zero, the whole number is zero. The remaining seven bits of the first byte are the decimal exponent, excess $40. The value $40 means ten to the zero power. $41 means 10, $42 means 100, and so on. $3F means .1, $3E means .01, and so on. Thus the exponent range is from $01 through $7F, meaning from 10^-63 through 10^63.

     smallest:  .1 * 10^-63
      largest:  .9999...9 * 10^63 

The mantissa bytes are considered to be a decimal fraction. The number is stored so that the most significant digit is always in the first nybble of the first byte, and the exponent is adjusted accordingly. Let's look at a few examples:

       42 14 90 00 00 00 00 00 00 00  =  14.9
       41 31 41 59 26 53 58 97 93 23  =  pi
       38 50 00 00 00 00 00 00 00 00  =  .000000005
       B8 50 00 00 00 00 00 00 00 00  =  -.000000005

Since listing the whole program at once is impossible, I have jumped right down to the lowest level so you can see how the elementary functions of addition and subtraction work. I put the origin at $0800 for this listing, but of course the final package will run wherever you assemble it for. Later we will get into I/O conversions, multiply and divide, math functions, print using, conversions between Applesoft and DP18 values, handling expressions with precedence and parentheses, and the linkage between DP18 and Applesoft.

The listing shown below has two main entry points, DSUB and DADD. You can guess what they mean! The two values to be operated on will already be unpacked into DAC and ARG by the time DSUB or DADD is called. Note that there is one extra byte for each accumulator, so that series of calculations will carry around an extra two digits of precision to avoid rounding errors. Unpacking a value into DAC involves storing the exponent byte in DAC.SIGN and then stripping the sign bit from DAC.EXPONENT.

DSUB and DADD both begin with the easiest cases, in which at least one of the values is zero. DSUB complements the value in DAC by merely toggling the sign bit, and then falls into DADD. In other words, ARG-DAC is the same as ARG+(-DAC).

DADD then determines which of the two values has the larger exponent. If necessary, it swaps ARG and DAC: the object is to have the value with the larger exponent in DAC (unless they are the same). Then the value in ARG is shifted right N digits, where N is the difference in the exponents. This what our teachers called "lining up the decimal points".

The subroutine which shifts ARG right N digits is rather smart. First, it will just fill ARG with zeros if the shift is 20 or more. Next, if the shift count is odd, it shifts right one digit position, or four bits. Then it does a direct move to shift the rest of the digits by N/2 bytes, and fills in with zero bytes on the left.

Addition is divided into two cases: either both arguments have the same sign, or they are different. If they are both the same, a simple addition loop is used. If the result carries into the next digit, DAC is shifted right one digit and a "1" is installed in the leftmost digit.

Otherwise, ARG is subtracted from DAC. If both ARG and DAC had the same exponents, it is possible that the value in ARG is larger than the value in DAC. In this case the subtracion loop will end with a "borrow" status, so the result needs to be complemented. I complement by subtracting from zero. Note that the three loops just described are all performed with the 6502 in decimal mode (the SED opcode at line 1490). CLD later reverts back to binary mode. After the mantissas are combined, the result may have one or more zero digits on the left. Therefore we go to a NORMALIZE subroutine.

NORMALIZE shifts the mantissa left until a non-zero digit is in the leftmost digit position. It also decrements the exponent for each digit-shift. I tried to do the shifting involved as intelligently as possible.

  1000  .LIF
  1010 *SAVE S.DP18 ADD & SUB
  1020 *--------------------------------
  1030 *      18-DIGIT DECIMAL FLOATING POINT
  1040 *      ADDITION AND SUBTRACTION
  1044 *--------------------------------
  1046 AS.OVRFLW  .EQ $E8D5
  1050 *--------------------------------
  1060 DAC           .BS 12
  1070 DAC.EXPONENT  .EQ DAC
  1080 DAC.HI        .EQ DAC+1
  1090 DAC.EXTENSION .EQ DAC+10
  1100 DAC.SIGN      .EQ DAC+11
  1110 *--------------------------------
  1120 ARG           .BS 12
  1130 ARG.EXPONENT  .EQ ARG
  1140 ARG.HI        .EQ ARG+1
  1150 ARG.EXTENSION .EQ ARG+10
  1160 ARG.SIGN      .EQ ARG+11
  1170 *--------------------------------
  1180 SWAP.ARG.DAC
  1190        LDY #11      SWAP 12 BYTES
  1200 .1     LDA ARG,Y
  1210        LDX DAC,Y
  1220        STA DAC,Y
  1230        TXA
  1240        STA ARG,Y
  1250        DEY
  1260        BPL .1
  1270        RTS
  1280 *--------------------------------
  1290 *      SUBTRACT DAC FROM ARG
  1300 * DAC = ARG - DAC
  1310 *--------------------------------
  1320 DSUB   LDA DAC.EXPONENT
  1330        BEQ SWAP.ARG.DAC  ARG-0=ARG
  1340        LDA DAC.SIGN
  1350        EOR #$80
  1360        STA DAC.SIGN
  1370 *--------------------------------
  1380 *      ADD ARG TO DAC
  1390 * DAC = ARG + DAC
  1400 *--------------------------------
  1410 DADD   LDA ARG.EXPONENT
  1420        BEQ .3       DAC+0=DAC
  1430 .1     SEC          COMPARE EXPONENTS
  1440        LDA DAC.EXPONENT
  1450        BEQ SWAP.ARG.DAC  ARG+0=ARG
  1460        SBC ARG.EXPONENT
  1470        BMI .8       ARG IS LARGER
  1480        JSR SHIFT.ARG.RIGHT.N
  1490        SED          SET DECIMAL MODE
  1500        LDA DAC.SIGN COMPARE SIGNS
  1510        EOR ARG.SIGN
  1520        BMI .4       OPPOSITE SIGNS
  1530 *---SAME SIGNS-------------------
  1540        CLC          SAME SIGNS, JUST ADD VALUES
  1550        LDY #9       TEN BYTES
  1560 .2     LDA DAC.HI,Y
  1570        ADC ARG.HI,Y
  1580        STA DAC.HI,Y
  1590        DEY
  1600        BPL .2
  1610        CLD          BINARY MODE
  1620        BCC .3       NO CARRY
  1630        JSR SHIFT.DAC.RIGHT.ONE
  1640        LDA DAC.HI
  1650        ORA #$10
  1660        STA DAC.HI
  1670 .3     RTS
  1680 *---DIFFERENT SIGNS--------------
  1690 .4     SEC          SUBTRACT ARG FROM FAC
  1700        LDY #9       TEN BYTES
  1710 .5     LDA DAC.HI,Y
  1720        SBC ARG.HI,Y
  1730        STA DAC.HI,Y
  1740        DEY
  1750        BPL .5
  1760        BCS .7       NO BORROW
  1770        SEC          BORROW, SO COMPLEMENT
  1780        LDY #9
  1790 .6     LDA #0
  1800        SBC DAC.HI,Y
  1810        STA DAC.HI,Y
  1820        DEY
  1830        BPL .6
  1840        LDA ARG.SIGN
  1850        STA DAC.SIGN
  1860 .7     CLD
  1870        JMP NORMALIZE.DAC
  1880 *---SWAP ARG & DAC, TRY AGAIN----
  1890 .8     JSR SWAP.ARG.DAC
  1900        JMP .1
  1910 *--------------------------------
  1920 *      SHIFT DAC RIGHT ONE DECIMAL DIGIT
  1930 *--------------------------------
  1940 SHIFT.DAC.RIGHT.ONE
  1950        INC DAC.EXPONENT
  1955        BMI .2
  1960        LDY #4       4 BITS RIGHT
  1970 .1     LSR DAC.HI
  1980        ROR DAC.HI+1
  1990        ROR DAC.HI+2
  2000        ROR DAC.HI+3
  2010        ROR DAC.HI+4
  2020        ROR DAC.HI+5
  2030        ROR DAC.HI+6
  2040        ROR DAC.HI+7
  2050        ROR DAC.HI+8
  2060        ROR DAC.HI+9 EXTENSION
  2070        DEY
  2080        BNE .1
  2090        RTS
  2095 .2     JMP AS.OVRFLW
  2100 *--------------------------------
  2110 *      SHIFT ARG RIGHT N DIGITS
  2120 *--------------------------------
  2130 SHIFT.ARG.RIGHT.N
  2140        LDY #9       SET UP FOR 10 BYTES
  2150        CMP #20      DON'T BOTHER IF OFF END
  2160        BCS .4       JUST ENTER ZERO INTO ARG
  2170        LSR          TEST SHIFT COUNT ODD OR EVEN
  2180        BCC .2       EVEN
  2190        JSR SHIFT.ARG.RIGHT.ONE
  2200 .2     TAY          # BYTES TO SHIFT
  2210        BEQ .6       NONE
  2220        EOR #$FF     -(#BYTES+1)
  2230        CLC
  2240        ADC #10      9-#BYTES
  2250        TAX
  2260        LDY #9
  2270 .3     LDA ARG.HI,X
  2280        STA ARG.HI,Y
  2290        DEY
  2300        DEX
  2310        BPL .3
  2320 .4     LDA #0
  2330 .5     STA ARG.HI,Y
  2340        DEY
  2350        BPL .5
  2360 .6     RTS
  2370 *--------------------------------
  2380 *      NORMALIZE VALUE IN DAC
  2390 *--------------------------------
  2400 NORMALIZE.DAC
  2410        LDY #-1
  2420 .1     INY          NEXT BYTE
  2430        CPY #10
  2440        BCS .7       ...NO MORE BYTES
  2450        LDA DAC.HI,Y
  2460        BEQ .1       ...STILL ZEROES
  2500 *--------------------------------
  2510 .2     TYA          TEST BYTE COUNT
  2520        BEQ .5       FIRST BYTE IS NON-ZERO
  2530        LDX #0       POINT X AT FIRST BYTE
  2540 .3     LDA DAC.HI,Y
  2550        STA DAC.HI,X
  2560        INX
  2570        INY
  2580        CPY #10
  2590        BCC .3
  2600 *--------------------------------
  2610        LDA #0       FILL REST OF DAC WITH ZEROES
  2620 .4     STA DAC.HI,X
  2630        DEC DAC.EXPONENT  ADJUST EXPONENT
  2640        DEC DAC.EXPONENT  FOR SHIFT DISTANCE
  2650        INX
  2660        CPX #10
  2670        BCC .4
  2680 *--------------------------------
  2690 .5     LDA DAC.HI   SEE IF NEED ONE-DIGIT SHIFT
  2700        AND #$F0
  2710        BNE .6       NO NYBBLE SHIFT NEEDED
  2720        DEC DAC.EXPONENT
  2730        JSR SHIFT.DAC.LEFT.ONE
  2740 .6     LDA DAC.EXPONENT
  2741        BPL .8
  2742 .7     LDA #0
  2743        STA DAC.EXPONENT
  2744        STA DAC.SIGN
  2745 .8     RTS
  2750 *--------------------------------
  2760 SHIFT.DAC.LEFT.ONE
  2770        LDY #4
  2780 .1     ASL DAC.EXTENSION
  2790        ROL DAC.HI+8
  2800        ROL DAC.HI+7
  2810        ROL DAC.HI+6
  2820        ROL DAC.HI+5
  2830        ROL DAC.HI+4
  2840        ROL DAC.HI+3
  2850        ROL DAC.HI+2
  2860        ROL DAC.HI+1
  2870        ROL DAC.HI
  2880        DEY
  2890        BNE .1
  2900        RTS
  2910 *--------------------------------
  2920 *      SHIFT ARG RIGHT ONE DECIMAL DIGIT
  2930 *--------------------------------
  2940 SHIFT.ARG.RIGHT.ONE
  2950        LDY #4
  2960 .1     LSR ARG.HI
  2970        ROR ARG.HI+1
  2980        ROR ARG.HI+2
  2990        ROR ARG.HI+3
  3000        ROR ARG.HI+4
  3010        ROR ARG.HI+5
  3020        ROR ARG.HI+6
  3030        ROR ARG.HI+7
  3040        ROR ARG.HI+8
  3050        ROR ARG.HI+9  EXTENSION
  3060        DEY
  3070        BNE .1
  3080        RTS
  3090 *--------------------------------

What That Code DidBob Sander-Cederlof

Way back in August 1981 I published a short article by John Broderick titled "What Does This Code Do?" Well, John never did tell us. But in the May 1984 Nibble, page 115, he finally has let the cat out of the bag. I think this article has probably been banging around the Nibble office for some time now, because John hasn't done anything with Apple's in quite a while. He developed a super fast accounting program in Apple II assembly language, then re-wrote the whole thing for the Sage 68000-based system. Last I heard he was in the IBM world.

The code he gave us three years ago was five bytes long:

             BRK
             PLA
             PLA
             PLA
             RTS

As published in Nibble, it is a little longer:

       BREAK BRK
             NOP
             PLA
             PLA
             JSR $FF3F
             RTS

Boiling it all down, John used this code during debugging sessions. By putting a JSR to the 8-byte program he can effect a clean breakpoint. Clean, in that he can use the monitor "G" command to continue execution after the BRK.

When JSR BREAK is executed, the BRK opcode will send Apple into the monitor and display the five registers. Their contents will have been saved at $45 thru $49. The address of the first PLA will also be saved. Typing the monitor "G" command will continue execution at that PLA. The two PLA's will pop off the return address the G command put on the stack, leaving it as it was before the BRK. The JSR $FF3F will restore the A-register, which the two PLA's clobbered. The the RTS will return right after the JSR BREAK which started this paragraph.

The original five-byte version was both confusing and erroneous. Confusing, because the PLA immediately after the BRK is never executed. BRK seems like a two-byte opcode to the 6502, so the saved address skips over the following byte. Erroneous, because the A-register has been changed by the time the RTS is executed. I think I would amend both of his versions to this:

       BREAK  BRK
              NOP
              PLA
              PLA
              LDA $45
              RTS

Making a Map of DifferencesBob Sander-Cederlof

Many times I have had two versions of the same program, and wondered where the differences might be.

For example, where are the differences between DOS 3.2 and 3.3, or between the various releases of DOS 3.3? And now that Apple has sent out some pre-releases of a new set of CDEF ROMs for the //e, where are the differences between these and the current //e ROMs?

I have always used the monitor V command to find them. By doing it a small piece at a time, I can pinpoint the changes. Then I turn on my printer and use the L command to document the new version wherever there are differences. But the piecemeal use of the V command wastes a lot of time. I wish I had some way of printing a complete map of all the differences....

What if I had a command which would compare two areas of memory, and print a map of differences? I could use a "." to represent matching locations, and a "*" to represent those that do not match. I could print either 32 or 64 per line: 32 on a 40-column screen, 64 on an 80-column screen or printer. Then I could tell at a glance where all changes had occurred!

I looked at the October 1981 issue of AAL to find out how to use the control-Y monitor command to add a new monitor feature. Then I looked in the listing of the monitor ROM (in my old "red" Apple Reference Manual) at the code for the V command and the command which prints a range of memory.

The program on the next page is the result.

Lines 1150-1190 set up the monitor control-Y vector. Booting DOS stores a branch which effectively makes the control-Y command do nothing. Storing the address of a real program there allows you to add your own commands to the monitor. Once installed, typing a control-Y into the monitor will execute the program named DIFFERENCES.

When we get there, if we typed a full length monitor command of the form "address1<address2.address3^Y" (by "^Y" I mean control-Y), all three of the addresses will have been converted to binary and stored in some standard locations. Address1 will be in $42 and $43, address2 in $3C and $3D, and address3 in $3E and $3F. We will interpret the addresses to mean to compare the block of memory beginning at address1 with the block running from address2 through address3.

Line 1220 prints a carriage return, the current address value in $3C and $3D, and a dash. Lines 1230-1280 compare the bytes at corresponding positions in the two blocks of memory, and select either a "." or a "*" accordingly. Line 1290 prints the selected character.

Lines 1300-1310 increment the two base addresses to point to the next byte in both memory blocks. The new address2 is also compared to address3 to see if we are finished yet.

Lines 1320-1350 check to see if we have printed all 32 on the current screen line. If not, back to .1 to print the next one. Otherwise, all the way back to print a new address and dash, starting a new line. If you want 64 bytes per line, change the mask in line 1330 from #$1F to #$3F. You might want to have the program check to see whether 80-columns is turned on or not, and automatically select #$1F or #$3F accordingly. You could also check to see if the output hook at $36, 37 is pointing at a printer, and use the longer lines.

Experiment. You'll learn a lot and have a lot of fun at the same time!

  1000 *SAVE S.DIFFERENCES
  1010 *--------------------------------
  1020 *      DISPLAY MAP OF DIFFERENCES
  1030 *      IN TWO MEMORY REGIONS
  1040 *
  1050 *      ADR1<ADR2.ADR3^Y
  1060 *
  1070 *--------------------------------
  1080 A1     .EQ $3C,3D
  1090 A4     .EQ $42,43
  1100 *--------------------------------
  1110 MON.NXTA4  .EQ $FCB4
  1120 MON.PRA1   .EQ $FD92
  1130 MON.COUT   .EQ $FDED
  1140 *--------------------------------
  1150 SETUP  LDA #DIFFERENCES
  1160        STA $3F9
  1170        LDA /DIFFERENCES
  1180        STA $3FA
  1190        RTS
  1200 *--------------------------------
  1210 DIFFERENCES
  1220        JSR MON.PRA1      PRINT CR, ADDRESS AND "-"
  1230 .1     LDY #0            COMPARE TWO BYTES
  1240        LDA (A1),Y
  1250        CMP (A4),Y
  1260        BEQ .2            SAME, SELECT FIRST CHAR
  1270        INY               DIFF, SELECT 2ND CHAR
  1280 .2     LDA CHARS,Y       GET DISPLAY CHAR
  1290        JSR MON.COUT      PRINT SAME OR DIFF CHAR
  1300        JSR MON.NXTA4     NEXT ADDRESS AND TEST
  1310        BCS .3            ...FINISHED
  1320        LDA A1            CHECK FOR FULL LINE
  1330        AND #$1F          OF 32
  1340        BNE .1            ...FULL YET
  1350        BEQ DIFFERENCES   ...FULL
  1360 .3     RTS
  1370 *--------------------------------
  1380 CHARS  .AS -/.*/         SAME AND DIFF CHARS
  1390 *--------------------------------

Apple Assembly Line is published monthly by S-C SOFTWARE CORPORATION, P.O. Box 280300, Dallas, Texas 75228. Phone (214) 324-2050. Subscription rate is $18 per year in the USA, sent Bulk Mail; add $3 for First Class postage in USA, Canada, and Mexico; add $12 postage for other countries. Back issues are available for $1.80 each (other countries add $1 per back issue for postage).

All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)