/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
*/

/*	Copyright (c) 1984, 1986, 1987, 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: atof.c 1.7 90/09/15 $@(#)libc-i386:libc-i386/gen/atof.c	1.4"
/*LINTLIBRARY*/
/*
 *	C library - ascii to floating (atof) and string to double (strtod)
 *
 *	This version compiles both atof and strtod depending on the value
 *	of STRTOD, which is set in the file and may be overridden on the
 *	"cc" command line.  The only difference is the storage of a pointer
 *	to the character which terminated the conversion.
 */
#ifndef STRTOD
#define STRTOD	0
#endif
#include "shlib.h"
#include <ctype.h>
#include "values.h"
#include <errno.h>
#include <math.h>

extern double _ptod();

extern char _numeric[];

extern int errno;

#define	POS_OVERFLOW		1
#define NEG_OVERFLOW		2
static int
range_check(x)
double x;
{
	union {
		double	d;
		unsigned int	i[2];
	} u;
	u.d = x;
	if ((u.i[1] >> 20) == 0x7FF)
		return(POS_OVERFLOW);
	if ((u.i[1] >> 20) == 0xFFF)
		return(NEG_OVERFLOW);
	return(0);
}

#if STRTOD
#define STORE_PTR	(*ptr = p)
#define DEC_PTR	(*ptr = p - 1)
#define SUB_PTR (*ptr = *ptr - 1)
#define GOT_DIGIT	(got_digit++)
#define RET_ZERO(val)	if (!got_digit) return (0.0)

double
strtod(p, ptr)
register char *p;
char **ptr;
#else
#define STORE_PTR
#define DEC_PTR
#define SUB_PTR
#define GOT_DIGIT
#define RET_ZERO(val)	if (!val) return (0.0)

double
atof(p)
register char *p;
#endif
{
	register int c, exp = 0;
	long high, low = 0;
	int lowlen = 0;
	int neg_val = 0;
	double ret_val;
#if STRTOD
	int got_digit = 0;
	char *dummy;
	if (ptr == (char **)0) 
		ptr = &dummy; /* harmless dumping place */
	STORE_PTR;
#endif
	while (isspace(c = *p)) /* eat leading white space */
		p++;
	switch (c) { /* process sign */
	case '-':
		neg_val = 1;
	case '+': /* fall-through */
		p++;
	}
	{	/* accumulate value */
		register long temphigh = 0;
		register int decpt = 0;

		while (isdigit(c = *p++) || (c == _numeric[0] || c == '.') && !decpt++ &&
							isdigit(c = *p++)) {
			GOT_DIGIT;
			exp -= decpt; /* decr exponent if decimal pt. seen */
			c -= '0';
			if (temphigh < MAXLONG/10) {
				temphigh *= 10;
				temphigh += c;
			} else if (++lowlen <= 9) {
				low = 10*low + c;
			} else {
				exp++;
				lowlen--;
			}
		}
		RET_ZERO(temphigh);
		high = temphigh;
	}
	DEC_PTR; /* in case there is no legitimate exponent */
	if (c == 'E' || c == 'e') { /* accumulate exponent */
		register int e_exp = 0, neg_exp = 0;
		STORE_PTR;

		switch (*p) { /* process sign */
		case '-':
			neg_exp++;
		case '+': /* fall-through */
		case ' ': /* many FORTRAN environments generate this! */
			p++;
		}
		if (isdigit(c = *p)) { /* found a legitimate exponent */
			do {
				/* limit outrageously large exponents */
				if (e_exp < DMAXEXP)
					e_exp = 10 * e_exp + c - '0';
			} while (isdigit(c = *++p));
			if (neg_exp)
				exp -= e_exp;
			else
				exp += e_exp;
			STORE_PTR;
		} else {
			SUB_PTR;
		}
	}
	ret_val = _ptod(high, low, lowlen, exp, neg_val);
	switch (range_check(ret_val)) {
	case POS_OVERFLOW:
	  	errno = ERANGE;
		return(HUGE_VAL);
	case NEG_OVERFLOW:
	  	errno = ERANGE;
		return(-HUGE_VAL);
	default:
		break;
	}
	return(ret_val);
}
