Machine Language: Jumbo Numbers
Jim Butterfield
Toronto, Canada
A single byte will hold an unsigned number whose value may be from 0 to 255. Most of us, sooner or later, want to handle larger numbers. The techniques are fairly straightforward.
A number may occupy several bytes of storage. The usual convention is for the higher order bytes to contain powers of 256. In simple terms, this means that one byte counts in "ones"; another byte counts in "256-s"; the next byte, if used, counts in "4096-s" and so on. It's easier than it sounds if you convert the number to hexadecimal. One million, which in hexadecimal is 0F4240, fits nicely into three bytes: from high order to low order these bytes contain 0F, 42, and 40 hexadecimal.
It is possible to hold numbers in a decimal type of format. This makes input and output easy, since no conversion is needed to convert the decimal digits, and addition and subtraction can be quite easily accomplished. More complex arithmetic is difficult—even multiplication and division requires an effort—so that we choose binary if any real math crunching is needed. Decimal numbers can be held two ways: packed, with two digits to a byte; and unpacked, with one digit to a byte.
Sizing
We must make room for the largest possible numbers we expect to handle. The following table may be halpful:
Unsigned | Signed | Packed Decimal | |
1 Byte: | 0 to 255 | -128 to + 127 | 0 to 99 |
2 Bytes: | 0 to 65535 | -32768 + 32767 | 0 to 9999 |
3 Bytes: | 0 to 1677215 | -8388608 to + 8388607 | 0 to 999999 |
The table grows proportionately; if a count of over sixteen million in three bytes won't do, four bytes reaches to over four billion (after taxes, that's four thousand million in Great Britain). Enough for most applications, but you can continue to add bytes as you wish.
What about fractions? The most common method is to use an assumed decimal point. In other words, count in pennies instead of in dollars and you won't need fractions. There are more exacting methods, but most of us sidestep them if we can.
Memory Arrangement
There's really no special law regarding how you arrange these bytes in memory. You can have high order values at the higher addresses, or turn it around and have high order values at the low end. I like to have low order at the low address end, etc.: it's easier to remember and is more consistent with address modes. On the other hand, storing the bytes the other way around (high order at the low address) makes it a little easier to handle a number with indexing. Why? Well, if we have to test an index register for the end of its range with CPX or CPY, we'll affect the Carry flag… and we often need that flag to link information between the various bytes. A fine point; the choice is really up to you.
You can even scatter the values through memory rather than having them consecutive. Often it's better to keep them together so that you can "walk through" a number using indexing. But there are exceptions to every rule.
Some Simple Operations
We can manipulate multi-byte numbers just as readily as single bytes. All we need is some new rules.
For the following sample code, let's assume a two-byte value stored in locations 0300 low-order and 0301 high-order.
Moving: move both bytes instead of one. To move 0300/0301 to 0320/0321 we might code: LDX #$01; LOOP: LDA $0300,X; STA S0320,X; DEX; BPL LOOP. We have moved the high order byte first, but this makes no difference.
Addition and subtraction: start at the low end; fix up the Carry flag before you start, and then let the Carry link the bytes together. To add the contents of $0300/0301 to $0320/0321 and place the result at $0320/0321, we might code: CLC; LDA $0300; ADC $0320; STA $0320; LDA $0301; ADC $0321; STA $0321. Note that it's vital that we start at the low end of the numbers, in this case the low addresses. We might wish to check to insure that the result hasn't overflowed (overflew?) the space available. For unsigned numbers, we do this by checking that the Carry flag is clear.
Subtraction goes the same way, except we give SEC and use the SBC command. A valid subtraction will complete with the Carry flag set; otherwise there's an unsigned number overflow.
Comparisons
Comparison is a little different from the single-byte compare. We need to decide in advance if we're testing for equality or for greater-than; it's hard to check for both in a single sequence.
Equality tests are quite straightforward: test each of the pairs of bytes, and if any are not the same, the two values are unequal. We might code: LDX #$01; LOOP: LDA $0300, X; CMP $0320,X; BNE UNEQUAL; DEX; BPL LOOP; EQUAL: … The code is fairly self-evident.
To compare for greater-than, we might do a full subtraction. We won't need to keep the result; the flags will tell us the answer. We might code: SEC; LDA $0300; SBC $0320; LDA $0301; SBC $0321. At this point, the Carry flag will be set if the value in $0300/0301 is greater than or equal to that in $0320/0321.
Its possible to compare from the high-order end down, on the theory that if the first byte is different, you don't need to look at the rest. Additionally, such a comparison can more easily test both equal and greater-than conditions. There's often not much difference; speed is likely to depend on whether or not the numbers are likely to be close or far apart.
Shifts And Rotates
Shifts and Rotates propogate readily through the Carry bit. The first operation must start at the proper end of the number: Right shifts start from the high end, Left shifts from the low. The remaining operations, which work their way through the number, must always be Rotates, regardless of whether the overall operation is Shift or Rotate.
To shift the two-byte number at $0300/0301 left, we might code: ASL $0300; ROL $0301. To rotate the same number, we would give ROL $0300; ROL $0301.
To shift the same number right, we would code; LSR $0301; ROR $0300. Finally we would rotate the number right with ROR $0301; ROR $0300.
Big numbers are not much harder to work with than small ones. All the usual operations are still available to you. There are more items to keep track of, but that's a natural result of expansion.
Make provision for future big numbers now. You wouldn't want to tell your boss that he can't give you a raise because there isn't room enough in the computer to hold what he wants to pay you…