Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NaN ASCII I/O with Visual C++

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"
}
like image 602
Mourad Avatar asked Sep 19 '11 22:09

Mourad


People also ask

What is ASCII C++?

American Standard Code for Information Interchange. ASCII Character Set. A char variable in C++ is a one-byte memory location where a single character value can be stored. Because one byte can hold values between 0 and 255 that means there are up to 256 different characters in the ASCII character set.

How do you write Ascii code?

To insert an ASCII character, press and hold down ALT while typing the character code. For example, to insert the degree (º) symbol, press and hold down ALT while typing 0176 on the numeric keypad. You must use the numeric keypad to type the numbers, and not the keyboard.


1 Answers

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.

like image 88
Adam Rosenfield Avatar answered Oct 01 '22 23:10

Adam Rosenfield