Technical Notes
OverviewFor C/C++ applications, use of the supplied quad class is recommended instead of the functions in this unit. See the Quad Class For C/C++ technical notes for further information.
In keeping with C philosophy, the integer math functions do not necessarily check for or return indicators of overflow. If overflow occurs, the returned values represent the low-order bits of the result (unless otherwise documented).
Since math operations are usually speed critical, the functions in this unit are designed with more emphasis on speed and less emphasis on size than the other library functions. Wherever it is possible to obtain a reasonable speed increase, a moderate amount of code is added. For example, _div_ql includes an early-out algorithm (roughly a 30% size increase) which reduces the average execution time by 45%.
The names of the unary functions in this unit also list the operation first, (_dec_..., _inc_..., _sgn_..., _neg_..., or _abs_...) followed by one letter to indicate the type of the operator.
Numeric Type Size Range unsigned char (inline only) 8-bit integer 0 to 255 char (inline only) 8-bit integer -128 to 127 unsigned int 16-bit integer 0 to 65,535 int 16-bit integer -32,768 to 32,767 unsigned long 32-bit integer 0 to 4,294,967,295 long 32-bit integer -2,147,483,648 to 2,147,483,647 uquad 64-bit integer 0 to 264-1 quad 64-bit integer -263 to 263-1Because C does not supply defined types for 64-bit integers, a uquad unsigned 64-bit type and a quad 64-bit type are provided to simplify the use of 64-bit integers. When compiling a C program, the quad and uquad types are defined using typedef, as follows:When using C++, quad and uquad are defined as classes to allow mathematical operators to be used with quad and uquad values. (See the Quad Class For C++ technical notes for more information.) 64-bit integers may be declared by using uquad for unsigned values or quad for signed values, as in the following example:typedef signed char quad[8]; typedef unsigned char uquad[8];
An extensive set of routines are provided for mixed-type operations (quad-quad, quad-long, and quad-int). The programmer should choose carefully from among these routines to avoid proliferation of code.... uquad num1, num2; /* declare two uquads */ quad num3, num4; /* declare two quads */ ...
Signed vs. Unsigned Arithmetic
The distinction between signed and unsigned values must first be made by the programmer and then enforced as the code is written. This decision is based on the range of values supported by each type of integer (see the Supported Numeric Types table, above). For example, if 64-bit arithmetic is to be performed and values greater than 263-1 are required, then uquad should be used. If values less than zero are required, then quad arithmetic must be used instead. Note that the ranges for signed and unsigned values overlap (i.e., quad values from 0 to 263-1 may be considered either signed or unsigned). If a given operation only needs to support values within this range, either type of operation may be performed. Unsigned arithmetic is preferable, however, since it is usually faster and tighter than signed arithmetic. Examples of both types of arithmentic are shown below.
Unsigned arithmetic:
Signed arithmetic:... uquad num; /* initialize a uquad */ _mov_uql(num, 0xFFFFFFFF); /* num = 0xFFFFFFFF */ _mul_uqi(num, 2); /* num = num x 2 */ _divr_uqi(num, 2); /* num = num/2 (rounded) */ ...
... quad num; /* initialize a quad */ _mov_ql(num, 0x7FFFFFFF); /* num = 0x7FFFFFFF */ _mul_qi(num, 2); /* num = num x 2 */ _divr_qi(num, 2); /* num = num/2 */ ...
Floating Point vs. Quad Arithmetic
quad operations provide a fast, tight, exact alternative to floating point operations whenever transcendental functions are not required and the values of interest lie in the range 0 to 264-1 (0 to 1.84E19) or -263 to 263-1 (-9.22E18 to 9.22E18).The code size difference between quad functions and floating point functions is striking. If a program is compiled with floating-point emulation support, the .EXE size increases by 10K to 20K. By contrast, all of the Spontaneous Assembly integer math functions and their C binders occupy less than 3.2K, including every variation of every function (i.e., functions which perform mixed-type operations). The actual memory usage for quad functions is, however, usually less than 800 bytes since only the required Spontaneous Assembly functions are linked into the final program and a limited number of functions is required for most applications.
Fixed-Point Integer Arithmetic
Fixed-point integers are true integer values with an implied number of decimal digits to the right of an implied decimal point. The precision of a fixed-point number specifies the fixed number of digits to the right of the decimal point. Restated, a fixed-point integer (fixint) has an implied exponent of þprecision or a value of fixintþ10þprecision.For example, a fixed-point integer value of 54,321 with a precision of 3 represents 54321e-3 or 54.321. A fixed-point integer value of 10,025 in U.S. monetary units (precision of 2), represents 10,025 cents or $100.25.
The precision of a fixed-point value is only specified when the _dec_to_fixp or _fixp_to_dec data conversion functions are called. In all other respects, fixed-point integers are treated as integer values. Because fixed-point integers are manipulated using standard integer math routines, they provide a fast, tight alternative to floating-point values.
WARNING! It is the responsibility of the programmer to keep track of the precision of each fixed-point value. When fixed-point values are multiplied, their precisions must be added to determine the precision of the result. When fixed-point values are divided, the precision of the divisor must be subtracted from the precision of the dividend. Negative precisions are not supported. Standard integer values have a precision of 0.
Addition
If overflow occurs when multiplying unsigned values, the returned value represents the low-order bits of the result. If overflow occurs when multiplying signed values, the result is undefined.
For signed operations, the sign of the remainder is the same as the sign of the dividend, and the sign of the quotient is the product of the signs of the dividend and divisor.
If overflow occurs, the results are undefined. However, no divide overflow interrupts take place. Overflow is only possible if the divisor is zero.
For signed operations, the sign of the rounded quotient is the product of the signs of the dividend and divisor.
If overflow occurs, the results are undefined. However, no divide overflow interrupts take place. Overflow is only possible if the divisor is zero or, in the case of signed operations, if the quotient is the maximum possible negative number before rounding takes place.
Overflow occurs if the input value is the maximum possible negative number. In this case, the value remains unchanged. No other indicator of overflow is returned.
Overflow occurs if the input value is the maximum possible negative number. In this case, the value remains unchanged. No other indicator of overflow is returned.
...
uquad num;
unsigned long remainder, lmod;
_mov_uql(num, 0xFFFFFFFF); /* num = value not divisible by 2 */
_div_uql(num, 2, &remainder); /* num = num/2, remainder is modified */
lmod = remainder; /* lmod = remainder value */
...
#include /* C headers */
#include
#include /* SA headers */
#include
#include
#include
#include
#define BS 0x08
#define ESC 0x1B
/* functions */
char field_len(int); /* determine field size of input/output */
void help_msg(void); /* display syntax */
void input(int); /* get number and operator */
int display(char *, int); /* display converted number */
char * calculate(void); /* calculate quad values */
/* data */
static char strbuf[80]; /* buffer for input/output */
static char curr_op, next_op, equal_op, op;
static int bang, pass, width, radix;
uquad num1, num2; /* declare uquads */
void main()
{
char *strindx, ch;
_console_init(); /* initialize console i/o */
help_msg(); /* display command line and instructions */
if (_arg_count() == 0) /* if no arguments, default to decimal */
radix = 10;
else /* else check if actual radix */
{
radix = _dec_to_c(_arg_next(strbuf, &ch), &strindx);
if (radix 2 || radix 36) /* valid radix? */
{
_cput_str("Invalid radix, use radix in range 2 to 36.");
exit(0); /* n: exit */
}
}
width = field_len(radix); /* determine field size for input/output */
_asc_to_uq("0", radix, num1, &strindx); /* num1 = 0 */
pass = 0; /* clear pass flag */
for (;;) /* run until turned off (ESC) */
{
while (pass != -1) /* run until CLEAR */
{
do { /* get numbers and display */
input(width); /* get user input */
if (bang == 1) break; /* CLEAR? */
pass = display(strbuf, pass);
} while (pass != -1); /* display right justified number */
if (bang == 1) break; /* CLEAR? */
_cput_chr('=');
_cput_strji(calculate(), width+2);
_cput_newline(); /* display right justified result */
_cput_chr(curr_op); /* put next operator */
pass = 1; /* reset num1 */
}
_clr_line(); _goto_left();
_cput_str("CLEAR\n\r"); /* display "CLEAR" msg */
pass = 0; bang = 0; /* reset num1 */
}
}
}
/*============================================================================
FUNC: CALCULATE
DESC: Calculates new quad value using user defined operator.
============================================================================*/
char * calculate(void)
{
switch(curr_op)
{
case '*': _uq_to_asc(_mul_uqq(num1,num2), radix, strbuf); break;
case '/': _uq_to_asc(_divr_uqq(num1,num2), radix, strbuf); break;
case '+': _uq_to_asc(_add_uqq(num1,num2), radix, strbuf); break;
case '-': _uq_to_asc(_sub_uqq(num1,num2), radix, strbuf); break;
default: break;
}
curr_op = next_op;
return(strbuf);
}
/*============================================================================
FUNC: DISPLAY
DESC: Converts user input numbers to quad values, converts quad to ASCII
string, right justifies display of string. Uses flag to determine
which number to convert, returns (-1) both values are ready for
calculation, (0) if num1 needs value and conversion, or (1) if num2
needs value and conversion.
============================================================================*/
int display(char * strbuf, int flag)
{
int status;
char *sptr;
_clr_eol();
if (flag == 1)
{
_asc_to_uq(strbuf, radix, num2, &sptr);
_clr_line(); /* clear current line */
_goto_left(); /* cursor at left-most column */
_cput_chr(curr_op); /* display current operator */
_cput_strji(_uq_to_asc(num2, radix, strbuf), width+2); _cput_newline();
if (equal_op)
{
_goto_x(3); _cput_nchr('',width); _cput_newline();
}
return(-1);
}
else
{
_asc_to_uq(strbuf, radix, num1, &sptr);
_clr_line(); /* clear current line */
_goto_left(); /* cursor at left-most column */
_cput_strji(_uq_to_asc(num1, radix, strbuf), width+3); _cput_newline();
_cput_chr(curr_op); /* display current operator */
if (equal_op)
{
_goto_x(3); _cput_nchr('',width); _cput_newline();
}
return(1);
}
}
/*============================================================================
FUNC: FIELD_LEN
DESC: Determine field size based on radix value.
============================================================================*/
char field_len(int i)
{
char array[35] = {64,42,32,27,24,22,21,20,19,18,17,17,16,16,16,15,15,15,14,\
14,14,14,13,13,13,13,13,13,13,12,12,12,12,12,12};
i -= 2;
return(array[i]);
}
/*============================================================================
FUNC: HELP_MSG
DESC: Determine field size based on radix value.
============================================================================*/
void help_msg(void)
{
_cput_str("\n\r\n\rCALC - Calculator using integer quad arithmetic");
_cput_str("\n\rSyntax: calc [base]");
_cput_str("\n\rWhere: base is the radix (2 to 36), default is 10");
_cput_str("\n\n\r Enter '!' to clear, ESC to turn off\n\n\r");
}
/*============================================================================
FUNC: INPUT
DESC: Get a character at a time, switch on each character, add numbers
to a string and break on operators.
============================================================================*/
void input(int num_cnt)
{
char num;
int flag = pass;
int i = 0;
int op_flag=0;
_str_set(strbuf,0); /* clear buffer */
do
{
while(num_cnt != 0)
{
num = _cget_chre(); /* get user number/operator */
switch(num)
{
case BS: if (_where_x() 1) /* handle BACKSPACE */
{ i--; /* dec counter for string buffer */
num_cnt++; /* counter backed up 1 */
strbuf[i] = 0; /* NULL out previous number */
_move_left(); /* move cursor left 1 space */
_cput_chr(' '); /* erase number from scr */
_move_left();
} break;
case ESC: exit(0); /* turn off */
case '!': bang = 1; num_cnt = 0; op_flag = 1; break;
case 13: /* (CR or =) preserves current operator */
case '=': num_cnt = 0; op_flag = 1; equal_op = 1; next_op = curr_op; break;
case '+':
case '-':
case '*':
case '/': if (flag == 0) /* handle 1 of 4 operators */
{
curr_op = num; /* set current operator */
flag = 1;
}
if (_where_x() == 2)
{
curr_op = num;
_move_left(); /* move cursor left 1 space */
_move_left(); /* ditto */
_cput_chr(num); /* erase current operator */
_cput_chr(' '); /* clear 2 character */
_move_left();
}
else
next_op = num; /* save for next operator */
if (i == 0) /* if number already entered, done */
{
num_cnt = 0; op_flag = 1; }
equal_op = 0;
}
break;
default: if (num 123 && num 47)
{
strbuf[i] = num; /* add number to string */
num_cnt--; /* decrement space left in buffer */
i++; /* increment numbers entered */
}
break;
} /* end switch 1 */
};
while (op_flag == 0) /* if no operator entered yet */
{
num = _cget_chr();
switch(num)
{
case '+':
case '-':
case '*':
case '/': if (pass == 0) /* if pass one */
curr_op = num; /* this is current operator */
else /* else it is the next operator */
next_op = num;
op_flag = 1;
equal_op = 0;
break;
case '=':
case 13: equal_op = 1; next_op = curr_op; op_flag = 1; break;
case BS: op_flag = 1; break;
case ESC: exit(0);
default: _cflush_keys(); _cput_beep(); break;
}
}
if(num == BS)
{
num_cnt++; i--; op_flag = 0;
strbuf[i] = 0; /* NULL out previous number */
_move_left(); /* move cursor left 1 space */
_cput_chr(' '); /* erase number from scr */
_move_left();
}
} while (num_cnt != 0);
}
The sample program shown above (CALC.C) is provided on the distribution diskettes and may be compiled and linked using the following Microsoft C and Borland C command lines:
Microsoft C:
cl /c /I\msc\include /I\sa\include calc.c
link calc,calc,,\sa\lib\_sas \msc\lib\slibce
Borland C:
bcc /c /ms /I\bc\include /I\sa\include calc.c
tlink \bc\lib\c0s calc,calc,,\sa\lib\_sas \bc\lib\cs