开发者

NaN ASCII I/O with Visual C++

开发者 https://www.devze.com 2023-04-05 19:32 出处:网络
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 .

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


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. A double 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 produces INF, INFINITY, or NAN instead of inf, infinity, or nan, 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;
— a 0x or 0X, 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 or INFINITY, ignoring case
NAN or NAN(n-char-sequenceopt), ignoring case in the NAN part, where:

n-char-sequence:
    digit
    nondigit
    n-char-sequence digit
    n-char-sequence nondigit
The 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.


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);
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号