v04i013: Replacement for strtod()

ok at quintus.UUCP ok at quintus.UUCP
Wed Aug 3 11:19:11 AEST 1988


Posting-number: Volume 4, Issue 13
Submitted-by: "A. Nonymous" <ok at quintus.UUCP>
Archive-name: strtod

strtod() is a nice function, but it's often missing or broken.
Here is a replacement for it which does the parsing and checking
required of strtod() but calls atof() to do the conversion.

---- cut here ---- cut here ---- cut here ---- cut here ---- cut here ----
#!/bin/sh
# This is a shell archive, meaning:                              
# 1. Remove everything above the #!/bin/sh line.                
# 2. Save the resulting test in a file.                          
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	makefile
#	str2dbl.c
sed -e 's/^X//' >README <<'------ EOF ------'
XFILES:
X	README		- this file
X	makefile	- a trivial makefile
X	str2dbl.c	- defines str2dbl(), requires atof() + float support
X
Xdouble str2dbl(char *str, char **ptr)
X    converts the character string str points to to a double-precision
X    floating-point number and returns it.  str2dbl() recognises
X
X	<space>... [+|-] <digit>... [. <digit>...] [<exponent>]
X
X    where <exponent> is
X
X	e|E [+|-| ] <digit> <digit>...
X
X    If ptr is not (char**)NULL, *ptr is assigned a pointer to the
X    character just after the last character accepted by str2dbl().
X    {This will typically be a layout character or NUL.}
X
X    If there aren't any digits at the front (e.g. the input is
X    "e45" or "Fred" or "-gotcha-") str2dbl() will make *ptr point
X    to the whole of str (even if there were leading spaces and signs)
X    and will return 0.0.
X
X    If there is some problem with the exponent (e.g. the input is
X    "1.2e%45") str2dbl() will reject all of the exponent, making *ptr
X    point to the 'e', and will convert only the preceding characters.
X
X    A single space after the 'e' or 'E' of an exponent is accepted as
X    if it were a '+' sign, because some output formatting routines
X    generate numbers that look like that.  Spaces are not otherwise
X    accepted inside numbers.
X
X    If the correct value cannot be represented, str2dbl() sets errno
X    to ERANGE, and returns +/-HUGE for overflow, +/-ZERO for underflow.
X
XWARNING:
X    The source code as provided is set up for 64-bit IEEE-754 floating
X    point.  VAX and IBM floating-point formats are different, so the
X    numeric range is different.  Some machines which look as though
X    they have IEEE floats may not support infinities or denormalised
X    numbers, in which case the numeric range is different.  You will
X    have to determine the correct values for your machine.
X
------ EOF ------
ls -l README
sed -e 's/^X//' >makefile <<'------ EOF ------'
Xstr2dbl.o: str2dbl.c
------ EOF ------
ls -l makefile
sed -e 's/^X//' >str2dbl.c <<'------ EOF ------'
X/*  File   : str2dbl.c
X    Author : Richard A. O'Keefe @ Quintus Computer Systems, Inc.
X    Updated: Tuesday August 2nd, 1988
X    Defines: double str2dbl(char *str, char**ptr)
X*/
X
X/*  This is an implementation of the strtod() function described in the 
X    System V manuals, with a different name to avoid linker problems.
X    All that str2dbl() does itself is check that the argument is well-formed
X    and is in range.  It leaves the work of conversion to atof(), which is
X    assumed to exist and deliver correct results (if they can be represented).
X
X    There are two reasons why this should be provided to the net:
X    (a) some UNIX systems do not yet have strtod(), or do not have it
X        available in the BSD "universe" (but they do have atof()).
X    (b) some of the UNIX systems that *do* have it get it wrong.
X	(some crash with large arguments, some assign the wrong *ptr value).
X    There is a reason why *we* are providing it: we need a correct version
X    of strtod(), and if we give this one away maybe someone will look for
X    mistakes in it and fix them for us (:-).
X*/
X    
X/*  The following constants are machine-specific.  MD{MIN,MAX}EXPT are
X    integers and MD{MIN,MAX}FRAC are strings such that
X	0.${MDMAXFRAC}e${MDMAXEXPT} is the largest representable double,
X	0.${MDMINFRAC}e${MDMINEXPT} is the smallest representable +ve double
X    MD{MIN,MAX}FRAC must not have any trailing zeros.
X    The values here are for IEEE-754 64-bit floats.
X    It is not perfectly clear to me whether an IEEE infinity should be
X    returned for overflow, nor what a portable way of writing one is,
X    so HUGE is just 0.MAXFRAC*10**MAXEXPT (this seems still to be the
X    UNIX convention).
X
X    I do know about <values.h>, but the whole point of this file is that
X    we can't always trust that stuff to be there or to be correct.
X*/
Xstatic	int	MDMINEXPT	= {-323};
Xstatic	char	MDMINFRAC[]	= "494065645841246544";
Xstatic	double	ZERO		= 0.0;
X
Xstatic	int	MDMAXEXPT	= { 309};
Xstatic	char	MDMAXFRAC[]	= "17976931348623147";
Xstatic	double	HUGE		= 1.7976931348623147e308;
X
Xextern	double	atof();		/* Only called when result known to be ok */
X
X#include <errno.h>
Xextern	int	errno;
X
Xdouble str2dbl(str, ptr)
X    char *str;
X    char **ptr;
X    {
X	int sign, scale, dotseen;
X	int esign, expt;
X	char *save;
X	register char *sp, *dp;
X	register int c;
X	char *buforg, *buflim;
X	char buffer[64];		/* 45-digit significand + */
X					/* 13-digit exponent */
X	sp = str;
X	while (*sp == ' ') sp++;
X	sign = 1;
X	if (*sp == '-') sign -= 2, sp++;
X	dotseen = 0, scale = 0;
X	dp = buffer;	
X	*dp++ = '0'; *dp++ = '.';
X	buforg = dp, buflim = buffer+48;
X	for (save = sp; c = *sp; sp++)
X	    if (c == '.') {
X		if (dotseen) break;
X		dotseen++;
X	    } else
X	    if ((unsigned)(c-'0') > (unsigned)('9'-'0')) {
X		break;
X	    } else
X	    if (c == '0') {
X		if (dp != buforg) {
X		    /* This is not the first digit, so we want to keep it */
X		    if (dp < buflim) *dp++ = c;
X		} else {
X		    /* No non-zero digits seen yet */
X		    /* If a . has been seen, scale must be adjusted */
X		    if (dotseen) scale--;
X		}
X	    } else {
X		/* This is a nonzero digit, so we want to keep it */
X		if (dp < buflim) *dp++ = c;
X		/* If it precedes a ., scale must be adjusted */
X		if (!dotseen) scale++;
X	    }
X	if (sp == save) {
X	    if (ptr) *ptr = str;
X	    errno = EDOM;		/* what should this be? */
X	    return ZERO;
X	}
X	
X	while (dp > buforg && dp[-1] == '0') --dp;
X	if (dp == buforg) *dp++ = '0';
X	*dp = '\0';
X	/*  Now the contents of buffer are
X	    +--+--------+-+--------+
X	    |0.|fraction|\|leftover|
X	    +--+--------+-+--------+
X			 ^dp points here
X	    where fraction begins with 0 iff it is "0", and has at most
X	    45 digits in it, and leftover is at least 16 characters.
X	*/
X	save = sp, expt = 0, esign = 1;
X	do {
X	    c = *sp++;
X	    if (c != 'e' && c != 'E') break;
X	    c = *sp++;
X	    if (c == '-') esign -= 2, c = *sp++; else
X	    if (c == '+' || c == ' ') c = *sp++;
X	    if ((unsigned)(c-'0') > (unsigned)('9'-'0')) break;
X	    while (c == '0') c = *sp++;
X	    for (; (unsigned)(c-'0') <= (unsigned)('9'-'0'); c = *sp++)
X		expt = expt*10 + c-'0';	    
X	    if (esign < 0) expt = -expt;
X	    save = sp-1;
X	} while (0);
X	if (ptr) *ptr = save;
X	expt += scale;
X	/*  Now the number is sign*0.fraction*10**expt  */
X	errno = ERANGE;
X	if (expt > MDMAXEXPT) {
X	    return HUGE*sign;
X	} else
X	if (expt == MDMAXEXPT) {
X	    if (strcmp(buforg, MDMAXFRAC) > 0) return HUGE*sign;
X	} else
X	if (expt < MDMINEXPT) {
X	    return ZERO*sign;
X	} else
X	if (expt == MDMINEXPT) {
X	    if (strcmp(buforg, MDMINFRAC) < 0) return ZERO*sign;
X	}
X	/*  We have now established that the number can be  */
X	/*  represented without overflow or underflow  */
X	(void) sprintf(dp, "E%d", expt);
X	errno = 0;
X	return atof(buffer)*sign;
X    }
X
------ EOF ------
ls -l str2dbl.c
exit 0



More information about the Comp.sources.misc mailing list