Integer Math

Technical Notes

Overview
General Philosophy
Naming Conventions
Supported Numeric Types
Floating Point vs. Quad Arithmetic
Fixed-Point Integer Arithmetic
Subtraction
Multiplication
Division
Rounded Division
Comparing Integer Values
Incrementing and Decrementing Integer Values
Type Conversion, Manipulation, and Initialization
Sign Determination
Negation
Taking Absolute Value
Generating Random Numbers
Modulo Arithmetic
Sample Program

Overview

This unit consists of functions which perform all essential quad (64-bit) integer math operations. Random number generation is also included in this unit.

For 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.

General Philosophy

Most C compilers support int (16-bit) and long (32-bit) numeric types but have no support for 64-bit integer math. For this reason, this unit only contains a C interface to the quad (64-bit) integer math functions and is not a complete mapping of the integer math functions available in Spontaneous Assembly 3.0. The complete set of integer math functions is available through inline assembly.

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%.

Naming Conventions

The names of the binary functions in this unit list the operation first (_add_..., _sub_..., _mul_..., _div_..., _divr_..., _cmp_..., or _mov_...). If the function is designed to work on two unsigned values, the next character is "u." The next character is always "q" to indicate that the destination numeric type is a quad, followed by "i", "l", or "q" to designate the source type (int, long int, or quad). For example, the function to add an unsigned int into a uquad (unsigned quad) is _add_uqi.

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.

Supported Numeric Types

The table below shows the numeric types which are supported by the integer math functions.
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-1
Because 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:

typedef signed char quad[8];
typedef unsigned char uquad[8];
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:

           ...
           uquad num1, num2;        /* declare two uquads */
           quad num3, num4;         /* declare two quads */
           ...
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.

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:


           ...
           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) */
           ...
Signed arithmetic:

           ...
           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

_add_uqi
Adds an unsigned int into a uquad.
_add_uql
Adds an unsigned long into a uquad.
_add_uqq
Adds two uquad values.
_add_qi
Adds a int into a quad.
_add_ql
Adds a long into a quad.
_add_qq
Adds two quad values.
These functions add int, long, and quad values into quad destinations. Separate functions are provided for signed and unsigned values. These functions expect both arguments to be either signed or unsigned; no mixing of signed and unsigned values is supported. If overflow occurs, the returned value represents the low-order bits of the result.

Subtraction

_sub_uqi
Subtracts an unsigned int from a uquad.
_sub_uql
Subtracts an unsigned long from a uquad.
_sub_uqq
Subtracts one uquad from another.
_sub_qi
Subtracts a int from a quad.
_sub_ql
Subtracts a long from a quad.
_sub_qq
Subtracts one quad from another.
These functions subtract int, long, and quad, from quad destinations. Separate functions are provided for signed and unsigned values. These functions expect both arguments to be either signed or unsigned; no mixing of signed and unsigned values is supported. If overflow occurs, the returned value represents the low-order bits of the result.

Multiplication

_mul_uqi
Multiplies a uquad by an unsigned int, returning a uquad result.
_mul_uql
Multiplies a uquad by an unsigned long, returning a uquad result.
_mul_uqq
Multiplies one uquad by another, returning a uquad result.
_mul_qi
Multiplies a quad by a int, returning a quad result.
_mul_ql
Multiplies a quad by a long, returning a quad result.
_mul_qq
Multiplies one quad by another, returning a quad result.
These functions multiply quads by int, long, and quad multipliers. Results are always returned in the location of the multiplied quad, so the size of the result never exceeds the size of either of the input values (unlike the results of the MUL and IMUL instructions). These functions expect both arguments to be either signed or unsigned; no mixing of signed and unsigned values is supported.

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.

Division

