changeset 9822:fb687649f177

Add hex float support. * modules/strtod (Depends-on): Add c-ctype. (Link): Mention POW_LIB. * lib/strtod.c (strtod): Recognize hex floats. Don't allow whitespace between 'e' and exponent. * tests/test-strtod.c (main): Enable hex float tests. * doc/posix-functions/strtod.texi (strtod): Document what gnulib now provides. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Sat, 29 Mar 2008 21:24:07 -0600
parents 18bab9955c43
children ac3a4b0c4b7e f09ed4bb510f
files ChangeLog doc/posix-functions/strtod.texi lib/strtod.c modules/strtod tests/test-strtod.c
diffstat 5 files changed, 128 insertions(+), 51 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Mar 29 13:50:21 2008 -0600
+++ b/ChangeLog	Sat Mar 29 21:24:07 2008 -0600
@@ -1,5 +1,14 @@
 2008-03-29  Eric Blake  <ebb9@byu.net>
 
+	Add hex float support.
+	* modules/strtod (Depends-on): Add c-ctype.
+	(Link): Mention POW_LIB.
+	* lib/strtod.c (strtod): Recognize hex floats.  Don't allow
+	whitespace between 'e' and exponent.
+	* tests/test-strtod.c (main): Enable hex float tests.
+	* doc/posix-functions/strtod.texi (strtod): Document what gnulib
+	now provides.
+
 	Document various strtod bugs, with some fixes.
 	* doc/posix-functions/strtod.texi (strtod): Document bugs with
 	"-0x", "inf", "nan", and hex constants.
--- a/doc/posix-functions/strtod.texi	Sat Mar 29 13:50:21 2008 -0600
+++ b/doc/posix-functions/strtod.texi	Sat Mar 29 21:24:07 2008 -0600
@@ -18,14 +18,6 @@
 @item
 This function returns a wrong end pointer on some platforms:
 Solaris 2.4.
-@end itemize
-
-Portability problems not fixed by Gnulib:
-@itemize
-@item
-This function returns a positive value for negative underflow on some
-platforms:
-glibc 2.4, Mingw, Cygwin.
 
 @item
 This function fails to do a valid parse of @samp{-0x} on some
@@ -53,9 +45,20 @@
 This function fails to parse C99 hexadecimal floating point on some
 platforms:
 Solaris 8, Mingw, OpenBSD 4.0.
+@end itemize
+
+Portability problems not fixed by Gnulib:
+@itemize
+@item
+This function returns a positive value for negative underflow on some
+platforms:
+glibc 2.4, Mingw, Cygwin.
 
 @item
 This function fails to correctly parse very long strings on some
 platforms:
 Mingw, Cygwin.
+
+@item
+The replacement function does not always return correctly rounded results.
 @end itemize
--- a/lib/strtod.c	Sat Mar 29 13:50:21 2008 -0600
+++ b/lib/strtod.c	Sat Mar 29 21:24:07 2008 -0600
@@ -25,12 +25,14 @@
 #include <stdbool.h>
 #include <string.h>
 
