|
 |
 |
5
Arithmetic
There'll be many
things you'll want to do in ML, but complicated math is not one of
them. Mathematics beyond simple addition and subtraction (and a very
easy form of elementary division and multiplication) will not be
covered in this book. For most games and other ML for personal
computing, you will rarely need to program with any complex math. In
this chapter we will cover what you are likely to want to know.
BASIC is well-suited to mathematical programming and is far easier
to program for such tasks. Before we look at
ML arithmetic, it is worth reviewing an important concept: how the
computer tells the difference between addresses, numbers as such,
and instructions. It is valuable to be able to visualize what the
computer is going to do as it comes upon each byte in your ML
routine. Even when the computer is working
with words, letters of the alphabet, graphics symbols and the like -
it is still working with numbers. A computer works only with
numbers. The ASCII code is a convention by which the computer
understands that when the context is alphabetic, the number 65 means
the letter A. At first this is confusing. How does it know when 65
is A and when it is just 65? The third possibility is that the 65
could represent the 65th cell in the computer's memory.
It is important to remember that, like us, the computer
means different things at different times when it uses a symbol
(like 65). We can mean a street address by it, a temperature, the
cost of a milk shake, or even a secret code. We could agree that
whenever we used the symbol "65" we were ready to leave a party. The
point is that symbols aren't anything in themselves. They stand for
other things, and what they stand for must be agreed upon in
advance. There must be rules. A code is an agreement in advance that
one thing symbolizes another.
The Computer's
Rules Inside your machine, at the most basic level,
there is a stream of input. The stream flows continually past a
"gate" like a river through a canal. For 99 percent of the time,
this input is zeros. (BASICs differ; some see continuous 255's, but
the idea is the same.) You turn it on and the computer sits there.
What's it doing? It might be updating a clock, if you have one, and
it's holding things coherent on the TV screen - but it mainly waits
in an endless loop for you to press a key on your keyboard to let it
know what it's supposed to do. There is a memory cell inside (this,
too, varies in its location) which the computer constantly checks.
On some computers, this cell always has a 255 in it unless a key is
pressed. If you press the RETURN key, a 13 will replace the 255. At
last, after centuries (the computer's sense of time differs from
ours) here is something to work with! Something has come up to the
gate at long last. You notice the effect at
once - everything on the screen moves up one line because 13 (in the
ASCII code) stands for carriage return. How did it know that you
were not intending to type the number 13 when it saw 13 in the
keyboard sampling cell? Simple. The number 13, and any other
keyboard input, is always read as an ASCII number.
In ASCII, the digits from 0 through 9 are the only number
symbols. There is no single symbol for 13. So, when you type in a 1
followed immediately by a 3, the computer's input-from-the-keyboard
routine scans the line on the screen and notices that you have not
pressed the "instant action" keys (the STOP, BREAK, ESC, TAB,
cursor-control keys, etc.). Rather, you typed 1 and 3 and the
keyboard sampling cell (the "which key pressed" address in zero
page) received the ASCII value for one and then for three. ASCII
digits are easy to remember in hex: zero is 30, 1 is 31, and up to
39 for nine. In decimal, they are 48 through 57.
The computer decides the "meaning" of the numbers which flow
into and through it by the numbers' context. If it is in
"alphabetic" mode, the computer will see the number 65 as "a"; or if
it has just received an "a," it might see a subsequent number 65 as
an address to store the "a". It all depends on the events that
surround a given number. We can illustrate this with a simple
example:
2000
LDA #65 A9 (169) 41
(65) 2000 STA $65 85
(133) 41 (65)
This short
ML program (the numbers in parentheses are the decimal values) shows
how the computer can "expect" different meanings from the number 65
(or 41 hex). When it receives an instruction to perform an
action, it is then prepared to act upon a number. The instruction
comes first and, since it is the first thing the computer sees when
it starts a job, it knows that the A9 (169) is not a number.
It has to be one of the ML instructions from its set of instructions
(see Appendix A).
Instructions And Their
Arguments The computer would no more think of this
first 169 as the number 169 than you would seal an envelope before
the letter was inside. If you are sending out a pile of Christmas
cards, you perform instruction-argument just the way the computer
does: you (1) fill the envelope (instruction) (2) with a card
(argument or operand). All actions do something to something. A
computer's action is called an instruction (or, in its numeric form
inside the computer's memory it's called an opcode for operation
code). The target of the action is called the instruction's argument
(operand). In our program above, the computer must LoaD Accumulator
with 65. The # symbol means "immediate"; the target is right there
in the next memory cell following the mnemonic LDA, so it isn't
supposed to be fetched from a distant memory cell.
Then the action is complete, and the next number (the 133
which means STore Accumulator in zero page, the first 256 cells) is
seen as the start of another complete action. The action of storing
always signals that the number following the store instruction must
be an address of a cell in memory to store to.
Think of the computer as completing each action and then
looking for another instruction. Recall from the last chapter that
the target can be "implied" in the sense that INX simply increases
the X register by one. That "one" is "implied" by the instruction
itself, so there is no target argument in these cases. The next cell
in this case must also contain an instruction for a new
instruction-argument cycle. Some instructions
call for a single-byte argument. LDA #65 is of this type. You cannot
LoaD Accumulator with anything greater than 255. The accumulator is
only one byte large, so anything that can be loaded into it can also
be only a single byte large. Recall that $FF (255 decimal) is the
largest number that can be represented by a single byte. STA $65
also has a one byte argument because the target address for the
STore Accumulator is, in this case, in zero page. Storing to zero
page or loading from it will need only a one byte argument - the
address. Zero page addressing is a special case, but an assembler
program will take care of it for you. It will pick the correct
opcode for this addressing mode when you type LDA $65. LDA $0065
would create ML code that performs the same operation though it
would use three bytes instead of two to do it.
The program counter is like a finger that keeps track of where
the computer is located in its trip up a series of ML instructions.
Each instruction takes up one, two, or three bytes, depending on
what type of addressing is going on.
Context Defines
Meaning TXA uses only one byte so the program counter
(PC) moves ahead one byte and stops and waits until the value in the
X register is moved over to the accumulator. Then the computer asks
the PC, "Where are we?" and the PC is pointing to the address of the
next instruction. It never points to an argument. It skips over them
because it knows how many bytes each addressing mode uses up in a
program. Say that the next addresses contain
an LDA $15. This is two bytes long (zero page addressing). The PC is
raised by two. The longest possible instruction would be using three
bytes, such as LDA $5000 (absolute addressing). Here the argument
takes up two bytes. Add that to the one byte used by any instruction
and you have a total of three bytes for the PC to count off. Zero
page LDA is represented by the number A5 and Absolute LDA is AD.
Since the opcodes are different, even though the mnemonics are
identical, the computer can know how many bytes the instruction will
use up. Having reviewed the way that your
computer makes contextual sense out of the mass of seemingly similar
numbers of which an ML program is composed, we can move on to see
how elementary arithmetic is performed in ML.
Addition Arithmetic
is performed in the accumulator. The accumulator holds the first
number, the target address holds the second number (but is not
affected by the activities), and the result is left in the
accumulator. So:
LDA #$40
(remember, the # means immediate, the $ means
hex) ADC #$01
will
result in the number 41 being left in the accumulator. We could then
STA that number wherever we wanted. Simple enough. The ADC means ADd
with Carry. If this addition problem resulted in a number higher
than 255 (if we added, say, 250+6), then there would have to be a
way to show that the number left behind in the accumulator was not
the correct result. What's left behind is the carry. What would
happen after adding 250+6 is that the accumulator would contain a 1.
To show that the answer is really 256 (and not 1), the "carry flag"
in the status register flips up. So, if that flag is up, we know
that the real answer is 255 plus the 1 Left in the
accumulator. To make sure that things never
get confused, always put in a CLC (CLear Carry) before any addition
problems. Then the flag will go down before any addition and, if it
is up afterward, we'll know that we need to add 256 to whatever is
in the accumulator. We'll know that the accumulator holds the carry,
not the total result. One other point about
the status register: there is another flag, the "decimal" flag. If
you ever set this flag up (SED), all addition and subtraction is
performed in a decimal mode in which the carry flag is set when
addition exceeds 99. In this book, we are not going into the decimal
mode at all, so it's a good precaution to put a CLear Decimal mode
(CLD) instruction as the first instruction of any ML program you
write. After you type CLD, the flag will be put down and the
assembler will move on to ask for your next instruction, but all the
arithmetic from then on will be as we are describing
it.
Adding
Numbers Larger Than 255 We have already discussed the
idea of setting aside some memory cells as a table for data. All we
do is make a note to ourselves that, say, $80 and $81 are declared a
zone for our personal use as a storage area. Using a familiar
example, let's think of this zone as the address that holds the
address of a ball-like character for a game. As long as the
addresses are not in ROM, or used by our program elsewhere, or used
by the computer (see your computer's memory map), it's fine to
declare any area a data zone. It is a good idea (especially with
longer programs) to make notes on a piece of paper to show where you
intend to have your subroutines, your main loop, your
initialization, and all the miscellaneous data - names, messages for
the screen, input from the keyboard, etc. This is one of those
things that BASIC does for you automatically, but which you must do
for yourself in ML. When BASIC creates a
string variable, it sets aside an area to store variables. This is
what DIM does. In ML, you set aside your own areas by simply finding
a safe and unused memory space and then not writing a part of your
program into it. Part of your data zone can be special registers you
declare to hold the results of addition or subtraction. You might
make a note to yourself that $80 and $81 will hold the current
address of the bouncing ball in your game. Since the ball is
constantly in motion, this register will be changing all the time,
depending on whether the ball hit a wall, a paddle, etc. Notice that
you need two bytes for this register. That is because one byte could
hold only a number from 0 to 255. Two bytes together, though, can
hold a number up to 65535. In fact, a
two-byte register can address any cell in most microcomputers
because most of us have machines with a total of 65536 memory cells
(from zero to 65535). So if your ball is located (on your screen) at
$8000 and you must move it down one, just change the ball-address
register you have set up. If your screen has 40 columns, you would
want to add 40 to this register. The ball
address register now looks like this: $0080 00 80 (remember that the
higher, most significant byte, comes after the LSB, the least
significant byte in the 6502's way of looking at pointers). We want
it to be: $0080 28 80. (The 28 is hex for 40.) In other words, we're
going to move the ball down one line on a 40-column
screen. Remember the "indirect Y" addressing
mode described in the previous chapter? It lets us use an address in
zero page as a pointer to another address in memory. The number in
the Y register is added to whatever address sits in 80,81, so we
don't STA to $80 or $81, but rather to the address that they
contain. STA ($80),Y or, using the simplified punctuation rules of
the Simple Assembler: STA (80)Y.
Moving A Ball
Down How to add $28 to the ball address register?
First of all, CLC, clear the carry to be sure that flag is down. To
simplify our addition, we can set aside another special register
which serves only to hold the $28 as a double-byte number all
through the game: $4009 28 00. This is the size of one screen line
in our 40-column computer and it won't change. Since it moves the
ball down one screen line, it can be used equally well for a
subtraction that would move the ball up one screen line as well. Now
to add them together:
1000
CLC (1000 is our "add 40
to ball address" subroutine) 1001 LDA
$80 (we fetch the LSB of ball
address) 1003 ADC $4009
(LSB of our permanent screen line size) 1006 STA $80 (put the
new result into the ball address) 1008
LDA $81 (get the MSB of ball
address) 100A ADC $400A
(add with carry to the MSB of screen
value) 100D STA $81
(update the ball address MSB)
That's it. Any carry will automatically set the carry
flag up during the ADC action on the LSB and will be added into the
result when we ADC to the MSB. It's all quite similar to the way
that we add ordinary decimal numbers, putting a carry onto the next
column when we get more than a 10 in the first column. And this
carrying is why we always CLC (clear the carry flag, putting it
down) just before additions. If the carry is set, we could get the
wrong answer if our problem did not result in a carry. Did the
addition above cause a carry? Note that we
need not check for any carries during the MSB+MSB addition. Any
carries resulting in a screen address greater than $FFFF (65535)
would be impossible on our machines. The 6502 is permitted to
address $FFFF tops, under normal conditions.
Subtraction As you
might expect, subtracting single-byte numbers is a
snap:
LDA #$41 SBC
#$01
results in a $40 being left in the
accumulator. As before, though, it is good to make it a habit to
deal with the carry flag before each calculation. When subtracting,
however, you set the carry flag: SEC. Why is unimportant. Just
always SEC before any subtractions, and your answers will be
correct. Here's double subtracting that will move the ball up the
screen one line instead of down one line:
$1020 SEC
($1020 is our "take 40 from ball address"
subroutine) 1021 LDA $80
(get the LSB of ball address) 1023 SBC $4009 (LSB of our
permanent screen line value) 1026 STA $80
(put the new result into the ball
address) 1028 LDA $81
(get the MSB of ball address) 102A SBC $400A
(subtract the MSB of screen value) 102D STA $81 (update
the ball address MSB)
Multiplication And
Division Multiplying could be done by repeated adding.
To multiply 5 x 4, you could just add 4+4+4+4+4. One way would be to
set up two registers like the ones we used above, both containing
04, and then loop through the addition process five times. For
practical purposes, though, multiplying and dividing are much more
easily accomplished in BASIC. They simply are often not worth the
trouble of setting up in ML, especially if you will need results
involving decimal points (floating point arithmetic). Perhaps
surprisingly, for the games and personal computing tasks where
creating ML routines is useful, there is little use either for
negative numbers or arithmetic beyond simple addition and
subtraction. If you find that you need
complicated mathematical structures, create the program in BASIC,
adding ML where super speeds are necessary or desirable. Such hybrid
programs are efficient and, in their way, elegant. One final note:
an easy way to divide the number in the accumulator by two is to LSR
it. Try it. Similarly, you can multiply by two with ASL. We'll
define LSR and ASL in the next chapter.
Double
Comparison One rather tricky technique is used fairly
often in ML and should be learned. It is tricky because there are
two branch instructions which seem to be worth using in this
context, but they are best avoided. If you are trying to keep track
of the location of a ball on the screen, it will have a two-byte
address. If you need to compare those two bytes against another
two-byte address, you need a "double compare" subroutine. You might
have to see if the ball is out of bounds or if there has been a
collision with some other item flying around on screen. Double
compare is also valuable in other kinds of ML programming.
The problem is the BPL (Branch on PLus) and BMI (Branch
on MInus) instructions. Don't use them for comparisons. In any
comparisons, single- or double-byte, use BEQ to test if two numbers
are equal; BNE for not equal; BCS for equal or higher; and BCC for
lower. You can remember BCS because its "S" is higher and BCC
because its "C" is lower in the alphabet. To see how to perform a
double-compare, here's one easy way to do it. (See Program
5-1.)
Program
5-I. Double Compare.
|
0005
|
.BA $1010
|
|
0010
|
; ------- STORAGE AREAS
-------
|
|
0020
|
TESTED .DE
$1000
|
|
0030
|
SECOND .DE
$1002
|
|
0040
|
TEMP
.DE $1008
|
|
0050
|
;
|
|
0060
|
; ------- LANDING PLACES
------
|
|
0070
|
LOWER .DE
$1004
|
|
0080
|
EQUAL .DE
$1005
|
|
0090
|
HIGHER .DE
$1006
|
|
0100
|
;
|
1010- 38
|
0110
|
START
SEC
|
1011- AD 00
10
|
0120
|
LDA TESTED ; COMPARE THE LOW
BYTES
|
1014- ED 02
10
|
0130
|
SBC SECOND
|
1017- 8D 08
10
|
0140
|
STA TEMP
|
101A- AD 01
10
|
0150
|
LDA TESTED+1 ; COMPARE THE HIGH
BYTES
|
101D- ED 03
10
|
0160
|
SBC SECOND+1
|
1020- 0D 08
10
|
0170
|
ORA TEMP
|
1023- F0
E0
|
0180
|
BEQ EQUAL ; TESTED =
SECOND
|
1025- 90
DD
|
0190
|
BCC LOWER ; TESTED <
SECOND
|
1027- B0
DD
|
0200
|
BCS HIGHER ; TESTED >
SECOND
|
|
0210
|
.EN
|
This is a full-dress, luxurious assembler at work. With
such assemblers you can use line numbers and labels, add numbers to
labels (see TESTED +1 in line 150), add comments, and all the rest.
To try this out, type in the hex bytes on the left, starting at
address $1010, which make up the program itself. Then fill bytes
$1000-100f with zeros - that's your storage area for the numbers you
are comparing as well as a simulated "landing place" where your
computer will branch, demonstrating that the comparison worked
correctly. Now try putting different numbers
into the two-byte zones called TESTED and SECOND. TESTED, at $1000,
is the first, the tested, number. It's being tested against the
second number, called SECOND. As you can see, you've got to keep it
straight in your mind which number is the primary number. There has
to be a way to tag them so that it means something when you say that
one is larger (or smaller) than the other.
When you've set up the numbers in their registers ($1000 to
$1003), you can run this routine by starting at $1010. All that will
happen is that you will land on a BRK instruction. Where you land
tells you the result of the comparison. If the numbers are equal,
you land at $1005. If the TESTED number is less than the SECOND
number, you'll end up at $1004. If all you needed to find out was
whether they were unequal, you could use BNE. Or you could leave out
branches that you weren't t interested in. Play around with this
routine until you've understood the ideas involved.
In a real program, of course, you would be branching to the
addresses of subroutines which do something if the numbers are equal
or greater or whatever. This example sends the computer to $1004,
$1005, or $1C06 just to let you see the effects of the
double-compare subroutine. Above all, remember that comparing in ML
is done with BCS and BCC (not BPL or BMI).
Return to Table
of Contents | Previous
Chapter | Next
Chapter |
| |
|