#include <math.h>
#include <useful.h>
# include	<sccs.h>

SCCSID(@(#)ftoa.c	8.1	12/31/84)

# define	MAXDIG		25
# define        MAXEXP          40

/*
**  FLOATING POINT TO ASCII CONVERSION
**
**	'Value' is converted to an ascii character string and stored
**	into 'ascii'.  Ascii should have room for at least 'width' + 1
**	characters.  'Width' is the width of the output field (max).
**	'Prec' is the number of characters to put after the decimal
**	point.  The format of the output string is controlled by
**	'format'.
**
**	'Format' can be:
**		e or E: "E" format output
**		f or F:  "F" format output
**		g or G:  "F" format output if it will fit, otherwise
**			use "E" format.
**		n or N:  same as G, but decimal points will not always
**			be aligned.
**
**	If 'format' is upper case, the "E" comes out in upper case;
**	otherwise it comes out in lower case.
**
**	When the field width is not big enough, it fills the field with
**	stars ("*****") and returns zero.  Normal return is the width
**	of the output field (sometimes shorter than 'width').
*/


/*
 * Macros for converting digits to letters and vice versa
 */
#define	to_digit(c)	((c) - '0')
#define is_digit(c)	((unsigned)to_digit(c) <= 9)
#define	to_char(n)	((n) + '0')

static char *fcvt();
static char *ecvt();
static char *round();
static char *exponent();

int
ftoa(value, ascii, width, prec1, format)
double	value;
char	*ascii;
int	width;
int	prec1;
char	format;
{
	auto int	expon;
	auto int	sign;
	register int	avail;
	register char	*a;
	register char	*p;
	char		mode;
	int		lowercase;
	int		prec;
        char            buf[MAXEXP];    /*  K.Okamoto  */
        char      *startp, *endp;

	prec = prec1;
	mode = format;
	lowercase = 'a' - 'A';
	if (mode >= 'a')
		mode -= 'a' - 'A';
	else
		lowercase = 0;

	if (mode != 'E')
	{
		/* try 'F' style output */
	        startp = buf;
	        endp = buf + sizeof(buf);
		p = fcvt(value, prec, &expon, &sign, startp, endp);

		avail = width;
		a = ascii;

		/* output sign */
		if (sign)
		{
			avail--;
			*a++ = '-';
		}

		/* output '0' before the decimal point */
		if (expon <= 0)
		{
			*a++ = '0';
			avail--;
		}

		/* compute space length left after dec pt and fraction */
		avail -= prec + 1;
		if (mode == 'G')
			avail -= 4;

		if (avail >= expon)
		{

			/* it fits.  output */
			while (expon > 0)
			{
				/* output left of dp */
				expon--;
				if (*p)
				{
					*a++ = *p++;
				}
				else
					*a++ = '0';
			}

			/* output fraction (right of dec pt) */
			avail = expon;
			goto frac_out;
		}
		/* won't fit; let's hope for G format */
	}

	if (mode != 'F')
	{
		/* try to do E style output */
	        startp = buf;
	        endp = buf + sizeof(buf);
		p = ecvt(value, prec + 1, &expon, &sign, startp, endp);
		avail = width - 5;
		a = ascii;

		/* output the sign */
		if (sign)
		{
			*a++ = '-';
			avail--;
		}
	}

	/* check for field too small */
	if (mode == 'F' || avail < prec)
	{
		/* sorry joker, you lose */
		a = ascii;
		for (avail = width; avail > 0; avail--)
			*a++ = '*';
		*a = 0;
		return (0);
	}

	/* it fits; output the number */
	mode = 'E';

	/* output the LHS single digit */
	*a++ = *p++;
	expon--;

	/* output the rhs */
	avail = 1;

  frac_out:
/*	*a++ = '.';           K.Okamoto      */
        prec++;           /*  K.Okamoto  */
	while (prec > 0)
	{
		prec--;
		if (avail < 0)
		{
			avail++;
			*a++ = '0';
		}
		else
		{
			if (*p)
				*a++ = *p++;
			else
				*a++ = '0';
		}
	}

	/* output the exponent */
	if (mode == 'E')
	{
		*a++ = 'E' + lowercase;
		if (expon < 0)
		{
			*a++ = '-';
			expon = -expon;
		}
		else
			*a++ = '+';
		*a++ = (expon / 10) % 10 + '0';
		*a++ = expon % 10 + '0';
	}

	/* output spaces on the end in G format */
	if (mode == 'G')
	{
		*a++ = ' ';
		*a++ = ' ';
		*a++ = ' ';
		*a++ = ' ';
	}

	/* finally, we can return */
	*a = 0;
	avail = a - ascii;
	return (avail);
}


/*-
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Chris Torek.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*  the following four functions are stolen from the source code of 
 *  function cvt() in 386bsd itself in /usr/src/lib/libc/stdio/vfprintf.c 
 *        by  K.Okamoto 
 */

char *
fcvt(number, prec, expon, sign, startp, endp)
	double number;
	register int prec;
	int *expon, *sign;
        char *startp, *endp;
{
	register char *p, *t;
	register double fract;
	int expcnt;
	double integer, tmp;

        *startp = '\0';
	expcnt = 0;
	if (number < 0) {
		number = -number;
		*sign = 1;
	} else
		*sign = 0;

	fract = modf(number, &integer);

	/* get an extra slot for rounding. */
	t = ++startp;

	/*
	 * get integer portion of number; put into the end of the buffer; the
	 * .01 is added for modf(356.0 / 10, &integer) returning .59999999...
	 */
	for (p = endp - 1; integer; ++expcnt) {
		tmp = modf(integer / 10, &integer);
		*p-- = to_char((int)((tmp + .01) * 10));
	}
	/* reverse integer into beginning of buffer */
	if (expcnt)
		for (; ++p < endp; *t++ = *p);
	else
		*t++ = '0';
	/*
	 * if precision required, add in a decimal point.
	 */
	if (prec )
		*t++ = '.';
	/* if requires more precision and some fraction left */
	if (fract) {
		if (prec)
			do {
				fract = modf(fract * 10, &tmp);
				*t++ = to_char((int)tmp);
			} while (--prec && fract);
		if (fract)
			startp = round(fract, (int *)NULL, startp,
			    t - 1, (char)0, sign);
	}
	for (; prec--; *t++ = '0');
        *expon = expcnt;
	return ((char *)(startp));
}


char *
ecvt(number, prec, expon, sign, startp, endp)
	double number;
	register int prec;
	int *expon, *sign;
        char *startp, *endp;
{
	register char *p, *t;
	register double fract;
	int expcnt, gformat;
	double integer, tmp;

        *startp = '\0';
	expcnt = gformat = 0;
	if (number < 0) {
		number = -number;
		*sign = 1;
	} else
		*sign = 0;

	fract = modf(number, &integer);

	/* get an extra slot for rounding. */
	t = ++startp;

	/*
	 * get integer portion of number; put into the end of the buffer; the
	 * .01 is added for modf(356.0 / 10, &integer) returning .59999999...
	 */
	for (p = endp - 1; integer; ++expcnt) {
		tmp = modf(integer / 10, &integer);
		*p-- = to_char((int)((tmp + .01) * 10));
	}
	if (expcnt) {
		*t++ = *++p;
		if (prec)
			*t++ = '.';
		/* if requires more precision and some integer left */
		for (; prec && ++p < endp; --prec)
			*t++ = *p;
		/*
		 * if done precision and more of the integer component,
		 * round using it; adjust fract so we don't re-round
		 * later.
		 */
		if (!prec && ++p < endp) {
			fract = 0;
			startp = round((double)0, &expcnt, startp,
			    t - 1, *p, sign);
		}
		/* adjust expcnt for digit in front of decimal */
		--expcnt;
	}
	/* until first fractional digit, decrement exponent */
	else if (fract) {
		/* adjust expcnt for digit in front of decimal */
		for (expcnt = -1;; --expcnt) {
			fract = modf(fract * 10, &tmp);
			if (tmp)
				break;
		}
		*t++ = to_char((int)tmp);
		if (prec)
			*t++ = '.';
	}
	else {
		*t++ = '0';
		if (prec)
			*t++ = '.';
	}
	/* if requires more precision and some fraction left */
	if (fract) {
		if (prec)
			do {
				fract = modf(fract * 10, &tmp);
			        *t++ = to_char((int)tmp);
			} while (--prec && fract);
		if (fract)
			startp = round(fract, &expcnt, startp,
			    t - 1, (char)0, sign);
	}
	/* if requires more precision */
	for (; prec--; *t++ = '0');
	/* unless alternate flag, trim any g/G format trailing 0's */
	if (gformat) {
		while (t > startp && *--t == '0');
		if (*t == '.')
			--t;
		++t;
	}
	t = exponent(t, expcnt);
        *expon = expcnt;
	return ((char *)(startp));
}

static char *
round(fract, exp, start, end, ch, sign)
	double fract;
	int *exp;
	register char *start, *end;
	char ch;
        int *sign;
{
	double tmp;

	if (fract)
		(void)modf(fract * 10, &tmp);
	else
		tmp = to_digit(ch);
	if (tmp > 4)
		for (;; --end) {
			if (*end == '.')
				--end;
			if (++*end <= '9')
				break;
			*end = '0';
			if (end == start) {
				if (exp) {	/* e/E; increment exponent */
					*end = '1';
					++*exp;
				}
				else {		/* f; add extra digit */
				*--end = '1';
				--start;
				}
				break;
			}
		}
	/* ``"%.3f", (double)-0.0004'' gives you a negative 0. */
	else if (*sign != 0)
		for (;; --end) {
			if (*end == '.')
				--end;
			if (*end != '0')
				break;
			if (end == start)
				*sign = 0;
		}
	return (start);
}

static char *
exponent(p, exp)
	register char *p;
	register int exp;
{
	register char *t;
	char expbuf[MAXEXP];

	*p++ = 'E';
	if (exp < 0) {
		exp = -exp;
		*p++ = '-';
	}
	else
		*p++ = '+';
	t = expbuf + MAXEXP;
	if (exp > 9) {
		do {
			*--t = to_char(exp % 10);
		} while ((exp /= 10) > 9);
		*--t = to_char(exp);
		for (; t < expbuf + MAXEXP; *p++ = *t++);
	}
	else {
		*p++ = '0';
		*p++ = to_char(exp);
	}
	return (p);
}
