c++ - float - ¿Es posible leer valores infinitos o NaN usando flujos de entrada?
limits c++ (5)
Escribe una función con una firma como esta:
std::istream & ReadDouble(std::istream & is, double & d);
En su interior, usted:
- Lee una cadena de la secuencia usando el
operator>>
- Trate de convertir la cadena a un doble usando uno de varios métodos.
std::stod
,boost::lexical_cast
, etc ... - Si la conversión se realiza correctamente, establezca el doble y devuelva la secuencia.
- Si la conversión falla, compruebe que la cadena sea igual a "inf" o "INF" o lo que sea.
- Si la prueba pasa, establezca el doble al infinito y devuelva la secuencia, de lo contrario:
- Si la prueba falla, establezca el bit de falla en el flujo y devuélvalo.
Tengo una entrada para ser leída por un flujo de archivos de entrada (por ejemplo):
-365.269511 -0.356123 -Inf 0.000000
Cuando uso std::ifstream mystream;
leer del archivo a algunos
double d1 = -1, d2 = -1, d3 = -1, d4 = -1;
(Supongamos que mystream
ya se ha abierto y el archivo es válido),
mystream >> d1 >> d2 >> d3 >> d4;
mystream
está en estado de falla. Yo esperaría
std::cout << d1 << " " << d2 << " " << d3 << " " << d4 << std::endl;
para dar salida
-365.269511 -0.356123 -1 -1
. Me gustaría que -365.269511 -0.356123 -Inf 0
lugar.
Este conjunto de datos se generó utilizando flujos de C ++. ¿Por qué no puedo hacer el proceso inverso (leer en mi salida)? ¿Cómo puedo obtener la funcionalidad que busco?
Desde MooingDuck:
#include <iostream>
#include <limits>
using namespace std;
int main()
{
double myd = std::numeric_limits<double>::infinity();
cout << myd << ''/n'';
cin >> myd;
cout << cin.good() << ":" << myd << endl;
return 0;
}
Entrada: inf
Salida:
inf
0:inf
Véase también: http://ideone.com/jVvei
También relacionado con este problema está el análisis de NaN
, aunque no doy ejemplos para ello.
Agregué a la respuesta aceptada una solución completa en ideone. También incluye la combinación de "Inf" y "nan", algunas posibles variaciones de las palabras clave que pueden provenir de otros programas, como MatLab.
Solo lee tus variables en cadena y analízalas. No puede poner la cadena en variables dobles y esperar que se muestren como una cadena, porque si funcionara, las cadenas no serían necesarias.
Algo como
string text;
double d;
while(cin >> text)
{
if(text == "Inf") //you could also add it with negative infinity
{
d = std::numeric_limits<double>::infinity();
}
else
{
d = atof(text.c_str());
}
}
Tendrá que escribir una función de extracción personalizada, ya que su implementación específica claramente no los maneja correctamente.
Actualización Proporcionó un caso de prueba simple que muestra que Boost Spirit es capaz de manejar todas las variedades de valores especiales en esta área. Vea a continuación: Boost Spirit (FTW) .
El estandar
La única información normativa en esta área que he podido encontrar está en las secciones 7.19.6.1/7.19.6.2 del estándar C99.
Lamentablemente, las secciones correspondientes del último documento estándar de C ++ (n3337.pdf) no parecen especificar el soporte para infinity
, inf
y NaN
de la misma manera. (Tal vez me esté perdiendo una nota al pie que se refiera a la especificación C99 / C11)
Los implementadores de la biblioteca.
En 2000, Apache libstdcxx recibió un informe de error que indicaba
Los
num_get<>
do_get()
la facetanum_get<>
no toman en cuenta las cadenas especiales[-]inf[inity]
y[-]nan
. La faceta informa de un error cuando encuentra tales cadenas. Consulte 7.19.6.1 y 7.19.6.2 de C99 para obtener una lista de las cadenas permitidas.
Sin embargo, la discusión posterior arrojó que (al menos con la locale
con nombre -s ) en realidad sería ilegal que una implementación analice valores especiales:
Los caracteres en la tabla de búsqueda son "0123456789abcdefABCDEF + -". El problema 221 de la biblioteca lo enmendaría a "0123456789abcdefxABCDEFX + -". "N" no está presente en la tabla de búsqueda, por lo que la etapa 2 de num_get <> :: do_get () no tiene permiso para leer la secuencia de caracteres "NaN".
Otros recursos
securecoding.cert.org establece claramente que se requiere el siguiente ''Código de Cumplimiento'' para evitar el análisis infinito o NaN . Esto implica que algunas implementaciones realmente lo admiten, suponiendo que el autor haya probado el código publicado.
#include <cmath>
float currentBalance; /* User''s cash balance */
void doDeposit() {
float val;
std::cin >> val;
if (std::isinf(val)) {
// handle infinity error
}
if (std::isnan(val)) {
// handle NaN error
}
if (val >= MaxValue - currentBalance) {
// Handle range error
}
currentBalance += val;
}
Espíritu de alza (FTW)
El siguiente ejemplo trivial tiene el resultado deseado:
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
int main()
{
const std::string input = "3.14 -inf +inf NaN -NaN +NaN 42";
std::vector<double> data;
std::string::const_iterator f(input.begin()), l(input.end());
bool ok = qi::parse(f,l,qi::double_ % '' '',data);
for(auto d : data)
std::cout << d << ''/n'';
}
Salida:
3.14
-inf
inf
nan
-nan
nan
42
Resumen / TL; DR
Me inclino a decir que C99 especifica el comportamiento de * printf / * scanf para incluir infinito y NaN . C ++ 11, lamentablemente parece no especificarlo (o incluso prohibirlo, en presencia de locales nombrados).
Edición: para evitar el uso de una estructura de envoltura alrededor de un doble, encierro un istream
dentro de una clase de envoltura.
Desafortunadamente, no puedo descubrir cómo evitar la ambigüedad creada al agregar otro método de entrada para el double
. Para la implementación a continuación, creé una estructura de envoltorio alrededor de un istream
, y la clase de envoltorio implementa el método de entrada. El método de entrada determina la negatividad y luego intenta extraer un doble. Si eso falla, comienza un análisis.
Edit: Gracias a sehe por hacerme revisar mejor las condiciones de error.
struct double_istream {
std::istream ∈
double_istream (std::istream &i) : in(i) {}
double_istream & parse_on_fail (double &x, bool neg);
double_istream & operator >> (double &x) {
bool neg = false;
char c;
if (!in.good()) return *this;
while (isspace(c = in.peek())) in.get();
if (c == ''-'') { neg = true; }
in >> x;
if (! in.fail()) return *this;
return parse_on_fail(x, neg);
}
};
La rutina de análisis fue un poco más difícil de implementar de lo que pensé al principio, pero quería evitar intentar putback
una cadena completa.
double_istream &
double_istream::parse_on_fail (double &x, bool neg) {
const char *exp[] = { "", "inf", "NaN" };
const char *e = exp[0];
int l = 0;
char inf[4];
char *c = inf;
if (neg) *c++ = ''-'';
in.clear();
if (!(in >> *c).good()) return *this;
switch (*c) {
case ''i'': e = exp[l=1]; break;
case ''N'': e = exp[l=2]; break;
}
while (*c == *e) {
if ((e-exp[l]) == 2) break;
++e; if (!(in >> *++c).good()) break;
}
if (in.good() && *c == *e) {
switch (l) {
case 1: x = std::numeric_limits<double>::infinity(); break;
case 2: x = std::numeric_limits<double>::quiet_NaN(); break;
}
if (neg) x = -x;
return *this;
} else if (!in.good()) {
if (!in.fail()) return *this;
in.clear(); --c;
}
do { in.putback(*c); } while (c-- != inf);
in.setstate(std::ios_base::failbit);
return *this;
}
Una diferencia en el comportamiento que tendrá esta rutina sobre la double
entrada predeterminada es que el carácter -
no se consume si la entrada fue, por ejemplo, "-inp"
. En caso de error, "-inp"
seguirá estando en la transmisión para double_istream
, pero para un istream
normal solo se dejará "inp"
en la transmisión.
std::istringstream iss("1.0 -NaN inf -inf NaN 1.2");
double_istream in(iss);
double u, v, w, x, y, z;
in >> u >> v >> w >> x >> y >> z;
std::cout << u << " " << v << " " << w << " "
<< x << " " << y << " " << z << std::endl;
La salida del fragmento de código anterior en mi sistema es:
1 nan inf -inf nan 1.2
Edición: Agregar un "iomanip" como clase de ayuda. Un objeto double_imanip
actuará como un interruptor cuando aparezca más de una vez en la >>
cadena.
struct double_imanip {
mutable std::istream *in;
const double_imanip & operator >> (double &x) const {
double_istream(*in) >> x;
return *this;
}
std::istream & operator >> (const double_imanip &) const {
return *in;
}
};
const double_imanip &
operator >> (std::istream &in, const double_imanip &dm) {
dm.in = ∈
return dm;
}
Y luego el siguiente código para probarlo:
std::istringstream iss("1.0 -NaN inf -inf NaN 1.2 inf");
double u, v, w, x, y, z, fail_double;
std::string fail_string;
iss >> double_imanip()
>> u >> v >> w >> x >> y >> z
>> double_imanip()
>> fail_double;
std::cout << u << " " << v << " " << w << " "
<< x << " " << y << " " << z << std::endl;
if (iss.fail()) {
iss.clear();
iss >> fail_string;
std::cout << fail_string << std::endl;
} else {
std::cout << "TEST FAILED" << std::endl;
}
La salida de lo anterior es:
1 nan inf -inf nan 1.2
inf
Edite desde Drise: hice algunas ediciones para aceptar variaciones como Inf y nan que no se incluyeron originalmente. También lo hice en una demostración compilada, que se puede ver en http://ideone.com/qIFVo .