Aviator on the BBC Micro

# Maths: ScaleUp

```       Name: ScaleUp                                                 [Show more]
Type: Subroutine
Category: Maths
Summary: Scale up a 16-bit number until it doesn't fit into 16 bits any
more
Context: See this subroutine in context in the source code
References: This subroutine is called as follows:
* DivideScaled calls ScaleUp
* ProjectPoint (Part 2 of 3) calls ScaleUp

Given a positive 16-bit argument, this routine scales the number up until it
doesn't fit into 16 bits any more. It does this by doubling it (i.e. shifting
it left) until a set bit pops out of the left end).

The number of shifts is returned (in the form of the minimum number of binary
digits in the original number) so we know how much the value was scaled up by.

Arguments:

(X Y)                A positive 16-bit number containing a coordinate
magnitude

Z flag               Set according to the high byte in X (i.e. the routine is
called after setting register X)

R                    Contains %00010000 (which is set at the start of
ProjectPoint)

Returns:

(A Y)                The original argument in (X Y), left-shifted until a set
bit pops out of the left end, so the leftmost set bit is
not in (A Y)

WW                   WW + 1 is the minimum number of binary digits that the
original value of (X Y) fitted into (from which we can
calculate the number of shifts we had to do to scale the
number up)

.ScaleUp

BEQ scup4              \ If the high byte in X = 0, jump down to scup4

LDA divisionLo,X       \ Set A = int(log2(X))
AND #%00000111         \
\ so we know X fits into a minimum of A + 1 binary
\ digits

CLC                    \ Set WW = A + 8
STA WW                 \ so (X Y) fits into WW + 1 binary digits

CMP #13                \ Set the flags for the WW < 13 comparison below

TXA                    \ Set (A T) = (X Y)
STY T

.scup1

\ If we get here, then (X Y) fits into 13 + 1 digits or
\ more, i.e. it fits into 14 digits or more
\
\ Also, (A T) = (X Y)

ASL T                  \ Left-shift (A T) until we shift a 1 out of bit 7 of
ROL A                  \ the high byte in A
BCC scup1

LDY T                  \ Set Y = T

\ So now we have:
\
\   (A Y) = (A T)
\
\ which is the result that we want

RTS                    \ Return from the subroutine

.scup2

ASL T                  \ Left-shift (A T) by one place
ROL A

.scup3

\ If we get here, then (X Y) fits into fewer than 13 + 1
\ binary digits, i.e. it fits into 13 digits or fewer
\
\ Also, (A T) = (X Y)

BIT R                  \ If bit 4 of A is clear, loop back to scup2 to keep
BEQ scup2              \ shifting (A T) left until bit 4 of A is set (we check
\ bit 4 because R = %00010000)

TAY                    \ Set (Y X) = (A T)
LDX T                  \
\ so (Y X) contains the original value of (X Y), shifted
\ left until bit 4 of X is set (note that the X and Y
\ have swapped round here)
\
\ Let's say (Y X) = (%AAAAaaaa %TTTTtttt), which is the
\ original (X Y) shifted left until bit 4 is set

LDA shift4Right,X      \ Set A = (X >> 4) OR (Y << 4)
ORA shift4Left,Y       \       = (%TTTTtttt >> 4) OR (%AAAAaaaa << 4)
\       = %aaaaTTTT

LDY shift4Left,X       \ Set Y = X << 4
\       = %TTTTtttt << 4
\       = %tttt0000

\ So we now have:
\
\   (A Y) = (%aaaaTTTT %tttt0000)
\         = (%AAAAaaaa %TTTTtttt) << 4
\         = (A T) << 4
\         = (X Y) << 4
\
\ which is the result that we want

RTS                    \ Return from the subroutine

.scup4

\ If we get here, then the high byte in X = 0

CPY #0                 \ If the low byte in Y = 0, jump down to scup8
BEQ scup8

LDA divisionLo,Y       \ Set WW = int(log2(Y))
AND #%00000111         \
STA WW                 \ so we know Y fits into a minimum of WW + 1 binary
\ digits

CMP #4                 \ Set the flags for the WW < 4 comparison below

TYA                    \ Set (A Y) = (Y 0)
LDY #0

.scup5

\ If we get here, then (X Y) fits into 4 + 1 digits or
\ more, i.e. it fits into 5 digits or more, so
\ (X Y) = (0 Y)
\
\ Also, (A Y) = (Y 0), which effectively does the first
\ 8 left-shifts for us, as we know none of those shifts
\ will shift a 1 out of bit 7 of the high byte

ASL A                  \ Left-shift (A Y) until we shift a 1 out of bit 7 of
BCC scup5              \ the high byte in A (we don't need to shift the low
\ byte as we know it's 0)

\ So now we have:
\
\   (A Y) = (X Y) shifted left until we shift a 1 out of
\           bit 7
\
\ which is the result that we want

RTS                    \ Return from the subroutine

.scup6

\ If we get here, then (X Y) fits into fewer than 4 + 1
\ binary digits, i.e. it fits into 4 digits or fewer, so
\ (X Y) = (0 Y) = (0 %0000yyyy)
\
\ Also, (A Y) = (Y 0), which effectively does the first
\ 8 left-shifts for us, as we know none of those shifts
\ will shift a 1 out of bit 7 of the high byte

ASL A                  \ Left-shift (A Y) by one place (we don't need to shift
\ the low byte as we know it's 0)

.scup7

BIT R                  \ If bit 4 of A is clear, loop back to scup6 to keep
BEQ scup6              \ shifting (A Y) left until bit 4 of A is set (we check
\ bit 4 because R = %00010000), so now we have something
\ like this (depending on how many of %yyyy are set):
\
\   (A Y) = (%0001yyy0 %00000000)

TAX                    \ Set A = A << 4
LDA shift4Left,X       \
\ which moves the result into the top byte of (A Y) like
\ this:
\
\   (A Y) = (%yyy00000 %00000000)
\
\ which is the result that we want

RTS                    \ Return from the subroutine

.scup8

\ If we get here, then (X Y) = 0

TSX                    \ We can only get here if we called this routine from
INX                    \ the DivideScaled routine, as the only other call of
INX                    \ this routine is from ProjectPoint, when we know we
TXS                    \ are calling it with a value of at least 1 in (X Y)
\
\ These instructions remove two bytes from the top of
\ the stack so the RTS below returns an extra level up
\ the call chain, and as DivideScaled itself must have
\ been called from ProjectPoint, this returns us to
\ ProjectPoint with the following results

LDA #0                 \ Set (Q P) = 0
STA Q
STA P

LDX UU                 \ Set WW = UU - 1
DEX
STX WW

RTS                    \ Return from the subroutine
```