ok@quintus.UUCP (08/03/88)
Posting-number: Volume 4, Issue 13 Submitted-by: "A. Nonymous" <ok@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