ftob.c 5.63 KB
/* Portions of this work are derived from the Standard C Library, */
/* copyright (c) 1992 by P.J. Plauger, published by Prentice-Hall, */
/* and are used with permission. */

#include <float.h>
#include <string.h>
#include <stdlib.h>

#include "gload.h"

#define NDIG	8

#define FINITE	-1
#define INF	1
#define NAN	2

/* static data */
static const double pows[] = {
	1e1L, 1e2L, 1e4L, 1e8L, 1e16L, 1e32L,
	1e64L, 1e128L, 1e256L,
};

static short unscale(short *, double *);
static void genf(field_t *, char, char *, short, short);

void
ftob(field_t *px, char code)
{	/* convert long double to text */
	char ac[32];
	char *p = ac;
	double dval = px->v.d;
	short errx, nsig, xexp;

	if (px->prec < 0)
		px->prec = 6;
	else if (px->prec == 0 && (code == 'g' || code == 'G'))
		px->prec = 1;
	if (0 < (errx = unscale(&xexp, &px->v.d))) {
		/* x == Nan, x == INF */
		memcpy(px->s, errx == NAN ? "NaN" : "Inf", px->n1 = 3);
		return;
	} else if (0 == errx)	/*x == 0 */
		nsig = 0, xexp = 0;
	else {	/* 0 < |x|, convert it */
		{	/* scale dval to ~~10^(NDIG/2) */
			int i, n;

			if (dval < 0.0)
				dval = -dval;
			if ((xexp = xexp * 30103L / 100000L - NDIG/2) < 0) {
				/* scale up */
				n = (-xexp + (NDIG/2-1)) & ~(NDIG/2-1), xexp = -n;
				for (i = 0; 0 < n; n >>= 1, ++i)
					if (n & 1)
						dval *= pows[i];
			} else if (0 < xexp) {	/* scale down */
				double factor = 1.0;

				xexp &= ~(NDIG/2-1);
				for (n = xexp, i = 0; 0 < n; n >>= 1, ++i)
					if (n & 1)
						factor *= pows[i];
				dval /= factor;
			}
		}
		{	/* convert significant digits */
			int gen = px->prec +
				(code == 'f' ? xexp + 2 + NDIG : 2 + NDIG / 2);

			if (LDBL_DIG + NDIG / 2 < gen)
				gen = LDBL_DIG + NDIG / 2;
			for (*p++ = '0'; 0 < gen && 0.0 < dval; p += NDIG) {
				/* convert NDIG at a time */
				int j;
				long lo = (long) dval;

				if (0 < (gen -= NDIG))
					dval = (dval - (double) lo) * 1e8L;
				for (p += NDIG, j = NDIG; 0 < lo && 0 <= --j; ) {
					/* convert NDIG digits */
					ldiv_t qr;

					qr = ldiv(lo, 10);
					*--p = qr.rem + '0', lo = qr.quot;
				}
				while (0 <= --j)
					*--p = '0';
			}
			gen = p - &ac[1];
			for (p = &ac[1], xexp += NDIG - 1; *p == '0'; ++p)
				--gen, --xexp;	/* correct xexp */
			nsig = px->prec + (code == 'f' ? xexp + 1 :
				code == 'e' || code == 'E' ? 1 : 0);
			if (gen < nsig)
				nsig = gen;
			if (0 < nsig) {	/* round and strip trailing zeros */
				const char drop = nsig < gen && '5' <= p[nsig] ? '9' : '0';
				int n;

				for (n = nsig; p[--n] == drop; )
					--nsig;
				if (drop == '9')
					++p[n];
				if (n < 0)
					--p, ++nsig, ++xexp;
			}
		}
	}
	genf(px, code, p, nsig, xexp);
}

