These routines add, subtract and multiply BCD numbers with up to 14-15 digits. The source code is quite commented, so i'll leave it without further discussions.
One small note: This code was originally intended to build a toy-calculator around the stamp. This explains the interface to an LCD for the display, the reference made in code to "keys" and the BCD approach.
If you want to download this program, I suggest you to save it using your Browser's "Save as.." function, and then selecting "Text file (*.TXT)" as format. This will automatically strip the source code of the embedded HTML tags.
If you download/use/change this program, please drop me a note to the asdress f.bonomi@agora.stm.it
' *************
' * BCDMATH *
' *************
' vers. 0.5
' BCD addition, subtraction and multiplication with
' arbitrary precision for the Basic Stamp II
' Copyright Francesco Bonomi 1996-1998, non-commercial use permitted
' Please send comments, corrections and bugs to f.bonomi@agora.stm.it
' Important constants and variables
' How many digits?
digits con 10
' maximum 16, as nibble variables are used to loop through the digits
' Anyway, if you use multiplication and allocate the multiply buffer
' then you don't have enough memory for 16 digits!
' We have three BCD buffers (most significant digit first)
' Accumulator
acc var nib(digits)
' The buffer (keyboard input goes here)
buf var nib(digits)
' The multiplication buffer
mul var nib(digits)
overflow var bit ' True if we had overflow
accneg var bit ' True if the accumulator is negative
oldneg var bit ' Used to temporarily store the status of accneg
' work variables
x var nib
y var nib
z var nib
b var bit
l var byte ' used in intermediate calulations, to hold
' the sum/difference between nibbles
' used for parameter passing
adigit var nib
' definitions for the LCD panel
' Serial data to the LCD
LCD_out con 15
' data mode for the LCD panel
n96n con $4054
' TEST PROGRAM FOR THE BCD SUBROUTINES
' fetch a number (1219) in the buffer
l=num1
gosub buf_read
' move it in the accumulator
gosub clear_acc
gosub acc_add
' fetch a second number (500)
l=num2
gosub buf_read
' subtract
gosub acc_sub
' display the result (1219456-1113412=106044)
gosub display_acc
' fetch a third number (2357) in the buffer
l=num3
gosub buf_read
' multiply the accumulator times the buffer
gosub acc_mul
' display the result (106044*2357=249945708)
gosub display_acc
end
num1 data "1219456:"
num2 data "1113412:"
num3 data "2357:"
' **************************
' SUBROUTINES FOLLOW
' **************************
'Subroutine ACC_ABS: Absolute value
ACC_ABS
' The accumulator is replaced by its absolute value
' (it is negated if negative)
if accneg=1 then ACC_NEG
return
'Subroutine ACC_ADD: Addition
ACC_ADD
' The buffer is added to the accumulator
for x=digits to 1 step 1
l=acc(x)+buf(x)
if l<10 then ACC_ADD_next_digit
' we have a carry! the digit becomes 0
l=l-10
' if this was the first digit, then we have a carry
' out of the most significant digit, otherwise it's
' a normal carry
if x>1 then ACC_ADD_normal_carry
' a carry out of the most significant digit
' is an overflow, unless the accumulator was negative,
' (in this case the accumulator goes back to positive)
if accneg=1 then ACC_ADD_carry_neg
' mark an overflow
gosub SET_ERROR
' and proceed
goto ACC_ADD_next_digit
ACC_ADD_carry_neg
' the accumulator returns positive
accneg=0
' proceed
goto ACC_ADD_next_digit
ACC_ADD_normal_carry
' The carry was an ordinary carry, increment
' the digit before
acc(x-1)=acc(x-1)+1
ACC_ADD_next_digit
acc(x)=l
next
return
'Subroutine ACC_MUL: Multiplication
ACC_MUL
' Multiplies the accumulator by the buffer
' Force the accumulator to positive, but remember
' its old sign, so that we can restore it later
' (as the buffer is always positive, the accumulator
' will keep its old sign after multiplication)
oldneg=accneg
gosub acc_abs
' Move the buffer in the multiply buffer,
' the accumulator in the buffer and
' clear the accumulator
for z=1 to digits
mul(z)=buf(z)
buf(z)=acc(z)
acc(z)=0
next
' we will use b to mark a shift-out condition
b=0
for z=digits to 1
if mul(z)=0 then ACC_MUL_skip
w var nib
w=mul(z)
for y=1 to w
gosub acc_add
' if we add after a shift-out condition, it's overflow!
if b=0 then ACC_MUL_no_over
gosub set_error
ACC_MUL_no_over
next
ACC_MUL_skip
' shift the buffer one place up, first checking
' for a shift-out
if buf(1)=0 then ACC_MUL_no_shiftout
b=1
ACC_MUL_no_shiftout
' shifting up is like pressing a "0" key!
adigit=0
gosub digit_key
next
' if the accumulator was negative, make it negative again
' by jumping into acc_neg
if oldneg then ACC_NEG
return
'Subroutine ACC_NEG: Negation
ACC_NEG
' The accumulator is negated
' (replaced by it's two's complement)
for x=1 to digits-1
acc(x)=9-acc(x)
next
acc(digits)=10-acc(digits)
accneg=1-accneg
return
'Subroutine ACC_SUB: Subtraction
ACC_SUB
' The buffer is subtracted from the accumulator
' b is used as a borrow flag
b=0
for x=digits to 1 step 1
l=acc(x)-buf(x)-b
b=0
' did we have a borrow?
if l<128 then ACC_SUB_next_digit
' Yes! remeber the borrow, and bring l in positiveness again
b=1
l=l+10
ACC_SUB_next_digit
acc(x)=l
next
' Do we have a borrow over the most significant digit?
if b=0 then ACC_SUB_exit
' then, if the accumulator was positive it becomes negative,
' otherwise it is an underflow!
if accneg then SET_ERROR
accneg=1
ACC_SUB_exit
return
'Subroutine BUF_READ: Read a constant from EEPROM in the buffer
BUF_READ
' reads a constant from EEPROM in the buffer
' the constant is pointed by l (a byte variable, this
' limits to 256 bytes of constants, but can be changed)
' and is stored as a decimal string terminated with ":",
' like in num data "12345678:"
gosub clear_buf
BUF_READ_loop
read l, adigit
' VERY DIRTY trick: a character "0" is stored in memory
' as ASCII 48 (hex $30). In a normal computer, after
' having read the ascii code, we would have to subtract
' 48 from the value, to obtain its numeric value.
' Here, I declared adigit as a nibble variable (4 bits)
' and this means that hex $30 is automatically truncated
' to $0, i.e., the conversion is automatic. The same holds
' for the other digits: character "1" is ASCII 49 (hex $31)
' that gets truncated to $1. The terminating colon ":",
' ASCII 58 (hex $3A) is truncated to $A, and this is what
' gets checked below:
if adigit=$A then BUF_READ_exit
gosub digit_key
l=l+1
goto BUF_READ_loop
BUF_READ_exit
return
'Subroutine CLEAR_ACC: Clear Accumulator
CLEAR_ACC
' Clears the accumulator
for x=1 to DIGITS
acc(x)=0
next
accneg=0
return
'Subroutine CLEAR_BUF: Clear Buffer
CLEAR_BUF
' Clears the buffer
for x=1 to DIGITS
buf(x)=0
next
return
'Subroutine DIGIT_KEY: Store a digit in the buffer
DIGIT_KEY
' The last key pressed (if any) goes
' in the low-order nibble, and the
' buffer is shifted upwards.
' Originally meant in the calculator to handle the
' pressure of a key, also used by ACC_MUL and BUF_READ
' to shift the buffer up.
' if this would cause an overflow, does nothing and exits
if buf(1)>0 then DIGIT_KEY_over
for x=1 to digits-1
buf(x)=buf(x+1)
next
buf(digits)=adigit
DIGIT_KEY_over:
return
'Subroutine SET_ERROR: Set an error condition
SET_ERROR
overflow=1
serout LCD_out, n96n, 5, [254,1,"ERROR"]
debug "ERROR", cr
return
'Subroutine DISPLAY_ACC: display Accumulator
DISPLAY_ACC
' Display the accumulator
' the value is displayed on the PC screen (with DEBUG) AND on a
' LCD panel (with SEROUT)
' in real life, remove the useless DEBUG instrucions!
if overflow=1 then DISPLAY_ACC_exit
' clear the LCD display
serout LCD_out, n96n, [254,1] : pause 10
' If the accumulator is negative, write
' a '-', and temporarily negate the accumulator
oldneg=0
if accneg=0 then DISPLAY_ACC_no_minus
debug "-"
serout LCD_out, n96n, ["-"]
oldneg=accneg
gosub acc_abs
DISPLAY_ACC_no_minus
' we will use b to mark if we have displayed any digit
' yet, for trailing-zero suppression
b=0
for x = 1 to digits
adigit = acc(x)
' Trailing zero suppression:
' the current digit must be displayed if
' 1) it is greater than 0,
if adigit>0 then DISPLAY_ACC_display_a_digit
' 2) it is a 0, but we have already displayed
' some digits, i.e. it is not a trailing 0
if b=0 then DISPLAY_ACC_next_digit
DISPLAY_ACC_display_a_digit
serout LCD_out, n96n, [dec adigit]
debug dec adigit
b=1
DISPLAY_ACC_next_digit
next
' If the accumulator was 0, and we suppress the trailing zeroes,
' then we will have suppressed ALL the zeroes, and nothing
' gets printed. If nothing has still been printed, print
' at least a '0'
if b=1 then DISPLAY_ACC_exit
serout LCD_out, n96n, ["0"]
debug "<0>"
DISPLAY_ACC_exit
debug cr
' before exiting, make the accumulator negative
' again if it was negative
if oldneg=0 then DISPLAY_ACC_no_negate
gosub acc_neg
DISPLAY_ACC_no_negate
return