Есть ли способ проверить, может ли строка быть float в C?
Проверка того, может ли это быть int достаточно просто - просто проверьте, что каждая цифра находится между '0'
и '9'
. Но поплавок сложнее. Я нашел этот, но ни один из ответов не работает. Рассмотрим этот фрагмент кода, основанный на верхнем (принятом) ответе:
float f;
int ret = sscanf("5.23.fkdj", "%f", &f);
printf("%d", ret);
1
будет напечатан.
Еще один ответ предложил использовать strpbrk
, чтобы проверить наличие определенных незаконных символов, но это не сработает, потому что 5fin7
не будет законным, но inf
будет.
Еще один ответ предложил проверить вывод strtod. Но учтите следующее:
char *number = "5.53 garbanzo beans"
char *foo;
strtod(number, &foo);
printf("%d", isspace(*foo) || *foo == '\0'));
Он напечатает 1
. Но я не хочу полностью удалить вызов isspace
, потому что " 5.53 "
должен быть допустимым числом.
Есть ли хороший, элегантный, идиоматический способ делать то, что я пытаюсь сделать?
Ответы
Ответ 1
Первый ответ должен работать, если вы объедините его с %n
, который представляет собой количество прочитанных символов:
int len;
float ignore;
char *str = "5.23.fkdj";
int ret = sscanf(str, "%f %n", &ignore, &len);
printf("%d", ret==1 && !str[len]);
!str[len]
выражение будет ложным, если строка содержит символы, не включенные в float
. Также обратите внимание на пробел после %f
, чтобы задать конечные пробелы.
Демо
Ответ 2
Вы можете проверить, если - после чтения значения с помощью strtod
- остаток состоит только из белых пробелов. Функция strspn
может помочь здесь, и вы даже можете определить "свой личный набор белых пространств", чтобы рассмотреть:
int main() {
char *number = "5.53 garbanzo beans";
char *foo;
double d = strtod(number, &foo);
if (foo == number) {
printf("invalid number.");
}
else if (foo[strspn(foo, " \t\r\n")] != '\0') {
printf("invalid (non-white-space) trailing characters.");
}
else {
printf("valid number: %lf", d);
}
}
Ответ 3
Есть ли способ проверить, может ли строка быть float?
Проблема с подходом sscanf(...,"%f")
заключается в переполнении, что является UB. Тем не менее, он обычно обрабатывается красиво.
Вместо этого используйте float strtof(const char * restrict nptr, char ** restrict endptr);
int float_test(const char *s) {
char *ednptr;
errno = 0;
float f = strtof(s, &endptr);
if (s == endptr) {
return No_Conversion;
}
while (isspace((unsigned char) *endptr)) { // look past the number for junk
endptr++;
}
if (*endptr) {
return Extra_Junk_At_End;
}
// If desired
// Special cases with with underflow not considered here.
if (errno) {
return errno; // likely under/overflow
}
return Success;
}
Ответ 4
Этот код тесно связан с ответом dasblinkenlight. Я предлагаю это как пищу для размышлений. Некоторые из ответов, которые он дает, могут быть не такими, какие вы хотели.
#include <stdio.h>
#include <string.h>
static void test_float(const char *str)
{
int len;
float dummy = 0.0;
if (sscanf(str, "%f %n", &dummy, &len) == 1 && len == (int)strlen(str))
printf("[%s] is valid (%.7g)\n", str, dummy);
else
printf("[%s] is not valid (%.7g)\n", str, dummy);
}
int main(void)
{
test_float("5.23.fkdj"); // Invalid
test_float(" 255. "); // Valid
test_float("255.123456"); // Valid
test_float("255.12E456"); // Valid
test_float(" .255 "); // Valid
test_float(" Inf "); // Valid
test_float(" Infinity "); // Valid
test_float(" Nan "); // Valid
test_float(" 255 "); // Valid
test_float(" 0x1.23P-24 "); // Valid
test_float(" 0x1.23 "); // Valid
test_float(" 0x123 "); // Valid
test_float("abc"); // Invalid
test_float(""); // Invalid
test_float(" "); // Invalid
return 0;
}
Тестирование на Mac под управлением macOS Sierra 10.12.6 с использованием GCC 7.1.0 в качестве компилятора, я получаю вывод:
[5.23.fkdj] is not valid (5.23)
[ 255. ] is valid (255)
[255.123456] is valid (255.1235)
[255.12E456] is valid (inf)
[ .255 ] is valid (0.255)
[ Inf ] is valid (inf)
[ Infinity ] is valid (inf)
[ Nan ] is valid (nan)
[ 255 ] is valid (255)
[ 0x1.23P-24 ] is valid (6.775372e-08)
[ 0x1.23 ] is valid (1.136719)
[ 0x123 ] is valid (291)
[abc] is not valid (0)
[] is not valid (0)
[ ] is not valid (0)
Шестнадцатеричные числа, вероятно, будут особенно проблематичными. Различные формы бесконечности и не-число тоже могут быть неприятными. И один пример с экспонентой (255.12E456
) переполняет float
и генерирует бесконечность - это действительно нормально?
Большинство проблем, возникающих здесь, - это определение - то есть, как вы определяете, что вы хотите быть приемлемым. Но обратите внимание, что strtod()
будет принимать все допустимые строки (и несколько недопустимых, но другое тестирование выявит эти проблемы).
Очевидно, что тестовый код можно было бы пересмотреть, чтобы использовать массив структуры, содержащий строку и желаемый результат, и это можно было бы использовать для повторения показанных тестовых примеров и любых добавленных дополнений.
Приведение результата strlen()
исключает предупреждение о компиляции (ошибка, потому что я компилирую с помощью -Werror
) - comparison between signed and unsigned integer expressions [-Werror=sign-compare]
. Если ваши строки достаточно длинны, чтобы результат от strlen()
переполнял подписанный int
, у вас возникли другие проблемы, притворяясь, что они действительные значения. OTOH, вы можете поэкспериментировать с 500 цифрами после десятичной точки - это действительно.
Этот код отмечает комментарии, сделанные для ответа dasblinkenlight:
Ответ 5
Это вариация на фрагмент кода, опубликованный dasblinkenlight, который немного проще и эффективнее, поскольку strlen(str)
может быть дорогостоящим:
const char *str = "5.23.fkdj";
float ignore;
char c;
int ret = sscanf(str, "%f %c", &ignore, &c);
printf("%d", ret == 1);
Объяснение: sscanf()
возвращает 1
тогда и только тогда, когда был преобразован float, за которым следует дополнительное пробел и другой символ.
Ответ 6
Может быть, это? Не очень хорошо, но может выполнять эту работу. Возвращает -1 при ошибке 0 без выполненных преобразований и > 0 с установленными флагами преобразованных чисел.
#define INT_CONVERTED (1 << 0)
#define FLOAT_CONVERTED (1 << 1)
int ReadNumber(const char *str, double *db, int *in)
{
int result = (str == NULL || db == NULL || in == NULL) * -1;
int len = 0;
char *tmp;
if (result != -1)
{
tmp = (char *)malloc(strlen(str) + 1);
strcpy(tmp, str);
for (int i = strlen(str) - 1; i >= 0; i--)
{
if (isspace(tmp[i]))
{
tmp[i] = 0;
continue;
}
break;
}
if (strlen(tmp))
{
if (sscanf(tmp, "%lf%n", db, &len) == 1 && strlen(tmp) == len)
{
result |= FLOAT_CONVERTED;
}
if (sscanf(tmp, "%d%n", in, &len) == 1 && strlen(tmp) == len)
{
result |= INT_CONVERTED;
}
}
free(tmp);
}
return result;
}