CALC05 - BCD math for the Basic Stamp 2


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

' *************

' *  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

' 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


' 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

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