/*	Copyright (c) 1984, 1986, 1987, 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)libm:i386/sin.c	1.2"
#ident	"@(#)sin.c	2.2 - 87/04/20"
/*LINTLIBRARY*/
/*
 *	sin and cos of double-precision argument.
 *	Returns ERANGE error and value 0 if argument too large.
 */

#include <math.h>
#include <values.h>
#include <errno.h>
#include <sys/sysi86.h>
#include <sys/fp.h>

#define NDP_UNKNOWN 0
#define NDP_287 1
#define NDP_387 2
static char ndp_kind = NDP_UNKNOWN;

static char x_pi_4 [10] =	/* constant for xsincos_287 */
	{ 0x35,0xC2,0x68,0x21,0xA2,0xDA,0x0F,0xC9,0xFE,0x3F };

asm	double xsincos_287(x,cosflag)	/* 80287 sine/cosine function */
{
%mem	x,cosflag;
	fldt	x_pi_4		/ load modulus, pi/4
	fldl	x		/ bring x to the top of stack
	fxam			/ record sign of argument
	fstsw	%ax		/ store argument status to %ax
	mov	%eax,%ecx	/ save argument status in %ecx
	fprem			/ reduce the angle
	fstsw	%ax		/ store remainder and status
	xchg	%eax,%ecx	/ %eax = fxam status, %ecx = fprem status
	testb	$0x04,%ch	/ if reduction complete, C2 = 0
	jz	xsincos_abs	/ continue if |x| < 2^63
	fstp	%st(0)		/ free st(0) and st(1)
	fstp	%st(0)
	fldz			/ return with %st = 0.0
	jmp	xsincos_end
xsincos_abs:
	fabs			/ ensure a positive argument
	testb	$0x01,cosflag	/ test function flag
	jz	xsincos_sin	/ branch on sine function
	and	$0xfd00,%eax	/ if cosine, reset sign of x and %al
	orb	$0x80,%ch	/ add '010' to octant (rotate x pi/2)
	addb	$0x40,%ch	/ add 1 to C3, forcing carry out
	rclb	$1,%al		/ and move carry flag to lsb of %al
	xorb	%al,%ch		/ add carry to C0 without changing C1
xsincos_sin:
	testb	$0x02,%ch	/ complement angle if C1 = 1
	jz	xsincos_tan	/ branch if x is in an even octant
	fsubr	%st(1),%st	/ subtract argument from pi/4
xsincos_tan:
	fptan			/ tan(st) = %st(1)/%st(0) = Y/X
	testb	$0x42,%ch	/ if x falls in octant 1,2,5, or 6
	jpo	xsincos_cos	/ find cos(x) = X/sqrt(X^2 + Y^2)
	fxch	%st(1)		/ else, find sin(x) = Y/sqrt(X^2 + Y^2)
xsincos_cos:
	fst	%st(2)		/ save X or Y in st(2)
	fmul	%st(0),%st	/ square X and Y
	fxch	%st(1)
	fmul	%st(0),%st
	faddp	%st,%st(1)	/ %st = X^2 + Y^2
	fsqrt			/ %st = sqrt(X^2 + Y^2)
	andb	$0x01,%ch	/ form the sign of the result
	andb	$0x02,%ah	/ combine fprem C0 and sign of x
	orb	%ah,%ch		/ two positives or two negatives
	jpe	xsincos_div	/ yield a normally signed result
	fchs			/ else, force a sign change
xsincos_div:
	fdivrp	%st,%st(1)	/ %st contains final result
xsincos_end:
}

asm	double xsin_387(x)	/* 80387 sine function */
{
%mem	x;
	fldl	x		/ bring x to the top of stack
	fsin			/ 80387 fsin instruction
}

asm	double xcos_387(x)	/* 80387 cosine function */
{
%mem	x;
	fldl	x		/ bring x to the top of stack
	fcos			/ 80387 fcos instruction
}

double
sin(x)
double x;
{
	extern double sin_cos();

	return (sin_cos(x, 0));
}

static double
sin_cos(x, cosflag)
double x;
int cosflag;
{

	struct exception exc;
	int fp_kind, fp_ret;

	if (ndp_kind == NDP_UNKNOWN) {
		fp_ret = sysi86(SI86FPHW, &fp_kind);
		if (fp_ret == 0 && (fp_kind & 3) == 3)
			ndp_kind = NDP_387;
		else
			ndp_kind = NDP_287;
	}

	exc.arg1 = x;
	if (cosflag) {
		exc.name = "cos";
	} else
		exc.name = "sin";
	if (x > X_TLOSS) {
		exc.type = TLOSS;
		exc.retval = 0.0;
		if (!matherr(&exc)) {
			(void) write(2, exc.name, 3);
			(void) write(2, ": TLOSS error\n", 14);
			errno = ERANGE;
		}
		return (exc.retval);
	}

	if (ndp_kind == NDP_387)	/* use 80387 sine/cosine instructions */
		x = cosflag ? xcos_387(x) : xsin_387(x);
	else {				/* use 80287 sine/cosine function */
		x = xsincos_287(x,cosflag);
	}

	exc.retval = x;
	if (exc.arg1 > X_PLOSS || exc.arg1 < -X_PLOSS) {
		exc.type = PLOSS;
		if (!matherr(&exc))
			errno = ERANGE;
	}
	return (exc.retval);
}

double
cos(x)
double x;
{
	return (x ? sin_cos(x, 1) : 1.0);
}
