Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

strtod underflow, return value != 0

Tags:

c

strtod

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.

like image 374
Petri Lehtinen Avatar asked Aug 26 '14 05:08

Petri Lehtinen


3 Answers

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.

like image 85
Jonathan Leffler Avatar answered Nov 12 '22 20:11

Jonathan Leffler


The code is behaving according to The Open Group's POSIX specification of strtod():

If the correct value would cause an underflow, a value whose magnitude is no greater than the smallest normalized positive number in the return type shall be returned and errno set to [ERANGE].

I'd say what you're seeing is an error in detail in the Linux manpage.

like image 35
Michael Burr Avatar answered Nov 12 '22 20:11

Michael Burr


If strtod() returned a non-zero value (that is not +/- HUGE_VAL), the call has succeeded (according to the man page you quoted).

Referring to the man page for errno.h:

The <errno.h> header file defines the integer variable errno, which is set by system calls and some library functions in the event of an error to indicate what went wrong. Its value is significant only when the return value of the call indicated an error (i.e., -1 from most system calls; -1 or NULL from most library functions); a function that succeeds is allowed to change errno.

Thus, you can only check errno for an error if the return value of your function actually returns a value indicating an error has occurred.

A more complete explanation of errno (and an explanation of its relationship to strtod()) can be found on another StackExchange.

like image 3
jxh Avatar answered Nov 12 '22 18:11

jxh