问题
I want to read and write NaN values from/into text files using iostream and Visual C++. When writing a NaN value, i get 1.#QNAN
. But, reading it back outputs 1.0
.
float nan = std::numeric_limits<float>::quiet_NaN ();
std::ofstream os("output.txt");
os << nan ;
os.close();
The output is 1.#QNAN
.
std::ifstream is("output.txt");
is >> nan ;
is.close();
nan
equals 1.0
.
Solution
Finally, as suggested by awoodland, I've come up with this solution. I chose "nan" as a string representation of a NaN. Both << and >> operators are overridden.
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;
}
A minimal working example: a finite float and a NaN are written into a stringstream and then read back.
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"
}
回答1:
With C++03 you can fairly easily work around the issue with the aid of a helper class and your own operator:
#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;
}
This works for NaN on my platform quite sensibly, but also illustrates the portability problem inherent there too - your library seems to represent it differently, which could complicate the issue somewhat if you wanted to support both. I used this test with it:
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;
}
You can also change the semantics of this to something possibly a little cleaner:
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;
}
Requires the changing FloatNaNNHelper
:
struct FloatNaNHelper {
float& value;
explicit FloatNaNHelper(float& f) : value(f) { }
};
And the operator:
std::istream& operator>>(std::istream& in, const FloatNaNHelper& f);
回答2:
When printing a float
or double
value to a std::ostream
, the class template std::num_put<>
is used (C++03 §22.2.2.2). It formats the value as if printed by printf
with one of the %e
, %E,
%f
, %g
, or %G
format specifiers, depending on the stream's flags (Table 58).
Likewise, when inputting a float
or double
value, it reads it as if with the scanf
function with a format specifier of %g
(§22.2.2.1.2/5).
So, the next question is why scanf
does not properly parse 1.#QNAN
. The C89 standard does not mention NaNs in its descriptions of both the fprintf
and fscanf
functions. It does say that the representation of floating-point numbers is unspecified, so this falls unspecified behavior.
C99, on the other hand, does specify the behavior here. For fprintf
(C99 §7.19.6.1/8):
A
double
argument representing an infinity is converted in one of the styles[-]inf
or[-]infinity
— which style is implementation-defined. Adouble
argument representing a NaN is converted in one of the styles[-]nan
or[-]nan(n-char-sequence)
— which style, and the meaning of any n-char-sequence, is implementation-defined. The F conversion specifier producesINF
,INFINITY
, orNAN
instead ofinf
,infinity
, ornan
, respectively.243)
fscanf
is specified to parse the number according to strtod(3) (C99 §7.19.6.2/12). strtod
parses as follows (§7.20.1.3/3):
The expected form of the subject sequence is an optional plus or minus sign, then one of the following:
— a nonempty sequence of decimal digits optionally containing a decimal-point character, then an optional exponent part as defined in 6.4.4.2;
— a0x
or0X
, then a nonempty sequence of hexadecimal digits optionally containing a decimal-point character, then an optional binary exponent part as defined in 6.4.4.2;
—INF
orINFINITY
, ignoring case
—NAN
orNAN(n-char-sequenceopt)
, ignoring case in the NAN part, where:n-char-sequence: digit nondigit n-char-sequence digit n-char-sequence nondigitThe subject sequence is defined as the longest initial subsequence of the input string, starting with the first non-white-space character, that is of the expected form. The subject sequence contains no characters if the input string is not of the expected form.
So, after taking all that in, the end result is that your C standard library is not C99-compliant, since 1.#QNAN
is not a valid output of fprintf
according to the above. But, it's well-known that Microsoft's C runtime is not C99-compliant, and it doesn't plan to become compliant any time soon, as far as I'm aware. Since C89 does not specify the behavior here with respect to NaNs, you're out of luck.
You could try switching to a different compiler and C runtime (such as Cygwin+GCC), but that's an awfully big hammer for such a small nail. If you really need this behavior, I'd recommend writing a wrapper class for floats that is capable of correctly formatting and parsing NaN values.
来源:https://stackoverflow.com/questions/7477978/nan-ascii-i-o-with-visual-c