view src/libsys/sprintf/float.c @ 86:425ab6d987f3

src/libsys: pieced together from Citrine
author Mychaela Falconia <falcon@freecalypso.org>
date Fri, 20 Jul 2018 20:36:19 +0000
parents
children
line wrap: on
line source

/*
 * Embedded [v]sprintf() implementation by Mychaela Falconia,
 * loosely based on the 4.3BSD-Tahoe version.
 *
 * This module contains the floating point conversion functions.
 */

#include <sys/types.h>
#include <ctype.h>
#include "defs.h"

extern double modf();

extern u_char * _sprintf_field(u_char *op, int width, int flags, int sign,
			u_char *body, int size, int dprec, int fpprec);

#define	MAX_INT_DIGITS	10	/* 2^32-1 in decimal */
#define	MAXFRACT	16	/* max sensible for 64-bit double */
#define	DEFPREC		6

static char *
emit_integer_portion(unsigned number, char *endp)
{
	char *t = endp;

	do {
		*--t = tochar(number % 10);
		number /= 10;
	} while (number);
	return (t);
}

static int
f_round(double fract, char *start, char *end, int sign)
{
	double tmp;

	(void)modf(fract * 10, &tmp);
	if (tmp > 4)
		for (;; --end) {
			if (*end == '.')
				--end;
			if (++*end <= '9')
				break;
			*end = '0';
			if (end == start) {
				*--end = '1';
				return(1);
			}
		}
	/* ``"%.3f", (double)-0.0004'' gives you a negative 0. */
	else if (sign == '-')
		for (;; --end) {
			if (*end == '.')
				--end;
			if (*end != '0')
				break;
			if (end == start)
				return(-1);	/* clear the -ve */
		}
	return(0);
}

u_char *
_sprintf_percent_f(u_char *op, int width, int flags, int sign,
		double number, int prec)
{
	char buf[MAX_INT_DIGITS + 1 + MAXFRACT];
	int extra_prec = 0;
	int origsign = sign;
	int round_stat;
	double fract, tmp;
	char *start, *t;

	/* first order of business: weed out infinities and NaNs */
	if (isinf(number)) {
		if (number < 0)
			sign = '-';
		return _sprintf_field(op, width, flags, sign, "Inf", 3, 0, 0);
	}
	if (isnan(number))
		return _sprintf_field(op, width, flags, sign, "NaN", 3, 0, 0);
	/* OK, we know it's a valid real like in the good old VAX days */
	if (number < 0) {
		sign = '-';
		number = -number;
	}
	fract = modf(number, &tmp);
	if (tmp > (double) 0xFFFFFFFF)
		return _sprintf_field(op, width, flags, sign, "Toobig", 6,
					0, 0);
	start = emit_integer_portion((unsigned) tmp, buf + MAX_INT_DIGITS);
	if (prec > MAXFRACT) {
		extra_prec = prec - MAXFRACT;
		prec = MAXFRACT;
	} else if (prec == -1)
		prec = DEFPREC;
	t = buf + MAX_INT_DIGITS;
	/*
	 * if precision required or alternate flag set, add in a
	 * decimal point.
	 */
	if (prec || flags&ALT)
		*t++ = '.';
	/* if requires more precision and some fraction left */
	if (fract) {
		if (prec)
			do {
				fract = modf(fract * 10, &tmp);
				*t++ = tochar((int)tmp);
			} while (--prec && fract);
		if (fract) {
			round_stat = f_round(fract, start, t - 1, sign);
			if (round_stat == 1)
				start--;
			else if (round_stat == -1)
				sign = origsign;
		}
	}
	return _sprintf_field(op, width, flags, sign, start, t - start,
				0, prec + extra_prec);
}