cstrtod

strtod underflow, return value != 0


Here's my test code:

errno = 0;
d = strtod("1.8011670033376514e-308", NULL);

With this code, I get d == 1.8011670033376514e-308 and errno == ERANGE.

From strtod(3):

If the correct value would cause overflow, plus or minus HUGE_VAL (HUGE_VALF, HUGE_VALL) is returned (according to the sign of the value), and ERANGE is stored in errno. If the correct value would cause underflow, zero is returned and ERANGE is stored in errno.

So, it seems to me that either errno should be zero (no error) or d should be zero (underflow).

Is this a bug, or am I missing something? This happens for many different versions of eglibc and gcc.


Solution

  • In §7.22.1.3 The strtod(), strtof() and strtold() functions, the C11 standard (ISO/IEC 9899:2011) says:

    The functions return the converted value, if any. If no conversion could be performed, zero is returned. If the correct value overflows and default rounding is in effect (7.12.1), plus or minus HUGE_VAL, HUGE_VALF, or HUGE_VALL is returned (according to the return type and sign of the value), and the value of the macro ERANGE is stored in errno. If the result underflows (7.12.1), the functions return a value whose magnitude is no greater than the smallest normalized positive number in the return type; whether errno acquires the value ERANGE is implementation-defined.

    The standard also notes in §5.2.4.2.2 Characteristics of floating types that IEC 60559 (IEEE 754) floating point numbers have the limit:

    DBL_MIN 2.2250738585072014E-308 // decimal constant
    

    Since 1.8011670033376514e-308 is smaller than DBL_MIN, you get a sub-normal number, and ERANGE is quite appropriate (but optional).

    On Mac OS X 10.9.4 with GCC 4.9.1, the following program:

    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main(void)
    {
        char *end;
        errno = 0;
        double d = strtod("1.8011670033376514e-308", &end);
        if (errno != 0)
        {
            int errnum = errno;
            printf("%d: %s\n", errnum, strerror(errnum));
        }
        printf("%24.16e\n", d);
        unsigned char *p = (unsigned char *)&d;
        const char *pad = "";
        for (size_t i = 0; i < sizeof(double); i++)
        {
            printf("%s0x%.2X", pad, *p++);
            pad = " ";
        }
        putchar('\n');
        return 0;
    }
    

    produces the output:

    34: Result too large
     1.8011670033376514e-308
    0x01 0x00 0x00 0x00 0xA8 0xF3 0x0C 0x00
    

    The error message is ironically wrong — the value is too small — but you can't have everything.