Si estoy leyendo números de tipo double de stdin, ¿cómo puedo verificar si los números que se están leyendo son realmente válidos (que los números son de hecho un doble)?
Puedes usar strtod
. Compruebe si el resultado es cero y, posteriormente, si endptr == nptr
, de acuerdo con la página del manual:
Si no se realiza ninguna conversión, se devuelve cero y el valor de nptr se almacena en la ubicación a la que hace referencia endptr.
Algo como esto:
char input[50]; char * end; double result = 0; fgets(input, sizeof input, stdin); errno = 0; result = strtod(input, &end); if(result == 0 && (errno != 0 || end == input)){ fprintf(stderr, "Error: input is not a valid double\n"); exit(EXIT_FAILURE); }
EDITAR parece que hay una pequeña discrepancia entre el estándar y la página de manual. La página del manual dice que endptr == nptr
cuando no se realiza una conversión, mientras que el estándar parece implicar que esto no es necesariamente el caso. Peor aún, dice que en caso de que no haya conversión, el errno
puede configurarse en EINVAL
. Editado el código de ejemplo para comprobar errno
también.
Alternativamente, se podría usar sscanf
(preferido sobre scanf
), junto con fgets
:
/* just fgetsed input */ if(sscanf(input, "%lf", &result) != 1){ fprintf(stderr, "Error: input is not a valid double\n"); exit(EXIT_FAILURE); }
Además, no olvide verificar el valor de retorno de fgets
para NULL
, ¡en caso de que falle!
Ni strtod
simple ni sscanf
son suficientes para distinguir casos como 1,5
o 1blah
de 1.0
deseado. Todos estos resultarán en 1.0
. La razon es que
Las funciones
strtod()
,strtof()
ystrtold()
convierten la parte inicial de la cadena apuntada por nptr a representación doble doble, flotante y larga, respectivamente.
Para asegurarte de que la cadena completa fuera un literal doble válido, usa strtod
así:
#include #include #include ... char *endptr; errno = 0; double result = strtod(input, &endptr); if (errno != 0 || *endptr != '\0') { fprintf("the value could not be represented as a double exactly\n"); }
El errno
se establecerá si el valor no se puede representar ( ERANGE
). Además, el end
apuntará al primer carácter no convertido. Si no se ha establecido la configuración regional, al analizar 1,5
o 1,5
1blah
, endptr
apuntará al segundo carácter. Si toda la cadena se analizó correctamente como una constante doble, *endptr
apuntará a la terminación ‘\ 0’ .
Tenga en cuenta que errno
debe establecerse en cero antes de llamar a la función; de lo contrario, conservará el valor de una llamada de función fallida anterior.
¿Cómo podemos verificar si una cadena de entrada es un doble válido?
Empezar con
strtod()
para double
,
strtof()
para float
y
strtold()
para long double
.
double strtod(const char * restrict nptr, char ** restrict endptr);
Las funciones
strtod
, … convierten la parte inicial de la cadena apuntada pornptr
adouble
….Un puntero a la cadena final se almacena en el objeto apuntado por
endptr
, siempre queendptr
no sea un puntero nulo.C11dr §7.22.1.3 2 y 5
Código simplificado para verificar flojamente la validez. No se queja de desbordamiento / desbordamiento ni texto extra.
// Return true on valid bool valid_string_to_double(const char *s) { char *end; strtod(s, &end); return s != end; }
Los desafíos de usar strto*()
incluyen: errno == RANGE
en el desbordamiento aritmético y quizás en el subdesbordamiento . El valor de retorno en el desbordamiento solo se especifica en el modo de redondeo predeterminado. Ese valor es HUGE_VAL
que puede ser un infinito o un gran número. El valor de retorno en el flujo insuficiente es la implementación definida. Se sabe que errno
se ha establecido en otros valores distintos de cero en condiciones no especificadas por la especificación C. Se permite el espacio en blanco inicial, no se considera el espacio en blanco al final.
Función de ejemplo que busca 1) conversión, 2) espacio adicional, 3) over / underflow. No solo devuelve una indicación válida, sino que también aborda el valor de la conversión y el estado de errno
posteriormente.
// Return 0 on success // Return non-0 on error, adjust these values as needed - maybe as an `enum`? int convert_string_to_double(double *y, const char *s) { char *end; errno = 0; *y = strtod(s, &end); if (s == end) { return 1; // Failed: No conversion, *y will be 0 } // This may/may not constitute an error - adjust per coding goals // Too great or too small (yet not exactly 0.0) if (errno == ERANGE) { if (fabs(*y) > 1.0) { return 2; // Overflow } // In the case of too small, errno _may_ be set. See §7.22.1.3 10. // For high consistency, return 0.0 and/or clear errno and/or return success. // *y = 0.0; errno = 0; } // What to do if the remainder of the string is not \0? // Since leading whitespace is allowed, // let code be generous and tolerate trailing whitespace too. while (isspace((unsigned char) *end)) { end++; } if (*end) { return 3; // Failed: Extra non-white-space junk at the end. } return 0; // success }
Si el resultado se desborda (7.12.1), las funciones devuelven un valor cuya magnitud no es mayor que el número positivo normalizado más pequeño en el tipo de retorno; si
errno
adquiere el valorERANGE
está definido por la implementación. C11dr §7.22.1.3 10
Una consideración incluye el valor de errno
después de que se realiza esta función. La especificación C solo especie errno == ERANGE
para strtod()
, sin embargo, se han conocido varias implementaciones para establecer errno
en otros valores por otras razones, incluyendo “sin conversión”. Código podría borrar errno
excepto cuando ERANGE
para alta consistencia.
Podrías usar la función atof estándar. Devuelve 0 en caso de error, y podría probar si la cadena era 0 de antemano.