[Toybox] Long division algorithm for the toybox project by Graff
CM Graff
cm0graff at gmail.com
Fri Oct 27 00:51:32 PDT 2017
// Fixed-point arbitrary precision long hand division algorithm written by
// Christopher M. Graff for Rob Landley's `toybox' project.
//
// A commonly known multiplication algorithm is included to help test the
// effectiveness of the fixed-point system.
//
// The division algorithm needs a large hardware type such as int in order to
// hold carried values. The multiplication algorithm uses "partial carries"
// so it will work with extremely small types such as char. As far as I know,
// no "partial carry" system has been invented for long division.
#include <stdlib.h>
#include <stdio.h>
typedef struct { // Toym fxdpnt type
int *number; // The actual number
int sign; // Sign
size_t fp; // Length left of radix
size_t len; // Length of number (count of digits / limbs)
size_t allocated; // Length of allocated memory
size_t chunk; // Allocation chunk size
} fxdpnt;
fxdpnt *toym_div(fxdpnt *, fxdpnt *, fxdpnt *, int base);
fxdpnt *toym_mul(fxdpnt *, fxdpnt *, fxdpnt *, int base);
fxdpnt *toym_expand(fxdpnt *, size_t);
fxdpnt *toym_str2fxdpnt(const char *);
fxdpnt *toym_add_precision(fxdpnt *, size_t);
fxdpnt *toym_alloc(size_t);
void *toym_malloc(size_t);
void *toym_realloc(void *, size_t);
void toym_flipsign(fxdpnt *);
void toym_setsign(fxdpnt *, fxdpnt *, fxdpnt *);
void toym_init(fxdpnt *);
void toym_print(fxdpnt *);
void toym_free(fxdpnt *);
void toym_error(char *);
void toym_copyarr(int *, int *, size_t);
void toym_setarr(int *, int, size_t);
int toym_highbase(int);
// helper functions that could be replaced or hard coded
int toym_highbase(int a)
{
// Handle high bases
static int glyphs[65] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '+',
'/', '='};
if ( a <= 64 )
return glyphs[a];
else // just use the ascii values for bases that are very high
return a;
}
fxdpnt *toym_alloc(size_t len)
{
// Allocate the basic requirements of a toym `fxdpnt'
fxdpnt *ret = toym_malloc(sizeof(fxdpnt));
ret->number = toym_malloc(sizeof(int) * len);
ret->sign = '+';
ret->fp = 0;
ret->allocated = len;
ret->len = 0;
ret->chunk = 4; // set to 4 to force worst case tests, change to >255
return ret;
}
void toym_init(fxdpnt *flt)
{
flt->sign = '+';
flt->len = flt->fp = 0;
}
void toym_flipsign(fxdpnt *flt)
{
if (flt->sign == '+')
flt->sign = '-';
else if (flt->sign == '-')
flt->sign = '+';
}
void toym_print(fxdpnt *flt)
{
size_t i = 0;
if (flt->sign == '-')
putchar(flt->sign);
for (i = 0; i < flt->len ; ++i){
if (flt->fp == i)
putchar('.');
putchar(toym_highbase((flt->number[i])));
}
putchar('\n');
fflush(stdout);
}
fxdpnt *toym_str2fxdpnt(const char *str)
{
// Convert a string to a toym `fxdpnt'
size_t i = 0;
int flt_set = 0, sign_set = 0;
fxdpnt *ret = toym_expand(NULL, 1);
for (i = 0; str[i] != '\0'; ++i){
if (str[i] == '.'){
flt_set = 1;
ret->fp = i - sign_set;
}
else if (str[i] == '+'){
sign_set = 1;
ret->sign = '+';
}
else if (str[i] == '-'){
sign_set = 1;
ret->sign = '-';
}
else{
ret = toym_expand(ret, ret->len + 1);
ret->number[ret->len++] = str[i] - '0';
}
}
if (flt_set == 0)
ret->fp = ret->len;
return ret;
}
void toym_free(fxdpnt *flt)
{
if (flt->number)
free(flt->number);
free(flt);
}
fxdpnt *toym_expand(fxdpnt *flt, size_t request)
{
// Enlarge or create a fxdpnt
if (flt == NULL){
flt = toym_alloc(request);
} else if (request > flt->allocated){
flt->allocated = (request + flt->chunk);
flt->number = toym_realloc(flt->number, flt->allocated * sizeof(int));
}
return flt;
}
fxdpnt *toym_add_precision(fxdpnt *flt, size_t more)
{
// Increase the precision of a toym `fxdpnt'
flt = toym_expand(flt, flt->len + more);
toym_setarr(flt->number + flt->len, 0, more);
flt->len += more;
return flt;
}
void toym_error(char *message)
{
fprintf(stderr, "%s\n", message);
exit(1);
}
void *toym_malloc(size_t len)
{
void *ret;
if(!(ret = malloc(len)))
toym_error("malloc failed\n");
return ret;
}
void *toym_realloc(void *ptr, size_t len)
{
void *ret;
if(!(ret = realloc(ptr, len)))
toym_error("realloc failed\n");
return ret;
}
void toym_copyarr(int *answer, int *from, size_t len)
{ // this could be replaced with memcpy
size_t i = 0;
for( i = 0; i < len ; i++)
answer[i] = from[i];
}
void toym_setarr(int *answer, int delim, size_t len)
{ // this could be replaced with memset
size_t i = 0;
for( i = 0; i < len; i++)
answer[i] = delim;
}
void toym_setsign(fxdpnt *a, fxdpnt *b, fxdpnt *c)
{
toym_init(c);
if (a->sign == '-')
toym_flipsign(c);
if (b->sign == '-')
toym_flipsign(c);
}
fxdpnt *toym_mul(fxdpnt *a, fxdpnt *b, fxdpnt *c, int base)
{
int i = 0, j = 0, sum = 0, carry = 0;
size_t k = 0;
toym_setsign(a, b, c);
c = toym_expand(c, a->len + b->len);
toym_setarr(c->number, 0, a->len + b->len);
for ( i = a->len - 1; i >= 0 ; i--){
for ( j = b->len - 1, k = i + j + 1, carry = 0; j >= 0 ; j--, k--){
sum = (a->number[i]) * (b->number[j]) + (c->number[k]) + carry;
carry = sum / base;
c->number[k] = (sum % base);
}
c->number[k] += carry;
c->len++;
}
c->fp = a->fp + b->fp;
return c;
}
fxdpnt *toym_div(fxdpnt *a, fxdpnt *b, fxdpnt *c, int base)
{
size_t i = 0, j = 0, z = 0, diff = 0, off = 0;
size_t width = a->len + b->len;
int sum = 0, rec = 0;
// temporary variables for division "guesses"
int *mir = toym_malloc(sizeof(int) * width);
int *tmir = toym_malloc(sizeof(int) * width);
toym_setsign(a, b, c);
c = toym_expand(c, a->len + b->len);
toym_setarr(mir + a->len, 0, width - a->len);
toym_copyarr(mir, a->number, a->len);
c->number[z] = 0;
// count the zeros before a non-zero value
while (b->number[off] == 0 && off < b->len)
++off;
// precompute the position of the radix for "c"
if (a->fp < b->fp){
diff = b->fp - a->fp - 1;
toym_setarr(c->number, 0, diff);
c->len = z = diff;
c->fp = off;
c->number[z] = 0;
}
else if (a->fp + 1> b->fp)
c->fp = a->fp - b->fp + off + 1;
else
c->fp = a->fp + off;
// this is the actual long division routine which operates on two arrays
for (;z < a->len + diff;){
for (rec = 0, i = off, j = z - diff;i < b->len;j++, i++){
sum = (mir[j]) - (b->number[i]);
if (sum < 0){
if (j == z - diff){
mir[j + 1] += ((mir[j]) * base);
z++;
c->len++;
c->number[z] = 0;
}
else{
mir[j - 1] -= 1;
mir[j] += base;
}
rec = 1;
break;
}
tmir[j] = sum;
}
if (rec == 0){
toym_copyarr(mir, tmir, j);
c->number[z] += 1;
}
}
free(mir);
free(tmir);
return c;
}
int main(int argc, char *argv[])
{
if (argc < 3 )
toym_error("Needs 2 numbers");
size_t scale = 100;
int base = 10;
fxdpnt *a, *b, *c;
a = toym_str2fxdpnt(argv[1]);
b = toym_str2fxdpnt(argv[2]);
a = toym_add_precision(a, scale);
b = toym_add_precision(b, scale);
c = toym_expand(NULL, 1);
// multiplication
c = toym_mul(a, b, c, base);
toym_print(c);
// division
c = toym_div(a, b, c, base);
toym_print(c);
toym_free(a);
toym_free(b);
toym_free(c);
return 0;
}
More information about the Toybox
mailing list