static short
unscale(short *pex, double *px)
{	/* separate *px to |frac| < 1/2 and 2^*pex */
	unsigned short *ps = (unsigned short *) px;
	short xchar = (ps[_D0] & _DMASK) >> _DOFF;

	if (xchar == _DMAX) {	/* NaN or INF */
		*pex = 0;
		return (ps[_D0] & _DFRAC || ps[_D1] || ps[_D2] || ps[_D3] ?
			NAN : INF);
	} else if (0 < xchar /* || (xchar = _Dnorm(ps)) != 0 */) {
		/* finite, reduce to [1/2, 1) */
		ps[_D0] = ps[_D0] & ~_DMASK | _DBIAS << _DOFF;
		*pex = xchar - _DBIAS + 1;	/* for SGI */
		return (FINITE);
	} else if (xchar < 0) {	/* error! */
		return (NAN);
	} else {	/* zero */
		*pex = 0;
		return (0);
	}
}

static void
genf(field_t *px, char code, char *p, short nsig, short xexp)
{	/* generate float text */
	const char point = '.';

	if (nsig <= 0)
		nsig = 1, p = "0";
	if (code == 'f' || (code == 'g' || code == 'G') &&
		-4 <= xexp && xexp < px->prec) {	/* 'f' format */
		++xexp;		/* change to leading digit count */
		if (code != 'f') {	/* fixup for 'g' */
			if (!(px->flags & _FNO) && nsig < px->prec)
				px->prec = nsig;
			if ((px->prec -= xexp) < 0)
				px->prec = 0;
		}
		if (xexp <= 0) {	/* digits only to right of point */
			px->s[px->n1++] = '0';
			if (0 < px->prec || px->flags & _FNO)
				px->s[px->n1++] = point;
			if (px->prec < -xexp)
				xexp = -px->prec;
			px->nz1 = -xexp;
			px->prec += xexp;
			if (px->prec < nsig)
				nsig = px->prec;
			memcpy(&px->s[px->n1], p, px->n2 = nsig);
			px->nz2 = px->prec - nsig;
		} else if (nsig < xexp) {	/* zeros before point */
			memcpy(&px->s[px->n1], p, nsig);
			px->n1 += nsig;
			px->nz1 = xexp - nsig;
			if (0 < px->prec || px->flags & _FNO)
				px->s[px->n1] = point, ++px->n2;
			px->nz2 = px->prec;
		} else {	/* enough digits before point */
			memcpy(&px->s[px->n1], p, xexp);
			px->n1 += xexp;
			nsig -= xexp;
			if (0 < px->prec || px->flags & _FNO)
				px->s[px->n1++] = point;
			if (px->prec < nsig)
				nsig = px->prec;
			memcpy(&px->s[px->n1], p + xexp, nsig);
			px->n1 += nsig;
			px->nz1 = px->prec - nsig;
		}
	} else {	/* 'e' format */
		if (code == 'g' || code == 'G') {	/* fixup for 'g' */
			if (nsig < px->prec)
				px->prec = nsig;
			if (--px->prec < 0)
				px->prec = 0;
			code = code == 'g' ? 'e' : 'E';
		}
		px->s[px->n1++] = *p++;
		if (0 < px->prec || px->flags & _FNO)
			px->s[px->n1++] = point;
		if (0 < px->prec) {	/* put fraction digits */
			if (px->prec < --nsig)
				nsig = px->prec;
			memcpy(&px->s[px->n1], p, nsig);
			px->n1 += nsig;
			px->nz1 = px->prec - nsig;
		}
		p = &px->s[px->n1];	/* put exponent */
		*p++ = code;
		if (0 <= xexp)
			*p++ = '+';
		else {	/* negative exponent */
			*p++ = '-';
			xexp = -xexp;
		}
		if (100 <= xexp) {	/* put oversize exponent */
			if (1000 <= xexp)
				*p++ = xexp / 1000 + '0', xexp %= 1000;
			*p++ = xexp / 100 + '0', xexp %= 100;
		}
		*p++ = xexp / 10 + '0', xexp %= 10;
		*p++ = xexp + '0';
		px->n2 = p - &px->s[px->n1];
	}
	if ((px->flags & (_FMI | _FZE)) == _FZE) {	/* pad with leading zeros */
		int n = px->n0 + px->n1 + px->nz1 + px->n2 + px->nz2;

		if (n < px->width)
			px->nz0 = px->width - n;
	}
}