/*
 * mf_arith.c -- optimized arithmetic routines for UNIX METAFONT 84
 *
 *	These functions account for a significant percentage of the processing
 *	time used by METAFONT, and have been recoded in assembly language for
 *	some classes of machines.
 *
 *      As yet I don't have code for SPARC, so I'm just using a C version
 *      by richards, in hopes that cc does better than pc on the brute-force
 *      algorithm found in mf.web.
 *
 *	function makefraction(p, q: integer): fraction;
 *		{ Calculate the function floor( (p * 2^28) / q + 0.5 )	}
 *		{ if both p and q are positive.  If not, then the value	}
 *		{ of makefraction is calculated as though both *were*	}
 *		{ positive, then the result sign adjusted.		}
 *		{ (e.g. makefraction ALWAYS rounds away from zero)	}
 *		{ In case of an overflow, return the largest possible	}
 *		{ value (2^32-1) with the correct sign, and set global	}
 *		{ variable "aritherror" to 1.  Note that -2^32 is 	}
 *		{ considered to be an illegal product for this type of	}
 *		{ arithmetic!						}
 *
 *	function takefraction(f: fraction; q: integer): integer;
 *		{ Calculate the function floor( (p * q) / 2^28 + 0.5 )	}
 *		{ takefraction() rounds in a similar manner as		}
 *		{   makefraction() above.				}
 *
 */

#include "h00vars.h"

extern	bool	aritherror;		/* to be set on overflow */

#define	EL_GORDO	0x7fffffff	/* 2^31-1		*/
#define	FRACTION_ONE	0x10000000	/* 2^28			*/
#define	FRACTION_HALF	0x08000000	/* 2^27			*/
#define	FRACTION_FOUR	0x40000000	/* 2^30			*/

int makefraction(p, q)
    register	int p, q;
{
    int		negative;
    register	int be_careful;
    register	int f;
    int		n;

    if (p < 0) {
	negative = 1;
	p = -p;
    } else negative = 0;

    if (q < 0) {
	negative = !negative;
	q = -q;
    }

    n = p / q;
    p = p % q;

    if (n >= 8) {
	aritherror = TRUE;
	return (negative? -EL_GORDO : EL_GORDO);
    }

    n = (n-1)*FRACTION_ONE;
    f = 1;
    do {
	be_careful = p - q;
	p = be_careful + p;
	if (p >= 0) {
	    f = f + f + 1;
	} else {
	    f <<= 1;
	    p += q;
	}
    } while (f < FRACTION_ONE);

    be_careful = p - q;
    if ((be_careful + p) >= 0)
	f += 1;
    return (negative? -(f+n) : (f+n));

}

int takefraction(q, f)
    register	int q;
    register	int f;
{
    int		n, negative;
    register	int p, be_careful;

    if (f < 0) {
	negative = 1;
	f = -f;
    } else negative = 0;
    if (q < 0) {
	negative = !negative;
	q = -q;
    }
    if (f < FRACTION_ONE)
	n = 0;
    else {
	n = f / FRACTION_ONE;
	f = f % FRACTION_ONE;
    	if (q < (EL_GORDO /  n))
	    n = n * q;
	else {
	    aritherror = TRUE;
	    n = EL_GORDO;
	}
    }

    f += FRACTION_ONE;
    p = FRACTION_HALF;
    if (q < FRACTION_FOUR) 
	do {
	    if (f & 0x1)
		p = (p+q) >> 1;
	    else
		p >>= 1;
	    f >>= 1;
	} while (f > 1);
    else
	do {
	    if (f & 0x1)
		p = p + ((q-p) >> 1);
	    else
		p >>= 1;
	    f >>= 1;
	} while (f > 1);

    be_careful = n - EL_GORDO;
    if ((be_careful + p) > 0) {
	aritherror = TRUE;
	n = EL_GORDO - p;
    }

    return(negative? -(n+p) : (n+p));
}