OpenSolaris_b135/lib/libc/sparc/fp/__quad_mag.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1994-1997, by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This file contains __quad_mag_add and __quad_mag_sub, the core
 * of the quad precision add and subtract operations.
 */

#include "quad.h"

/*
 * __quad_mag_add(x, y, z, fsr)
 *
 * Sets *z = *x + *y, rounded according to the rounding mode in *fsr,
 * and updates the current exceptions in *fsr.  This routine assumes
 * *x and *y are finite, with the same sign (i.e., an addition of
 * magnitudes), |*x| >= |*y|, and *z already has its sign bit set.
 */
void
__quad_mag_add(const union longdouble *x, const union longdouble *y,
	union longdouble *z, unsigned int *fsr)
{
	unsigned int	lx, ly, ex, ey, frac2, frac3, frac4;
	unsigned int	round, sticky, carry, rm;
	int		e, uflo;

	/* get the leading significand words and exponents */
	ex = (x->l.msw & 0x7fffffff) >> 16;
	lx = x->l.msw & 0xffff;
	if (ex == 0)
		ex = 1;
	else
		lx |= 0x10000;

	ey = (y->l.msw & 0x7fffffff) >> 16;
	ly = y->l.msw & 0xffff;
	if (ey == 0)
		ey = 1;
	else
		ly |= 0x10000;

	/* prenormalize y */
	e = (int) ex - (int) ey;
	round = sticky = 0;
	if (e >= 114) {
		frac2 = x->l.frac2;
		frac3 = x->l.frac3;
		frac4 = x->l.frac4;
		sticky = ly | y->l.frac2 | y->l.frac3 | y->l.frac4;
	} else {
		frac2 = y->l.frac2;
		frac3 = y->l.frac3;
		frac4 = y->l.frac4;
		if (e >= 96) {
			sticky = frac4 | frac3 | (frac2 & 0x7fffffff);
			round = frac2 & 0x80000000;
			frac4 = ly;
			frac3 = frac2 = ly = 0;
			e -= 96;
		} else if (e >= 64) {
			sticky = frac4 | (frac3 & 0x7fffffff);
			round = frac3 & 0x80000000;
			frac4 = frac2;
			frac3 = ly;
			frac2 = ly = 0;
			e -= 64;
		} else if (e >= 32) {
			sticky = frac4 & 0x7fffffff;
			round = frac4 & 0x80000000;
			frac4 = frac3;
			frac3 = frac2;
			frac2 = ly;
			ly = 0;
			e -= 32;
		}
		if (e) {
			sticky |= round | (frac4 & ((1 << (e - 1)) - 1));
			round = frac4 & (1 << (e - 1));
			frac4 = (frac4 >> e) | (frac3 << (32 - e));
			frac3 = (frac3 >> e) | (frac2 << (32 - e));
			frac2 = (frac2 >> e) | (ly << (32 - e));
			ly >>= e;
		}

		/* add, propagating carries */
		frac4 += x->l.frac4;
		carry = (frac4 < x->l.frac4);
		frac3 += x->l.frac3;
		if (carry) {
			frac3++;
			carry = (frac3 <= x->l.frac3);
		} else {
			carry = (frac3 < x->l.frac3);
		}
		frac2 += x->l.frac2;
		if (carry) {
			frac2++;
			carry = (frac2 <= x->l.frac2);
		} else {
			carry = (frac2 < x->l.frac2);
		}
		lx += ly;
		if (carry)
			lx++;

		/* postnormalize */
		if (lx >= 0x20000) {
			sticky |= round;
			round = frac4 & 1;
			frac4 = (frac4 >> 1) | (frac3 << 31);
			frac3 = (frac3 >> 1) | (frac2 << 31);
			frac2 = (frac2 >> 1) | (lx << 31);
			lx >>= 1;
			ex++;
		}
	}

	/* keep track of whether the result before rounding is tiny */
	uflo = (lx < 0x10000);

	/* get the rounding mode, fudging directed rounding modes */
	/* as though the result were positive */
	rm = *fsr >> 30;
	if (z->l.msw)
		rm ^= (rm >> 1);

