Как я могу проверить, может ли строка быть преобразована в число с плавающей запятой?

Во-первых, мой контекст - это контекст автора компилятора, которому нужно преобразовать литералы с плавающей запятой (строки) в значения с плавающей запятой/двойные числа. Я не занимался программированием с плавающей запятой последние 15 лет, поэтому я почти уверен, что это совершенно глупый вопрос новичка.

double res; 
errno=0; 
*res = strtod((const char*)literal,NULL); 
if (errno==ERANGE) throw_a_literal_out_of_range_exception();
return res;

но в библиотеке времени выполнения c нет функции "strtof"?

РЕДАКТИРОВАТЬ: Чтобы уточнить. Я уже знаю, что строка 'literal' является допустимым шаблоном для числа C с плавающей запятой. Он уже прошел проверку регулярными выражениями. Я просто хочу проверить, есть ли проблема с диапазоном/точностью.

Причина в исходном коде Eiffel, который пользователь может написать

a := {REAL_32} 3.1415
b := {REAL_32} 3.0E200

Чтобы преобразовать записанное число с плавающей запятой в 32-битное число с плавающей запятой. Думаю, во втором случае компилятор должен определить, что значение выходит за пределы допустимого диапазона, и выдать ошибку или хотя бы предупреждение.


person Lothar    schedule 20.12.2009    source источник
comment
Почему вам нужно, чтобы это было число с плавающей точкой, а не двойное?   -  person    schedule 20.12.2009
comment
Я думаю, вы должны преобразовать свой литерал в двойной. Затем приступайте к актерскому составу. Во время каста вы должны проверить, находится ли двойное значение в пределах диапазона с плавающей запятой.   -  person    schedule 21.12.2009


Ответы (7)


Поскольку у вас есть значение в double, вы можете просто проверить, выходит ли оно за пределы диапазона float:

#include <stdlib.h>
#include <stdio.h>
#include <float.h>

int main(int argc, char *argv[])
{
    double d = FLT_MAX;
    if (argc > 1) {
        d = strtod(argv[1], NULL);
    }
    if ((d > 0 && (d > FLT_MAX || d < FLT_MIN))
                || (d < 0 && (d < -FLT_MAX || d > -FLT_MIN)))
        printf("Invalid float: %g\n", d);
    else
        printf("Valid: %g\n", d);

    return EXIT_SUCCESS;
}

Запуск программы:

$ ./a.out
Valid: 3.40282e+38
$ ./a.out 3.5e38
Invalid float: 3.5e+38
$ ./a.out -1e40
Invalid float: -1e+40

Вам может понадобиться или не понадобиться добавить тест для правильного возврата strtod(), в зависимости от того, есть ли возможность переполнения в типе double.

person Alok Singhal    schedule 22.12.2009

В C89 вы можете использовать sscanf.

Например:

float myfloat;

if(sscanf(str, "%f", &myfloat) != 1)
    /* String could not be converted */
else
    /* String was converted, myfloat is usable */
person Community    schedule 20.12.2009

