# HG changeset patch # User Eric Blake # Date 1206847447 21600 # Node ID fb687649f1778aec0bd67cf99b2e05cf82f2147d # Parent 18bab9955c43081f13ed7d879ed950e0c79c42c4 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 diff -r 18bab9955c43 -r fb687649f177 ChangeLog --- 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 + 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. diff -r 18bab9955c43 -r fb687649f177 doc/posix-functions/strtod.texi --- 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 diff -r 18bab9955c43 -r fb687649f177 lib/strtod.c --- 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 #include +#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. */ diff -r 18bab9955c43 -r fb687649f177 modules/strtod --- 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: +Link: +$(POW_LIB) + License: LGPL diff -r 18bab9955c43 -r fb687649f177 tests/test-strtod.c --- 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. */ {