/* Integer Version 2.0, RD, 20.1.93	idigitkara.c	*/
/*	use DigitVecMult, RD, 24.2.93			*/

/*	Karatsuba method for multiplication of long integers.	*/
/*	Parameters optimized for Sparc Version.		*/


#include "iint.h"
#include "idigit.h"

/*#ifndef KARATSUBA_LIMIT2
#define KARATSUBA_LIMIT2 15
#endif
*/
#define KARATSUBA_LIMIT2 16


/* Formulae:
    a == B^n a1 + a0;
    b == B^n b1 + b0;
    a b == (B^(2n) + B^n) a1 b1 + B^n (a1 - a0) (b0 - b1) + (B^n + 1) a0 b0;

	-----------------
V	|     a1 b1 	|
	-----------------
		-----------------
IV		|     a1 b1	|
		-----------------
		-----------------
III		||a1-a0||b0-b1|	|
		-----------------
		-----------------
II		|     a0 b0	|
		-----------------
			-----------------
I			|     a0 b0	|
			-----------------
	---------------------------------
	|	    prod		|
	---------------------------------

		|	|	|	|
		n3	n2	n	0

*/


static  DigitType vecadd4carry (/*sum, a, b, c, d, carry, k*/);
static  int vecadd3subcarry (/*accu, a, b, c, d, carry, k*/);
static  DigitType vecaddDigitType (/*accu, a, d, k*/);
static  DigitType vecaddscarry (/*accu, a, d, k*/);

void DigitVecKaratsubaM (prod, a, b, tmp, n2)
	DigitType *prod, *a, *b, *tmp;
	int n2;

/* 	In:	a[n2], b[n2];	n2 == 2^k * n0, with n0 <= KARATSUBA_LIMIT2
	Out:	prod[2*n2] = a[n2] * b[n2];
	Local:	tmp[2*n2] for intermediate results
	for n2>KARATSUBA_LIMIT2:	karatsuba-recursion with k-1
	else:		standard multiplication
*/
{	int sign, i, n, n3;
	DigitType carry;
	int subcarry;

	if (n2<=KARATSUBA_LIMIT2) {
	    /* use DigitVecMult and DigitVecMultAdd */
	    /* clearly n2 >= 1 */
	    prod[n2]=DigitVecMult(prod, a, b[0], n2);
            for ( i = 1; i < n2; i++) 
                prod[i+n2] = DigitVecMultAdd(&prod[i], a, b[i], n2);
	    return;
	}

	n=n2/2;
	n3=n2+n;

	DigitVecKaratsubaM(&prod[n2], a, b, prod, n);
	/* prod:	|    a0*b0	|		|	*/

	if (DigitVecGt(a, &a[n], n)) {
		sign=1;
		DigitVecSub(tmp, a, &a[n], n);	/* a0 - a1 */
	} else {
		sign=0;
		DigitVecSub(tmp, &a[n], a, n);	/* a1 - a0 */
	}
	/* tmp:		|	|	|	||a0-a1||	*/
	if (DigitVecGt(&b[n], b, n)) {
		sign^=1;
		DigitVecSub(&tmp[n], &b[n], b, n);	/* b1 - b0 */
	} else {
		DigitVecSub(&tmp[n], b, &b[n], n);	/* b0 - b1 */
	}
	/* tmp:		|	|	||b1-b0|||a0-a1||	*/


	DigitVecKaratsubaM(&tmp[n2], tmp, &tmp[n], prod, n);
	/* tmp:		||b1-b0|*|a0-a1|||b1-b0|||a0-a1||	*/

	DigitVecKaratsubaM(tmp, &a[n], &b[n], prod, n);
	/* tmp:		||b1-b0|*|a0-a1||    a1*b1	|	*/

	/*
			---------------------------------
	prod		|     a0 b0	|		|
			---------------------------------

			---------------------------------
	tmp		||a1-a0||b0-b1|	|     a1 b1	|
			---------------------------------
	*/

	if (sign==0) {		/* Einfach addieren */
	    for (i=0; i<n; i++)
		prod[i]=prod[n2+i];			/* I */
	    /* I + II + III + IV */
	    carry=vecadd4carry(&prod[n], 
			&prod[n3], &prod[n2], &tmp[n2], tmp, 0, n);
	    /* carry + II + III + IV + V */
	    carry=vecadd4carry(&prod[n2], 
			&prod[n3], &tmp[n3], &tmp[n], tmp, carry, n);
	    /* carry + V */
	    vecaddDigitType(&prod[n3], &tmp[n], carry, n);
	} else {		/* III subtrahieren */
	    for (i=0; i<n; i++)
		prod[i]=prod[n2+i];			/* I */
	    /* I + II + IV - III */
	    subcarry=vecadd3subcarry(&prod[n], 
			&prod[n3], &prod[n2], tmp, &tmp[n2], 0, n);
	    /* carry + II + IV + V - III */
	    subcarry=vecadd3subcarry(&prod[n2], 
			&prod[n3], &tmp[n], tmp, &tmp[n3], subcarry, n);
	    /* subcarry + V */
	    vecaddscarry (&prod[n3], &tmp[n], subcarry, n);
	}
}