	/* see if we need to round */
	if (round | sticky) {
		*fsr |= FSR_NXC;

		/* round up if necessary */
		if (rm == FSR_RP || (rm == FSR_RN && round &&
			(sticky || (frac4 & 1)))) {
			if (++frac4 == 0)
				if (++frac3 == 0)
					if (++frac2 == 0)
						if (++lx >= 0x20000) {
							lx >>= 1;
							ex++;
						}
		}
	}

	/* check for overflow */
	if (ex >= 0x7fff) {
		/* store the default overflowed result */
		*fsr |= FSR_OFC | FSR_NXC;
		if (rm == FSR_RN || rm == FSR_RP) {
			z->l.msw |= 0x7fff0000;
			z->l.frac2 = z->l.frac3 = z->l.frac4 = 0;
		} else {
			z->l.msw |= 0x7ffeffff;
			z->l.frac2 = z->l.frac3 = z->l.frac4 = 0xffffffff;
		}
	} else {
		/* store the result */
		if (lx >= 0x10000)
			z->l.msw |= (ex << 16);
		z->l.msw |= (lx & 0xffff);
		z->l.frac2 = frac2;
		z->l.frac3 = frac3;
		z->l.frac4 = frac4;

		/* if the pre-rounded result was tiny and underflow trapping */
		/* is enabled, simulate underflow */
		if (uflo && (*fsr & FSR_UFM))
			*fsr |= FSR_UFC;
	}
}

/*
 * __quad_mag_sub(x, y, z, fsr)
 *
 * Sets *z = *x - *y, rounded according to the rounding mode in *fsr,
 * and updates the current exceptions in *fsr.  This routine assumes
 * *x and *y are finite, with opposite signs (i.e., a subtraction of
 * magnitudes), |*x| >= |*y|, and *z already has its sign bit set.
 */
void
__quad_mag_sub(const union longdouble *x, const union longdouble *y,
	union longdouble *z, unsigned int *fsr)
{
	unsigned int	lx, ly, ex, ey, frac2, frac3, frac4;
	unsigned int	guard, round, sticky, borrow, rm;
	int		e;

	/* get the leading significand words and exponents */
	ex = (x->l.msw & 0x7fffffff) >> 16;
	lx = x->l.msw & 0xffff;
	if (ex == 0)
		ex = 1;
	else
		lx |= 0x10000;

	ey = (y->l.msw & 0x7fffffff) >> 16;
	ly = y->l.msw & 0xffff;
	if (ey == 0)
		ey = 1;
	else
		ly |= 0x10000;

	/* prenormalize y */
	e = (int) ex - (int) ey;
	guard = round = sticky = 0;
	if (e > 114) {
		sticky = ly | y->l.frac2 | y->l.frac3 | y->l.frac4;
		ly = frac2 = frac3 = frac4 = 0;
	} else {
		frac2 = y->l.frac2;
		frac3 = y->l.frac3;
		frac4 = y->l.frac4;
		if (e >= 96) {
			sticky = frac4 | frac3 | (frac2 & 0x3fffffff);
			round = frac2 & 0x40000000;
			guard = frac2 & 0x80000000;
			frac4 = ly;
			frac3 = frac2 = ly = 0;
			e -= 96;
		} else if (e >= 64) {
			sticky = frac4 | (frac3 & 0x3fffffff);
			round = frac3 & 0x40000000;
			guard = frac3 & 0x80000000;
			frac4 = frac2;
			frac3 = ly;
			frac2 = ly = 0;
			e -= 64;
		} else if (e >= 32) {
			sticky = frac4 & 0x3fffffff;
			round = frac4 & 0x40000000;
			guard = frac4 & 0x80000000;
			frac4 = frac3;
			frac3 = frac2;
			frac2 = ly;
			ly = 0;
			e -= 32;
		}
		if (e > 1) {
			sticky |= guard | round |
				(frac4 & ((1 << (e - 2)) - 1));
			round = frac4 & (1 << (e - 2));
			guard = frac4 & (1 << (e - 1));
			frac4 = (frac4 >> e) | (frac3 << (32 - e));
			frac3 = (frac3 >> e) | (frac2 << (32 - e));
			frac2 = (frac2 >> e) | (ly << (32 - e));
			ly >>= e;
		} else if (e == 1) {
			sticky |= round;
			round = guard;
			guard = frac4 & 1;
			frac4 = (frac4 >> 1) | (frac3 << 31);
			frac3 = (frac3 >> 1) | (frac2 << 31);
			frac2 = (frac2 >> 1) | (ly << 31);
			ly >>= 1;
		}
	}

