c++ climits ¿Es posible leer valores infinitos o NaN usando flujos de entrada?



dbl_max c++ (4)

Escribe una función con una firma como esta:

std::istream & ReadDouble(std::istream & is, double & d);

En su interior, usted:

  1. Lee una cadena de la secuencia usando el operator>>
  2. Trate de convertir la cadena a un doble usando uno de varios métodos. std::stod , boost::lexical_cast , etc ...
  3. Si la conversión se realiza correctamente, establezca el doble y devuelva la secuencia.
  4. Si la conversión falla, compruebe que la cadena sea igual a "inf" o "INF" o lo que sea.
  5. Si la prueba pasa, establezca el doble al infinito y devuelva la secuencia, de lo contrario:
  6. Si la prueba falla, establezca el bit de falla en el flujo y devuélvalo.

https://ffff65535.com

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());
    }
}

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 faceta num_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 &in;

    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 = &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 .





numeric-limits