2011-09-19 10 views
17

Quiero leer y escribir valores NaN desde/en archivos de texto usando iostream y Visual C++. Al escribir un valor NaN, obtengo 1.#QNAN. Pero, al leerlo devuelve 1.0.NaN ASCII I/O con Visual C++

float nan = std::numeric_limits<float>::quiet_NaN(); 
std::ofstream os("output.txt"); 

os << nan ; 
os.close(); 

La salida es 1.#QNAN.

std::ifstream is("output.txt"); 
is >> nan ; 
is.close(); 

nan es igual a 1.0.

Solución

Por último, según lo sugerido por awoodland, se me ha ocurrido con esta solución. Elegí "nan" como una representación de cadena de un NaN. Se anulan los operadores < < y >>.

using namespace ::std; 

class NaNStream 
{ 
public: 
    NaNStream(ostream& _out, istream& _in):out(_out), in(_in){} 
    template<typename T> 
    const NaNStream& operator<<(const T& v) const {out << v;return *this;} 
    template<typename T> 
    const NaNStream& operator>>(T& v) const {in >> v;return *this;} 
protected: 
    ostream& out; 
    istream& in; 
}; 

// override << operator for float type 
template <> const NaNStream& NaNStream::operator<<(const float& v) const 
{ 
    // test whether v is NaN 
    if(v == v) 
    out << v; 
    else 
    out << "nan"; 
    return *this; 
} 

// override >> operator for float type 
template <> const NaNStream& NaNStream::operator>>(float& v) const 
{ 
    if (in >> v) 
    return *this; 

    in.clear(); 
    std::string str; 
    if (!(in >> str)) 
    return *this; 

    if (str == "nan") 
    v = std::numeric_limits<float>::quiet_NaN(); 
    else 
    in.setstate(std::ios::badbit); // Whoops, we've still "stolen" the string 

    return *this; 
} 

Un ejemplo de trabajo mínimo: un flotador finito y un NaN se escriben en un stringstream y luego se vuelven a leer.

int main(int,char**) 
{ 
    std::stringstream ss; 
    NaNStream nis(ss, ss); 
    nis << 1.5f << std::numeric_limits<float>::quiet_NaN(); 
    std::cout << ss.str() << std::endl; // OUTPUT : "1.5nan" 

    float a, b; 
    nis >> a; nis >> b; 
    std::cout << a << b << std::endl; // OUTPUT : "1.51.#QNAN" 
} 
+3

La pregunta debería ser "Cómo hacer E/S formateada con NaN". Supongo. Buena pregunta. –

+0

http://pubs.opengroup.org/onlinepubs/007904975/functions/scanf.html dice 'Si la familia de funciones fprintf() genera representaciones de cadenas de caracteres para el infinito y NaN (una entidad simbólica codificada en formato de coma flotante) para soporte IEEE Std 754-1985, la familia de funciones fscanf() los reconocerá como entrada. "para lo que sea que valga la pena". –

+0

@Moo: si eso es cierto, entonces al menos sabemos ahora que iostreams no usan 'fscanf' :-) En cualquier caso, sería ridículo confiar, ya que la representación textual de NaN varía de compilador a compilador, y probablemente también de un estado a otro. Tal vez simplemente no es posible * leer * NaN. (Ni siquiera hay un literal para ello, volviéndolo a pensar.) –

Respuesta

7

con C++ 03 se puede trabajar con bastante facilidad en torno al tema con la ayuda de una clase de ayuda y de su propio operador:

#include <iostream> 
#include <sstream> 
#include <string> 
#include <limits> 

struct FloatNaNHelper { 
    float value; 
    operator const float&() const { return value; } 
}; 

std::istream& operator>>(std::istream& in, FloatNaNHelper& f) { 
    if (in >> f.value) 
    return in; 

    in.clear(); 
    std::string str; 
    if (!(in >> str)) 
    return in; 

    // use std::transform for lowercaseness? 
    // NaN on my platform is written like this. 
    if (str == "NaN") 
    f.value = std::numeric_limits<float>::quiet_NaN(); 
    else 
    in.setstate(std::ios::badbit); // Whoops, we've still "stolen" the string 

    return in; 
} 

Esto funciona para NaN en mi plataforma con bastante sensatez, sino que también ilustra la el problema de la portabilidad inherente allí también: su biblioteca parece representarlo de manera diferente, lo que podría complicar el problema de alguna manera si usted quisiera apoyar ambos. utilicé esta prueba con él:

