/*
 * Electric(tm) VLSI Design System
 *
 * File: dbmult.c
 * Extended precision multiplication and division
 * Written by: Mark Brinsmead at the University of Calgary
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "global.h"
#include "database.h"

/* prototypes for local routines */
void db_checkfloat(void);
INTBIG db_intmult(INTBIG a, INTBIG b);
INTBIG db_floatmult(INTBIG a, INTBIG b);
INTBIG db_intmuldiv(INTBIG ai, INTBIG bi, INTBIG ci);
INTBIG db_floatmuldiv(INTBIG ai, INTBIG bi, INTBIG ci);

enum {USEFLOAT = 1, USEINTEGER = 2};

#if 0		/* always use floating point: it is more accurate and usually faster */
  INTSML db_muldivstyle = 0;
  INTSML db_multstyle   = 0;
#else
  INTSML db_muldivstyle = USEFLOAT;
  INTSML db_multstyle   = USEFLOAT;
#endif

#define ABS(Mi) (Mi<0 ? -Mi : Mi)
#define HI(Mi) (Mi>>16)
#define LO(Mi) (Mi&0xFFFF)
#define LOHI(Mi) (Mi<<16)

/****************************** MULT ******************************/

/*
 * mult(a, b) returns (a*b)>>30, as a 32 bit result, where a, b are 32 bit
 * integers.
 */
INTBIG mult(INTBIG a,INTBIG b)
{
	/* if it hasn't been done yet, see which way is faster */
	if (db_multstyle == 0) db_checkfloat();

	/* do the appropriate version */
	if (db_multstyle == USEFLOAT) return(db_floatmult(a, b));
	return(db_intmult(a, b));
}

/*
 * the floating-point version of "mult()".
 */
INTBIG db_floatmult(INTBIG a, INTBIG b)
{
	return(INTBIG)((double)a * (double)b / 1073741824.0);
}

/*
 * the integer version of "mult()".
 */
INTBIG db_intmult(INTBIG a, INTBIG b)
{
	INTBIG temp1, temp2, temp3, temp4, low_a, hi_a, low_b, hi_b;
	INTSML sign;

	sign = ((a>0) == (b>0)) ? 1 : -1;
	a = ABS(a);    b = ABS(b);

	low_a = a & 0xFFFF;				/** grab low 16 bits of a **/
	low_b = b & 0xFFFF;				/** grab low 16 bits of b **/
	hi_a  = (a>>16) & 0xFFFF;		/** grab hi 16 bits of a **/
	hi_b  = (b>>16) & 0xFFFF;		/** grab hi 16 bits of b **/

	temp1 = low_a * low_b;
	temp2 = low_a * hi_b;
	temp3 = low_b * hi_a;
	temp4 = hi_a * hi_b;

	return(((temp1>>30)+((temp2+temp3)>>14)+(temp4<<2))*sign);
}

/****************************** MULDIV ******************************/

/*
 * return a*b/c, where a, b, c are 32 bit integers
 */
INTBIG muldiv(INTBIG ai, INTBIG bi, INTBIG ci)
{

	/* if it hasn't been done yet, see which way is faster */
	if (db_muldivstyle == 0) db_checkfloat();

	/* do the appropriate version */
	if (db_muldivstyle == USEFLOAT) return(db_floatmuldiv(ai, bi, ci));
	return(db_intmuldiv(ai, bi, ci));
}

/*
 * the floating-point version of "muldiv()".
 */
INTBIG db_floatmuldiv(INTBIG ai, INTBIG bi, INTBIG ci)
{
	return(INTBIG)((double)ai*(double)bi/(double)ci);
}

/*
 * the integer version of "muldiv()".
 */
INTBIG db_intmuldiv(INTBIG ai, INTBIG bi, INTBIG ci)
{
	UINTBIG ah, bh, al, bl, c;
	INTSML neg;
	REGISTER UINTBIG msb, lsb, bit;
	REGISTER INTBIG result;

	/* determine the sign of the result */
	if (ai == 0 || bi == 0) return 0;
	if (ai < 0) { neg = 1;  al = -ai; }
	else { neg = 0;   al = ai; }
	if (bi < 0) { neg = neg ^ 1; bl = -bi; }
	else bl = bi;
	if (ci < 0) { neg = neg ^ 1; c = -ci; }
	else c = ci;

	/* create the product a*b in polynomial form */
	/* registers to hold base 2**16 digits */
	ah = HI(al);   bh = HI(bl);
	al = LO(al);   bl = LO(bl);

	/* create the polynomial ah*bh*base**2 + (al*bh+ah*bl)base + al*bl */
	lsb = al * bl;
	msb = (al * bh) + (ah * bl) + HI(lsb);
	lsb = LO(lsb) | LOHI(msb);
	msb = HI(msb) + (ah * bh);

	if (msb > c)
	{
	   /* this is an overflow condition, exit */
	   ttyputerr(_("error: dbmuldiv integer overflow on %ld*%ld/%ld\n"), ai, bi, ci);
	   return 0;
	}

	/* now generate the result a*b/c */
	/* use a subtraction method for calculating the result */
	/* first simplify the most significant bits */
	/* shift bits and subtract */
	if (msb != 0)
	{
		result = 0;
		/* shift in the lsb, and subtract */
		bit = 0x80000000;
		do
		{
			result = result << 1;
			msb = msb << 1;
			if (lsb & bit) msb++;
			if (msb >= c)
			{
				msb -= c;
				result++;
			}
		} while ((bit = bit>>1));
		if (c - msb < msb) result++;
	} else
	{
		result = lsb / c;
		lsb = lsb % c;
		if (c - lsb < lsb) result++;
	}

	/* return the result with the correct sign */
	return(neg ? -result : result);
}

/****************************** FIGURING OUT WHICH IS BETTER ******************************/

#define INTERATIONS 1000000
#define A 100000
#define B 100000
#define C 100000

void db_checkfloat(void)
{
	float Fmuldiv, Imuldiv, Fmult, Imult;
	INTBIG i;

	/* time floating point version of "muldiv" */
	db_muldivstyle = USEFLOAT;
	starttimer();
	for(i=0; i<INTERATIONS; i++) muldiv(A, B, C);
	Fmuldiv = endtimer();

	/* time integer version of "muldiv" */
	db_muldivstyle = USEINTEGER;
	starttimer();
	for(i=0; i<INTERATIONS; i++) muldiv(A, B, C);
	Imuldiv = endtimer();

	/* time floating point version of "mult" */
	db_multstyle = USEFLOAT;
	starttimer();
	for(i=0; i<INTERATIONS; i++) mult(A, B);
	Fmult = endtimer();

	/* time integer version of "mult" */
	db_multstyle = USEINTEGER;
	starttimer();
	for(i=0; i<INTERATIONS; i++) mult(A, B);
	Imult = endtimer();

	/* figure out which versions are better */
	if (Imuldiv > Fmuldiv) db_muldivstyle = USEFLOAT; else
		db_muldivstyle = USEINTEGER;
	if (Imult > Fmult) db_multstyle = USEFLOAT; else
		db_multstyle = USEINTEGER;
}
