Skip to navigation

BBC Micro Elite

Printing decimal numbers

How to print big decimal numbers with decimal points and padding
References: BPRNT
  Elite prints out a lot of numbers, all of them in decimal (hexadecimal and
  binary numbers are used internally, but we never see them displayed). The
  BPRNT routine can print out numbers from 0.1 to 4,294,967,295 (32 set bits),
  though as the highest figure in the game is the cash pot and that stores the
  cash amount * 10, the highest figure BPRNT might ever be asked to display is
  429,496,729.5 - the biggest cash pot we can have.
  Let's first look at the algorithm for printing decimal numbers, as all our
  numbers are stored in binary, and then we'll look at the BPRNT implementation
  of it.
  Printing decimal numbers
  The algorithm is relatively simple, but it looks fairly complicated because
  we're dealing with 32-bit numbers.
  To see how it works, let's first consider a simple example with fewer digits.
  Let's say we want to print out the following number to three digits:
  First we subtract 100 repeatedly until we can't do it any more, counting how
  many times we can do this:
    567 - 100 - 100 - 100 - 100 - 100 = 67
  Not surprisingly, we can subtract it 5 times, so our first digit is 5. Now we
  multiply the remaining number by 10 to get 670, and repeat the process:
    670 - 100 - 100 - 100 - 100 - 100 - 100 = 70
  We subtracted 100 6 times, so the second digit is 6. Now to multiply by 10
  again to get 700 and repeat the process:
    700 - 100 - 100 - 100 - 100 - 100 - 100 - 100 = 0
  So the third digit is 7 and we are done.
  The BPRNT routine
  The BPRNT subroutine does exactly this in its main loop at TT36, except that
  instead of having a three-digit number and subtracting 100, we have up to an
  11-digit number and subtract 10 billion each time (as 10 billion has 11
  digits), using 32-bit arithmetic and an overflow byte, and that's where
  the complexity comes in.
  Let's look at the above algorithm in more detail. We need to implement it with
  multi-byte subtraction, which we can do byte-by-byte using the carry flag,
  but we also need to be able to multiply a multi-byte number by 10, which is
  slightly trickier. Multiplying by 10 isn't directly supported the 6502, but
  multiplying by 2 is, in the guise of shifting and rotating left, so we can do
  this to multiply K by 10:
    K * 10 = K * (2 + 8)
           = (K * 2) + (K * 8)
           = (K * 2) + (K * 2 * 2 * 2)
  And that's essentially what we do in the TT35 subroutine, just with 32-bit
  numbers with an 8-bit overflow. This doubling process is used quite a few
  times in the following, so let's look at an example, in which we double the
  number in K(S 0 1 2 3):
    ASL K+3
    ROL K+2
    ROL K+1
    ROL K
    ROL S
  First we use ASL K+3 to shift the least significant byte left (so bit 7 goes
  to the carry flag). Then we rotate the next most significant byte with ROL
  K+2 (so the carry flag goes into bit 0 and bit 7 goes into the carry), and we
  repeat this with each byte in turn, until we get to the overflow byte S. This
  has the effect of shifting the entire five-byte number one place to the left,
  which doubles it in-place.
  Finally, there are three variables that are used as counters in the above
  loop, each of which gets decremented as we go work our way through the
  digits. Their starting values are:
    XX17   The maximum number of characters to print in total (this is
           hard-coded to 11)
    T      The maximum number of digits that we might end up printing (11 if
           there's no decimal point, 10 otherwise)
    U      The loop number at which we should start printing digits or spaces
           (calculated from the U argument to BPRNT)
  We do the loop XX11 times, once for each character that we might print. We
  start printing characters once we reach loop number U (at which point we
  print a space if there isn't a digit at that point, otherwise we print the
  calculated digit). As soon as we have printed our first digit we set T to 0
  to indicate that we should print characters for all subsequent loops, so T is
  effectively a flag for denoting that we're switching from spaces to zeroes
  for zero values, and decrementing T ensures that we always have at least one
  digit in the number, even if it's a zero.