int main() { 
    std::istringstream in("1.0 555 NaN foo"); 
    FloatNaNHelper f1,f2,f3; 
    in >> f1 >> f2 >> f3; 
    std::cout << static_cast<float>(f1) << ", " << static_cast<float>(f2) << ", " << static_cast<float>(f3) << std::endl; 

    if (in >> f1) 
    std::cout << "OOPS!" << std::endl; 
} 

También puede cambiar la semántica de esto a algo, posiblemente, un poco más limpio:

int main() { 
    std::istringstream in("1.0 555 NaN foo"); 
    float f1,f2,f3; 
    in >> FloatNaNHelper(f1) >> FloatNaNHelper(f2) >> FloatNaNHelper(f3); 
    std::cout << f1 << ", " << f2 << ", " << f3 << std::endl; 
} 

requiere el cambio de FloatNaNNHelper:

struct FloatNaNHelper { 
    float& value; 
    explicit FloatNaNHelper(float& f) : value(f) { } 
}; 

Y el operador:

std::istream& operator>>(std::istream& in, const FloatNaNHelper& f); 
16

Al imprimir un valor float o double a un std::ostream, se usa la plantilla de clase std::num_put<> (C++ 03 §22.2.2.2). Formatea el valor como si fuera impreso por printf con uno de los %e, %E,%f, %g o %G formateadores de formato, dependiendo de los indicadores de la secuencia (Tabla 58).

Del mismo modo, cuando se introduce un valor float o double, se lee como si con la función scanf con un especificador de formato de %g (§22.2.2.1.2/5).

Entonces, la siguiente pregunta es por qué scanf no analiza correctamente 1.#QNAN. El estándar C89 no menciona los NaN en sus descripciones de las funciones fprintf y fscanf. Sí dice que la representación de los números de coma flotante no está especificada, por lo que esto no corresponde al comportamiento especificado.

C99, por otro lado, especifica el comportamiento aquí. Para fprintf (C99 §7.19.6.1/8):

A double argumento representa una infinidad se convierte en uno de los estilos [-]inf o [-]infinity - que el estilo es definido por la implementación. Un argumento double que representa un NaN se convierte en uno de los estilos [-]nan o [-]nan(n-char-sequence) - que el estilo, y el significado de cualquier n-char-secuencia, es definido por la implementación. El especificador de conversión F produce INF, INFINITY, o NAN en lugar de inf, infinity, o nan, respectivamente. 243)

fscanf se especifica para analizar el número de acuerdo con strtod(3) (C99 §7.19.6.2/12).strtod análisis sintáctico de la siguiente manera (§7.20.1.3/3):

La forma esperada de la secuencia sujeto es un signo más o menos opcional, entonces uno de la siguiente:
- una secuencia no vacía de dígitos decimales que contiene opcionalmente un carácter decimal , luego una parte exponente opcional como se define en 6.4.4.2;
- 0x o 0X, luego una secuencia no vacía de dígitos hexadecimales que contiene opcionalmente un carácter de punto decimal , luego una parte de exponente binario opcional como se define en 6.4.4.2;
- INF o INFINITY, ignorando caso
- NAN o NAN(n-char-sequenceopt), ignorando caso en la parte NAN, donde:

 
n-char-sequence: 
    digit 
    nondigit 
    n-char-sequence digit 
    n-char-sequence nondigit 
la secuencia sujeto se define como la subsecuencia inicial más larga de la cadena de entrada, comenzando con el primero no blanco -espacio de espacio, que es de la forma esperada. La secuencia del asunto no contiene caracteres si la cadena de entrada no es de la forma esperada.


Así que, después de tomar todo esto en, el resultado final es que la biblioteca estándar de C no es compatible con C99, ya que 1.#QNAN no es un resultado válido del fprintf acuerdo con lo anterior. Sin embargo, es bien sabido que el tiempo de ejecución de C de Microsoft no cumple con C99 y, por lo que yo sé, no tiene previsto cumplir pronto. Como C89 no especifica el comportamiento aquí con respecto a los NaN, no tienes suerte.

Puede intentar cambiar a otro compilador y tiempo de ejecución C (como Cygwin + GCC), pero es un martillo terriblemente grande para un clavo tan pequeño. Si realmente necesita este comportamiento, le recomendaría escribir una clase contenedora para flotantes que sea capaz de formatear y analizar correctamente los valores NaN.

+0

Muy buena respuesta! –

+0

Buena respuesta, pero cambiar a un compilador diferente no es una opción para mí. – Mourad