Strtod underflow, возвращаемое значение!= 0

Здесь мой тестовый код:

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

С помощью этого кода я получаю d == 1.8011670033376514e-308 и errno == ERANGE.

Из strtod (3):

Если правильное значение вызовет переполнение, возвращается плюс или минус HUGE_VAL (HUGE_VALF, HUGE_VALL) (в соответствии с знаком значения), а ERANGE хранится в errno. Если правильное значение приведет к недопущению, возвращается ноль и ERANGE сохраняется в errno.

Итак, мне кажется, что либо errno должен быть нулевым (без ошибок), либо d должен быть равен нулю (underflow).

Это ошибка, или я чего-то не хватает? Это происходит для разных версий eglibc и gcc.

Ответ 1

В §7.22.1.3 Функции strtod(), strtof() и strtold(), стандарт C11 (ISO/IEC 9899: 2011) гласит:

Функции возвращают преобразованное значение, если оно есть. Если преобразование не может быть выполнено, ноль возвращается. Если правильное значение переполнения и округление по умолчанию действуют (7.12.1), плюс или минус HUGE_VAL, HUGE_VALF или HUGE_VALL (согласно тип возврата и знак значения), а значение макроса ERANGE сохраняется в errno. Если результат заканчивается (7.12.1), функции возвращают значение, величина которого равна не более, чем наименьшее нормализованное положительное число в возвращаемом типе; будь то errno получает значение ERANGE определяется реализацией.

Стандарт также отмечает в §5.2.4.2.2. Характеристики плавающих типов, которые имеют номера с плавающей запятой IEC 60559 (IEEE 754), имеют предел:

DBL_MIN 2.2250738585072014E-308 // decimal constant

Так как 1.8011670033376514e-308 меньше, чем DBL_MIN, вы получаете под нормальное число, а ERANGE вполне уместно (но необязательно).

В Mac OS X 10.9.4 с GCC 4.9.1 следующая программа:

#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;
}

выводит результат:

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

Сообщение об ошибке иронически неверно - значение слишком мало, но вы не можете иметь все.

Ответ 2

Код ведет себя в соответствии с Спецификация OpenOS POSIX для strtod():

Если правильное значение приведет к нижнему потоку, значение, величина которого не превышает наименьшее нормализованное положительное число в возвращаемом типе, возвращается и errno устанавливается на [ERANGE].

Я бы сказал, что то, что вы видите, представляет собой подробную ошибку на странице руководства Linux.

Ответ 3

Если strtod() возвращено ненулевое значение (это не +/- HUGE_VAL), вызов преуспел (в соответствии с цитируемой вами страницей man).

Ссылаясь на страницу руководства для errno.h:

Заголовочный файл <errno.h> определяет целочисленную переменную errno, которая        устанавливается системными вызовами и некоторыми библиотечными функциями в случае        чтобы указать, что пошло не так. Его ценность значительна только        когда возвращаемое значение вызова указывает на ошибку (т.е. -1 из        большинство системных вызовов; -1 или NULL из большинства библиотечных функций); а        функция, которой преуспевает, может изменить errno.

Таким образом, вы можете проверить только errno на ошибку, если возвращаемое значение вашей функции фактически возвращает значение, указывающее на ошибку.

Более полное объяснение errno (и объяснение его отношения к strtod()) можно найти на другом StackExchange.