@ Николас Гой: я не думаю, что sscanf(str, "%f, &float) == 1 (или != 1) действительно делает то, что нужно.

Если в str есть дополнительные символы (например, "1.1foo"), он будет парсить без ошибок, что, вероятно, нежелательно. Это можно исправить, выполнив:

char dummy;
if (sscanf(str, "%f%c", &float, &dummy) != 1) {
   /* Parse error. */
} else {
   /* Parsed cleanly. */
}

вместо. Тем не менее, sscanf с %f, скорее всего, будет использовать strtod внутренне и все равно будет приведено к float. Язык в стандарте C говорит:

a,e,f,g Соответствует необязательному знаковому числу с плавающей запятой, бесконечности или NaN, формат которого совпадает с ожидаемым для последовательности субъектов функции strtod. Соответствующий аргумент должен быть указателем на плавающий.

что подразумевает это, и это кажется правдой для меня (gcc 4.2.1 на FreeBSD). Используя приведенный выше код sscanf, "1.79e308" анализируется успешно, но имеет значение +inf, как и "5e-300" со значением 0.0, и это те же результаты, которые вы получили бы от (float) 1.79e308 и (float) 5e-300.

Так или иначе. Все сказанное, я задаюсь вопросом, почему OP все равно хочет использовать float вместо double.

person jamesdlin    schedule 20.12.2009
comment
Потому что я пишу компилятор Eiffel (ну, это огромный препроцессор Eiffel->C), и пользователь может написать {REAL_32}3.1415, чтобы получить 32-битное действительное число вместо 64-битного по умолчанию. Я думаю, что компилятор должен выдать ошибку или предупреждение, если исходный код уже содержит потерю точности. И, кстати, я уже знаю, что строка представляет собой строку с допустимым числом с плавающей запятой с чистым синтаксисом, меня интересуют только ошибки диапазона и точности. Извините - я пропустил эту деталь в вопросе. - person Lothar; 20.12.2009
comment
Что ж, я не знаю, насколько строго OP хотел преобразования, но да, мой код сканирования будет игнорировать конечный символ (как strtod). - person ; 21.12.2009
comment
@Nicholas Goy: strtod не вернет ошибку для конечного символа, но вы, по крайней мере, можете использовать конечный указатель, чтобы убедиться, что он указывает на ожидаемый конец строки. Код scanf не дает никаких преимуществ перед strtod. - person jamesdlin; 21.12.2009

Я хотел сказать, просто используйте код, который у вас уже есть, но присвойте результат strod() веществу с плавающей запятой, а не двойному. Но ваш код неверен по нескольким причинам.

Во-первых, вы не можете проверить errno, если не произошла ошибка. Во-вторых, strtod() не будет устанавливать errno, за исключением таких вещей, как ошибки диапазона. Если вы передадите ему недопустимый номер, например «XYZ», он не будет установлен.

Более правильное использование strtod:

char *p;
double d = strtod( "123.4xyz", & p );
if ( * p != 0 ) {
   // not a number - in this case will point at 'x'
}

Используя strtod() для чтения числа с плавающей запятой, вы можете потерять некоторую точность, но это цена, которую вы платите за использование числа с плавающей запятой - в общем, если у вас нет очень веской причины не делать этого, вы всегда должны предпочитать использование числа с плавающей запятой.

person Community    schedule 20.12.2009
comment
Да, конечно, кажется, у меня сегодня днем ​​проблемы. Спасибо. - person ; 20.12.2009
comment
Ну, лексер уже дает мне лексически верную числовую последовательность. Единственное, что мне нужно сделать, это искать ошибки диапазона. Вот почему я написал, что создаю компилятор. Я думаю, что это должно выдавать предупреждение, когда буквальное значение неверно, и компилятор может это обнаружить. - person Lothar; 20.12.2009
comment
В этом случае я бы выбрал простой путь и не поддерживал тип float, а только удваивал. поплавки не особенно полезны. - person ; 20.12.2009
comment
К сожалению, есть стандарт языка Eiffel, который я должен реализовать. И слишком много сторонних библиотек используют поплавки в своем API. - person Lothar; 21.12.2009

strtof не существует в C89, но есть в C99.

person Thomas    schedule 20.12.2009
comment
Хорошо, я забыл упомянуть компилятор: MSVC 2005. - person Lothar; 20.12.2009

Я предлагаю сначала преобразовать в double, а затем перевести в float. Если относительная разница (f-d)/f больше, чем точность числа с плавающей запятой (примерно 1e-7), то цифр больше, чем можно безопасно хранить в числе с плавающей запятой.

person Pavel Radzivilovsky    schedule 20.12.2009

В MSVC вы можете использовать _atoflt(), определенный в stdlib.h

person Hans Passant    schedule 20.12.2009