	/* complement guard, round, and sticky as need be */
	if (sticky) {
		round = !round;
		guard = !guard;
	} else if (round) {
		guard = !guard;
	}
	borrow = (guard | round | sticky);

	/* subtract, propagating borrows */
	frac4 = x->l.frac4 - frac4;
	if (borrow) {
		frac4--;
		borrow = (frac4 >= x->l.frac4);
	} else {
		borrow = (frac4 > x->l.frac4);
	}
	frac3 = x->l.frac3 - frac3;
	if (borrow) {
		frac3--;
		borrow = (frac3 >= x->l.frac3);
	} else {
		borrow = (frac3 > x->l.frac3);
	}
	frac2 = x->l.frac2 - frac2;
	if (borrow) {
		frac2--;
		borrow = (frac2 >= x->l.frac2);
	} else {
		borrow = (frac2 > x->l.frac2);
	}
	lx -= ly;
	if (borrow)
		lx--;

	/* get the rounding mode */
	rm = *fsr >> 30;

	/* handle zero result */
	if (!(lx | frac2 | frac3 | frac4 | guard)) {
		z->l.msw = ((rm == FSR_RM)? 0x80000000 : 0);
		z->l.frac2 = z->l.frac3 = z->l.frac4 = 0;
		return;
	}

	/* postnormalize */
	if (lx < 0x10000) {
		/* if cancellation occurred or the exponent is 1, */
		/* the result is exact */
		if (lx < 0x8000 || ex == 1) {
			while ((lx | (frac2 & 0xfffe0000)) == 0 && ex > 32) {
				lx = frac2;
				frac2 = frac3;
				frac3 = frac4;
				frac4 = ((guard)? 0x80000000 : 0);
				guard = 0;
				ex -= 32;
			}
			while (lx < 0x10000 && ex > 1) {
				lx = (lx << 1) | (frac2 >> 31);
				frac2 = (frac2 << 1) | (frac3 >> 31);
				frac3 = (frac3 << 1) | (frac4 >> 31);
				frac4 <<= 1;
				if (guard) {
					frac4 |= 1;
					guard = 0;
				}
				ex--;
			}
			if (lx >= 0x10000)
				z->l.msw |= (ex << 16);
			z->l.msw |= (lx & 0xffff);
			z->l.frac2 = frac2;
			z->l.frac3 = frac3;
			z->l.frac4 = frac4;

			/* if the result is tiny and underflow trapping is */
			/* enabled, simulate underflow */
			if (lx < 0x10000 && (*fsr & FSR_UFM))
				*fsr |= FSR_UFC;
			return;
		}

		/* otherwise we only borrowed one place */
		lx = (lx << 1) | (frac2 >> 31);
		frac2 = (frac2 << 1) | (frac3 >> 31);
		frac3 = (frac3 << 1) | (frac4 >> 31);
		frac4 <<= 1;
		if (guard)
			frac4 |= 1;
		ex--;
	} else {
		sticky |= round;
		round = guard;
	}

	/* fudge directed rounding modes as though the result were positive */
	if (z->l.msw)
		rm ^= (rm >> 1);

	/* see if we need to round */
	if (round | sticky) {
		*fsr |= FSR_NXC;

		/* round up if necessary */
		if (rm == FSR_RP || (rm == FSR_RN && round &&
			(sticky || (frac4 & 1)))) {
			if (++frac4 == 0)
				if (++frac3 == 0)
					if (++frac2 == 0)
						if (++lx >= 0x20000) {
							lx >>= 1;
							ex++;
						}
		}
	}

	/* store the result */
	z->l.msw |= (ex << 16) | (lx & 0xffff);
	z->l.frac2 = frac2;
	z->l.frac3 = frac3;
	z->l.frac4 = frac4;
}