Skip to navigation

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 ADC #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 BCC scup3 \ If WW < 13, jump to scup3 .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 BCC scup7 \ If WW < 4, jump to scup7 .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 \ beencalled 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