shithub: purgatorio

ref: bd6c2aad586814b091ce5aca9d41cf2c51adb37b
dir: /utils/c2l/mpatof.c/

View raw version
#include	"cc.h"

enum
{
	Mpscale	= 29,		/* safely smaller than bits in a long */
	Mpprec	= 36,		/* Mpscale*Mpprec sb > largest fp exp */
	Mpbase	= 1L<<Mpscale,
};

typedef
struct
{
	long	a[Mpprec];
	char	ovf;
} Mp;

int	mpatof(char*, double*);
int	mpatov(char *s, vlong *v);
void	mpint(Mp*, int);
void	mppow(Mp*, int, int);
void	mpmul(Mp*, int);
void	mpadd(Mp*, Mp*);
int	mptof(Mp*, double*);

/*
 * convert a string, s, to floating in *d
 * return conversion overflow.
 * required syntax is [+-]d*[.]d*[e[+-]d*]
 */
int
mpatof(char *s, double *d)
{
	Mp a, b;
	int dp, c, f, ef, ex, zer;
	double d1, d2;

	dp = 0;		/* digits after decimal point */
	f = 0;		/* sign */
	ex = 0;		/* exponent */
	zer = 1;	/* zero */
	memset(&a, 0, sizeof(a));
	for(;;) {
		switch(c = *s++) {
		default:
			goto bad;
		case '-':
			f = 1;
		case ' ':
		case  '\t':
		case  '+':
			continue;
		case '.':
			dp = 1;
			continue;
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			zer = 0;
		case '0':
			mpint(&b, c-'0');
			mpmul(&a, 10);
			mpadd(&a, &b);
			if(dp)
				dp++;
			continue;
		case 'E':
		case 'e':
			ex = 0;
			ef = 0;
			for(;;) {
				c = *s++;
				if(c == '+' || c == ' ' || c == '\t')
					continue;
				if(c == '-') {
					ef = 1;
					continue;
				}
				if(c >= '0' && c <= '9') {
					ex = ex*10 + (c-'0');
					continue;
				}
				break;
			}
			if(ef)
				ex = -ex;
		case 0:
			break;
		}
		break;
	}
	if(a.ovf)
		goto bad;
	if(zer) {
		*d = 0;
		return 0;
	}
	if(dp)
		dp--;
	dp -= ex;
	if(dp > 0) {
		/*
		 * must divide by 10**dp
		 */
		if(mptof(&a, &d1))
			goto bad;

		/*
		 * trial exponent of 8**dp
		 * 8 (being between 5 and 10)
		 * should pick up all underflows
		 * in the division of 5**dp.
		 */
		d2 = frexp(d1, &ex);
		d2 = ldexp(d2, ex-3*dp);
		if(d2 == 0)
			goto bad;

		/*
		 * decompose each 10 into 5*2.
		 * create 5**dp in fixed point
		 * and then play with the exponent
		 * for the remaining 2**dp.
		 * note that 5**dp will overflow
		 * with as few as 134 input digits.
		 */
		mpint(&a, 1);
		mppow(&a, 5, dp);
		if(mptof(&a, &d2))
			goto bad;
		d1 = frexp(d1/d2, &ex);
		d1 = ldexp(d1, ex-dp);
		if(d1 == 0)
			goto bad;
	} else {
		/*
		 * must multiply by 10**|dp| --
		 * just do it in fixed point.
		 */
		mppow(&a, 10, -dp);
		if(mptof(&a, &d1))
			goto bad;
	}
	if(f)
		d1 = -d1;
	*d = d1;
	return 0;

bad:
	return 1;
}

/*
 * convert a to floating in *d
 * return conversion overflow
 */
int
mptof(Mp *a, double *d)
{
	double f, g;
	long x, *a1;
	int i;

	if(a->ovf)
		return 1;
	a1 = a->a;
	f = ldexp(*a1++, 0);
	for(i=Mpscale; i<Mpprec*Mpscale; i+=Mpscale)
		if(x = *a1++) {
			g = ldexp(x, i);
			/*
			 * NOTE: the test (g==0) is plan9
			 * specific. ansi compliant overflow
			 * is signaled by HUGE and errno==ERANGE.
			 * change this for your particular ldexp.
			 */
			if(g == 0)
				return 1;
			f += g;		/* this could bomb! */
		}
	*d = f;
	return 0;
}

/*
 * return a += b
 */
void
mpadd(Mp *a, Mp *b)
{
	int i, c;
	long x, *a1, *b1;

	if(b->ovf)
		a->ovf = 1;
	if(a->ovf)
		return;
	c = 0;
	a1 = a->a;
	b1 = b->a;
	for(i=0; i<Mpprec; i++) {
		x = *a1 + *b1++ + c;
		c = 0;
		if(x >= Mpbase) {
			x -= Mpbase;
			c = 1;
		}
		*a1++ = x;
	}
	a->ovf = c;
}

/*
 * return a = c
 */
void
mpint(Mp *a, int c)
{

	memset(a, 0, sizeof(*a));
	a->a[0] = c;
}

/*
 * return a *= c
 */
void
mpmul(Mp *a, int c)
{
	Mp p;
	int b;

	memmove(&p, a, sizeof(p));
	if(!(c & 1))
		memset(a, 0, sizeof(*a));
	c &= ~1;
	for(b=2; c; b<<=1) {
		mpadd(&p, &p);
		if(c & b) {
			mpadd(a, &p);
			c &= ~b;
		}
	}
}

/*
 * return a *= b**e
 */
void
mppow(Mp *a, int b, int e)
{
	int b1;

	b1 = b*b;
	b1 = b1*b1;
	while(e >= 4) {
		mpmul(a, b1);
		e -= 4;
		if(a->ovf)
			return;
	}
	while(e > 0) {
		mpmul(a, b);
		e--;
	}
}

/*
 * convert a string, s, to vlong in *v
 * return conversion overflow.
 * required syntax is [0[x]]d*
 */
int
mpatov(char *s, vlong *v)
{
	vlong n, nn;
	int c;
	n = 0;
	c = *s;
	if(c == '0')
		goto oct;
	while(c = *s++) {
		if(c >= '0' && c <= '9')
			nn = n*10 + c-'0';
		else
			goto bad;
		if(n < 0 && nn >= 0)
			goto bad;
		n = nn;
	}
	goto out;
oct:
	s++;
	c = *s;
	if(c == 'x' || c == 'X')
		goto hex;
	while(c = *s++) {
		if(c >= '0' || c <= '7')
			nn = n*8 + c-'0';
		else
			goto bad;
		if(n < 0 && nn >= 0)
			goto bad;
		n = nn;
	}
	goto out;
hex:
	s++;
	while(c = *s++) {
		if(c >= '0' && c <= '9')
			c += 0-'0';
		else
		if(c >= 'a' && c <= 'f')
			c += 10-'a';
		else
		if(c >= 'A' && c <= 'F')
			c += 10-'A';
		else
			goto bad;
		nn = n*16 + c;
		if(n < 0 && nn >= 0)
			goto bad;
		n = nn;
	}
out:
	*v = n;
	return 0;

bad:
	*v = ~0;
	return 1;
}