+#include "c-ctype.h"
+
 /* Convert NPTR to a double.  If ENDPTR is not NULL, a pointer to the
    character after the last one used in the number is put in *ENDPTR.  */
 double
 strtod (const char *nptr, char **endptr)
 {
-  const char *s;
+  const unsigned char *s;
   bool negative = false;
 
   /* The number so far.  */
@@ -38,6 +40,7 @@
 
   bool got_dot;			/* Found a decimal point.  */
   bool got_digit;		/* Seen any digits.  */
+  bool hex = false;		/* Look for hex float exponent.  */
 
   /* The exponent of the number.  */
   long int exponent;
@@ -51,7 +54,7 @@
   s = nptr;
 
   /* Eat whitespace.  */
-  while (isspace ((unsigned char) *s))
+  while (isspace (*s))
     ++s;
 
   /* Get the sign.  */
@@ -63,59 +66,104 @@
   got_dot = false;
   got_digit = false;
   exponent = 0;
-  for (;; ++s)
+
+  /* Check for hex float.  */
+  if (*s == '0' && c_tolower (s[1]) == 'x'
+      && (c_isxdigit (s[2]) || ('.' == s[2] && c_isxdigit (s[3]))))
     {
-      if ('0' <= *s && *s <= '9')
+      hex = true;
+      s += 2;
+      for (;; ++s)
 	{
-	  got_digit = true;
+	  if (c_isxdigit (*s))
+	    {
+	      got_digit = true;
 
-	  /* Make sure that multiplication by 10 will not overflow.  */
-	  if (num > DBL_MAX * 0.1)
-	    /* The value of the digit doesn't matter, since we have already
-	       gotten as many digits as can be represented in a `double'.
-	       This doesn't necessarily mean the result will overflow.
-	       The exponent may reduce it to within range.
+	      /* Make sure that multiplication by 16 will not overflow.  */
+	      if (num > DBL_MAX / 16)
+		/* The value of the digit doesn't matter, since we have already
+		   gotten as many digits as can be represented in a `double'.
+		   This doesn't necessarily mean the result will overflow.
+		   The exponent may reduce it to within range.
+
+		   We just need to record that there was another
+		   digit so that we can multiply by 16 later.  */
+		++exponent;
+	      else
+		num = ((num * 16.0)
+		       + (c_tolower (*s) - (c_isdigit (*s) ? '0' : 'a' - 10)));
 
-	       We just need to record that there was another
-	       digit so that we can multiply by 10 later.  */
-	    ++exponent;
+	      /* Keep track of the number of digits after the decimal point.
+		 If we just divided by 16 here, we would lose precision.  */
+	      if (got_dot)
+		--exponent;
+	    }
+	  else if (!got_dot && *s == '.')
+	    /* Record that we have found the decimal point.  */
+	    got_dot = true;
 	  else
-	    num = (num * 10.0) + (*s - '0');
+	    /* Any other character terminates the number.  */
+	    break;
+	}
+    }
+
+  /* Not a hex float.  */
+  else
+    {
+      for (;; ++s)
+	{
+	  if (c_isdigit (*s))
+	    {
+	      got_digit = true;
 
-	  /* Keep track of the number of digits after the decimal point.
-	     If we just divided by 10 here, we would lose precision.  */
-	  if (got_dot)
-	    --exponent;
+	      /* Make sure that multiplication by 10 will not overflow.  */
+	      if (num > DBL_MAX * 0.1)
+		/* The value of the digit doesn't matter, since we have already
+		   gotten as many digits as can be represented in a `double'.
+		   This doesn't necessarily mean the result will overflow.
+		   The exponent may reduce it to within range.
+
+		   We just need to record that there was another
+		   digit so that we can multiply by 10 later.  */
+		++exponent;
+	      else
+		num = (num * 10.0) + (*s - '0');
+
+	      /* Keep track of the number of digits after the decimal point.
+		 If we just divided by 10 here, we would lose precision.  */
+	      if (got_dot)
+		--exponent;
+	    }
+	  else if (!got_dot && *s == '.')
+	    /* Record that we have found the decimal point.  */
+	    got_dot = true;
+	  else
+	    /* Any other character terminates the number.  */
+	    break;
 	}
-      else if (!got_dot && *s == '.')
-	/* Record that we have found the decimal point.  */
-	got_dot = true;
-      else
-	/* Any other character terminates the number.  */
-	break;
     }
 
   if (!got_digit)
     {
       /* Check for infinities and NaNs.  */
-      if (tolower ((unsigned char) *s) == 'i'
-	  && tolower ((unsigned char) s[1]) == 'n'
-	  && tolower ((unsigned char) s[2]) == 'f')
+      if (c_tolower (*s) == 'i'
+	  && c_tolower (s[1]) == 'n'
+	  && c_tolower (s[2]) == 'f')
 	{
 	  s += 3;
 	  num = HUGE_VAL;
-	  if (tolower ((unsigned char) *s) == 'i'
-	      && tolower ((unsigned char) s[1]) == 'n'
-	      && tolower ((unsigned char) s[2]) == 'i'
-	      && tolower ((unsigned char) s[3]) == 't'
-	      && tolower ((unsigned char) s[4]) == 'y')
+	  if (c_tolower (*s) == 'i'
+	      && c_tolower (s[1]) == 'n'
+	      && c_tolower (s[2]) == 'i'
+	      && c_tolower (s[3]) == 't'
+	      && c_tolower (s[4]) == 'y')
 	    s += 5;
 	  goto valid;
 	}
 #ifdef NAN
-      else if (tolower ((unsigned char) *s) == 'n'
-	       && tolower ((unsigned char) s[1]) == 'a'
-	       && tolower ((unsigned char) s[2]) == 'n')
+      else if (c_tolower (*s) == 'n'
+	       && c_tolower (s[1]) == 'a'
+	       && c_tolower (s[2]) == 'n')
 	{
 	  s += 3;
 	  num = NAN;
@@ -126,8 +174,8 @@
 	     hexadecimal number, but the result is still a NaN.  */
 	  if (*s == '(')
 	    {
-	      const char *p = s + 1;
-	      while (isalnum ((unsigned char) *p))
+	      const unsigned char *p = s + 1;
+	      while (c_isalnum (*p))
 		p++;
 	      if (*p == ')')
 		s = p + 1;
@@ -138,7 +186,7 @@
       goto noconv;
     }
 
-  if (tolower ((unsigned char) *s) == 'e')
+  if (c_tolower (*s) == (hex ? 'p' : 'e') && !isspace (s[1]))
     {
       /* Get the exponent specified after the `e' or `E'.  */
       int save = errno;
@@ -160,7 +208,7 @@
 	  else
 	    goto overflow;
 	}
-      else if (end == s)
+      else if (end == (char *) s)
 	/* There was no exponent.  Reset END to point to
 	   the 'e' or 'E', so *ENDPTR will be set there.  */
 	end = (char *) s - 1;
@@ -172,6 +220,13 @@
   if (num == 0.0)
     goto valid;
 
+  if (hex)
+    {
+      /* ldexp takes care of range errors.  */
+      num = ldexp (num, exponent);
+      goto valid;
+    }
+
   /* Multiply NUM by 10 to the EXPONENT power,
      checking for overflow and underflow.  */
 
--- a/modules/strtod	Sat Mar 29 13:50:21 2008 -0600
+++ b/modules/strtod	Sat Mar 29 21:24:07 2008 -0600
@@ -6,6 +6,7 @@
 m4/strtod.m4
 
 Depends-on:
+c-ctype
 stdbool
 stdlib
 
@@ -19,6 +20,9 @@
 Include:
 <stdlib.h>
 
+Link:
+$(POW_LIB)
+
 License:
 LGPL
 
--- a/tests/test-strtod.c	Sat Mar 29 13:50:21 2008 -0600
+++ b/tests/test-strtod.c	Sat Mar 29 21:24:07 2008 -0600
@@ -333,6 +333,15 @@
   }
   {
     errno = 0;
+    const char input[] = "1E 2";
+    char *ptr;
+    double result = strtod (input, &ptr);
+    ASSERT (result == 1.0);
+    ASSERT (ptr == input + 1);
+    ASSERT (errno == 0);
+  }
+  {
+    errno = 0;
     const char input[] = "0x";
     char *ptr;
     double result = strtod (input, &ptr);
@@ -637,8 +646,6 @@
   }
 
   /* Hex.  */
-#if 0
-  /* TODO - gnulib doesn't implement this yet.  */
   {
     errno = 0;
     const char input[] = "0xa";
@@ -693,7 +700,6 @@
     ASSERT (ptr == input + 6);
     ASSERT (errno == 0);
   }
-#endif
 
   /* Large buffers.  */
   {