_div_uqi
Divides a uquad by an unsigned int, returning a uquad result.
_div_uql
Divides a uquad by an unsigned long, returning a uquad result.
_div_uqq
Divides one uquad by another, returning a uquad result.
_div_qi
Divides a quad by a int, returning a quad result.
_div_ql
Divides a quad by a long, returning a quad result.
_div_qq
Divides one quad by another, returning a quad result.
These functions divide quad values by int, long, and quad divisors. The resulting quotient is returned in place of the dividend. The remainder is also returned. These functions expect both arguments to be either signed or unsigned; no mixing of signed and unsigned values is supported.

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.

Rounded Division

_divr_uqi
Divides a uquad by an unsigned int, returning a rounded uquad result.
_divr_uql
Divides a uquad by an unsigned long, returning a rounded uquad result.
_divr_uqq
Divides one uquad by another, returning a rounded uquad result.
_divr_qi
Divides a quad by a int, returning a rounded quad result.
_divr_ql
Divides a quad by a long, returning a rounded quad result.
_divr_qq
Divides one quad by another, returning a rounded quad result.
These functions divide quad values by int, long, and quad divisors. The resulting rounded quotient is returned in place of the dividend. These functions expect both arguments to be either signed or unsigned; no mixing of signed and unsigned values is supported.

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.

Comparing Integer Values

_cmp_uqi
Compares a uquad and an unsigned int.
_cmp_uql
Compares a uquad and an unsigned long.
_cmp_uqq
Compares two uquad valuess.
_cmp_qi
Compares a quad and a int.
_cmp_ql
Compares a quad and a long.
_cmp_qq
Compares two quad values.
These functions compare quad values against int, long, and quad values. The result of the comparison is returned as -1, 0, or 1. These functions expect both arguments to be either signed or unsigned; no mixing of signed and unsigned values is supported.

Incrementing and Decrementing Integer Values

_dec_q
Decrements a quad or uquad.
_inc_q
Increments a quad or uquad.
These functions increment and decrement quads. They may be used with signed or unsigned values. If overflow occurs, the returned value represents the low-order bits of the result.

Type Conversion, Manipulation, and Initialization

_mov_uqi
Moves the value of an unsigned int into a uquad.
_mov_uql
Moves the value of an unsigned long into a uquad.
_mov_uqq
Moves the value of one uquad into another.
_mov_qi
Moves the value of a int into a quad.
_mov_ql
Moves the value of a long into a quad.
_mov_qq
Moves the value of one quad into another.
The _mov_... functions allow int, long, and quad values to be moved into quad destinations. If the destination type is larger, the value is sign extended to the proper size.

Sign Determination

_sgn_q
Determines the sign of a quad.
This function determines the sign of a quad value. It can also be used to check the high bit of uquads. The result of the test is returned as 1 for negative and 0 for positive.

Negation

_neg_q
Negates a quad.
This function negates a quad.

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.

Taking Absolute Value

_abs_q
Converts a quad to its absolute value.
This function converts a quad to its absolute value.

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.

Generating Random Numbers

_rand_init
Initializes the random number generator.
_rand_w
Generates a random number.
_randr_w
Generates a random number within a specific range.
rand_seed
(Variable) Holds the seed value for the random number generator.
These functions generate pseudo-random numbers from 0 to any arbitrary limit less than 65536. A proven, well-tested, multiplicative random number generator with a period of 230 is employed. A function is also provided (_rand_init) to initialize the random number generator using the system time. This allows different sequences of random numbers to be generated automatically. Access is provided to the generator's seed value (rand_seed) to allow exact sequences of pseudo-random numbers to be easily reproduced.

Modulo Arithmetic

The MOD operator is not directly supported dor quad or uquad values in the Spontaneous Assembly library. Instead, modulo arithmetic is performed by dividing and using the remainder instead of the quotient. This is shown in the following example:

           ...
           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 */
           ...

Sample Program

The sample program shown below uses several integer math functions and demonstrates calculating input/output field size from radix and calculating quad values entered by the user. Several console I/O functions are used to manipulate the position of the displayed calculations and to manipulate the cursor during input. Also, the use of several data conversion functions and program control functions are presented in this example. The program is listed in its entirety.

#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