2011-09-19 13 views
17

Chcę odczytać i zapisać wartości NaN z/do plików tekstowych za pomocą iostream i Visual C++. Podczas pisania wartości NaN otrzymuję 1.#QNAN. Ale, czytając to z powrotem, wychodzi 1.0.NaN ASCII I/O z Visual C++

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

os << nan ; 
os.close(); 

Dane wyjściowe to 1.#QNAN.

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

nan równa 1.0.

Rozwiązanie

Wreszcie, zgodnie z sugestią awoodland, mam wymyślić tego rozwiązania. Wybrałem "nan" jako ciąg znaków na NaN. Zarówno operatory < < jak i >> są nadpisywane.

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

Minimalny przykład działania: skończony float i NaN są zapisywane w strumieniu, a następnie odczytywane.

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

Pytanie powinno brzmieć: "Jak sformatować wejścia/wyjścia z NaN". Przypuszczam. Dobre pytanie. –

+0

http://pubs.opengroup.org/onlinepubs/007904975/functions/scanf.html mówi: "Jeśli rodzina funkcji fprintf() generuje reprezentacje ciągów znaków dla nieskończoności i NaN (symboliczna encja zakodowana w formacie zmiennoprzecinkowym) do obsługują IEEE Std 754-1985, funkcje fscanf() funkcji powinny je rozpoznawać jako dane wejściowe. "niezależnie od tego, co jest warte. –

+0

@Moo: jeśli to prawda, to przynajmniej wiemy teraz, że iostreams nie używają 'fscanf' :-) W każdym razie byłoby śmiesznie na czym polegać, ponieważ tekstowa reprezentacja NaN różni się od kompilatora do kompilator i prawdopodobnie również ze stanu. Może po prostu nie można * przeczytać * NaN. (Nie ma nawet żadnego dosłownego, żeby o tym pomyśleć.) –

Odpowiedz

7

z C++ 03 można dość łatwo obejść ten problem przy pomocy klasy pomocnika i własnego operatora:

#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; 
} 

Działa to dla NaN na mojej platformie całkiem sensownie, ale także ilustruje problem przenośności jest nieodłączny - twoja biblioteka wydaje się reprezentować to inaczej, co może nieco komplikować problem, jeśli chcesz wspierać oba. użyłem tego testu z nim:

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

Można również zmienić semantykę to coś ewentualnie trochę czystsze:

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

Wymaga zmieniając FloatNaNNHelper:

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

I operator:

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

Podczas drukowania float lub double wartości do std::ostream, używany jest szablon klasy C++ std::num_put<> (03 §22.2.2.2). Formatuje wartość tak, jakby była wydrukowana przez printf ze specyfikatorem formatu, ,, %g lub , w zależności od flag strumienia (Tabela 58).

Podobnie, podczas wprowadzania do float lub double wartość odczytuje się jakby z funkcją scanf się od określenia formatu %g (§22.2.2.1.2/5).

Następne pytanie brzmi, dlaczego scanf nie analizuje poprawnie 1.#QNAN. Standard C89 nie wspomina o NaN w opisach funkcji fprintf i fscanf. Mówi się, że reprezentacja liczb zmiennoprzecinkowych jest nieokreślona, ​​a więc nieokreślone zachowanie.

C99, z drugiej strony, określa zachowanie tutaj. Dla fprintf (C99 §7.19.6.1/8):

double argumentu przedstawiający nieskończoność przekształca się w jedną z postaci [-]inf lub [-]infinity - jaki styl jest realizacja określone. double argumentu stanowiących NaN przekształca się w jedną z postaci [-]nan lub [-]nan(n-char-sequence) - jaki styl i znaczenie każdy n char sekwencji jest realizacja określone. F Konwersja specyfikator wytwarza INF, INFINITY lub NAN zamiast inf, infinity lub nan, odpowiednio. 243)

fscanf podano przeanalizować liczbę według strtod(3) §7.19.6.2 (C99/12).strtod analizuje się następująco (§7.20.1.3/3):

Oczekiwany postać sekwencji odniesienia jest opcjonalnie odpowiednio plus lub minus, a następnie jeden z następujących :
- to niepusty sekwencja cyfr opcjonalnie zawierający znak dziesiętny , a następnie opcjonalną część wykładniczą zdefiniowaną w 6.4.4.2;
- a 0x lub 0X, a następnie niepustą sekwencję cyfr szesnastkowych, opcjonalnie zawierającą znak dziesiętny o numerze , a następnie opcjonalną binarną część wykładniczą zdefiniowaną w 6.4.4.2;
- INF lub INFINITY ignorując Case
- NAN lub NAN(n-char-sequenceopt) ignorując przypadku w części szlaku, gdzie:

 
n-char-sequence: 
    digit 
    nondigit 
    n-char-sequence digit 
    n-char-sequence nondigit 
badanej sekwencji określa się jako najdłuższy początkowej części danego łańcucha wejściowego począwszy od pierwszego nie-białych -space, czyli o spodziewanej formie. Sekwencja nie zawiera żadnych znaków, jeśli ciąg wejściowy nie ma oczekiwanej postaci.


Więc po zrobieniu wszystkiego, w, końcowy wynik jest to, że biblioteki standardowej C nie jest zgodny z C99, ponieważ 1.#QNAN nie jest ważne wyjście fprintf zgodnie z powyższym. Ale wiadomo, że środowisko wykonawcze C firmy Microsoft nie jest zgodne z C99, i nie planuje wkrótce uzyskać zgodności, o ile wiem. Ponieważ C89 nie określa zachowania tutaj w odniesieniu do NaN, nie masz szczęścia.

Możesz spróbować przełączyć się do innego kompilatora i środowiska wykonawczego C (na przykład Cygwin + GCC), ale jest to bardzo duży młotek do takiego małego paznokcia. Jeśli naprawdę potrzebujesz tego zachowania, polecam napisanie klasy wrapper dla obiektów typu float, które są w stanie poprawnie formatować i analizować wartości NaN.

+0

Bardzo ładna odpowiedź! –

+0

Dobra odpowiedź, ale przełączanie na inny kompilator nie jest dla mnie opcją. – Mourad