static  DigitType vecadd4carry (accu, a, b, c, d, carry, k)
	DigitType *accu, *a, *b, *c, *d, carry;
	int k;
	/* accu[k]=a[k]+b[k]+c[k]+d[k]+carry; return carry; */
{	DigitType aa, bb, cc, dd, sum;
	for ( ; k>0; k--) {
		aa=*a++;
		bb=*b++;
		cc=*c++;
		dd=*d++;
		sum = aa+carry;
		carry = (sum < aa);
		sum += bb;
		carry += (sum < bb);
		sum += cc;
		carry += (sum < cc);
		sum += dd;
		carry += (sum < dd);
		*accu++= sum;
	}
	return carry;
}

static  int vecadd3subcarry (accu, a, b, c, d, carry, k)
	DigitType *accu, *a, *b, *c, *d;
	int carry, k;
	/* carry >= -1 */
	/* accu[k]=a[k]+b[k]+c[k]-d[k]+carry; return carry; */
{	DigitType aa, bb, cc, dd, sum, tmp, subc;
	if (carry < 0) {
		subc = 1;
		carry = 0;
	} else {
		subc=0;
	}
	for ( ; k>0; k--) {
		aa=*a++;
		bb=*b++;
		cc=*c++;
		dd=*d++;
		sum = aa+carry;
		carry = (sum < aa);
		sum += bb;
		carry += (sum < bb);
		sum += cc;
		carry += (sum < cc);
		tmp = sum - subc;
		subc = (tmp > sum);
		sum = tmp - dd;
		subc += (sum > tmp);
		*accu++= sum;
	}
	return carry-subc;
}

static  DigitType vecaddDigitType (accu, a, d, k)
	DigitType *accu, *a, d;
	int k;
	/* accu[k]=a[k]+d; return carry; */
{	DigitType sum, carry=d;
	for ( ; k>0; k-- ) {
		sum = *a++ + carry;
		carry = (sum < carry);
		*accu++ = sum;
	}
	return carry;
}
/* ACHTUNG:	k==0  ==>  carry==d */

static  DigitType vecaddscarry (accu, a, scarry, k)
	DigitType *accu, *a;
	int scarry, k;
	/* 2^BitsPerDigit > scarry >=-1 */
	/* accu[k]=a[k]+scarry; return carry; */
{	DigitType sum, carry, tmp;
	if (scarry >= 0) {
	    carry = scarry;
	    for ( ; k>0; k-- ) {
		sum = *a++ + carry;
		carry = (sum < carry);
		*accu++ = sum;
	    }
	    return carry;
	} else {
	    carry = 1;
	    for ( ; k>0; k-- ) {
		tmp = *a++;
		sum = tmp - carry;
		carry = (sum > tmp);
		*accu++ = sum;
	    }
	    return -carry;
	}
}
/* ACHTUNG:	k==0  ==>  carry==scarry */

