使用Visual C++的NaN ASCII I/O.

Mou*_*rad 17 c++ floating-point iostream visual-c++

我想使用iostream和Visual C++从/向文本文件中读取和写入NaN值.当写NaN值时,我得到1.#QNAN.但是,读回来输出1.0.

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

os << nan ;
os.close();
Run Code Online (Sandbox Code Playgroud)

输出是1.#QNAN.

std::ifstream is("output.txt");
is >> nan ;
is.close();
Run Code Online (Sandbox Code Playgroud)

nan等于1.0.

最后,正如awoodland所建议的那样,我想出了这个解决方案.我选择"nan"作为NaN的字符串表示.<<和>>运算符都被覆盖.

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;
}
Run Code Online (Sandbox Code Playgroud)

一个最小的工作示例:有限浮点数和NaN被写入字符串流然后回读.

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"
}
Run Code Online (Sandbox Code Playgroud)

Ada*_*eld 16

将a floatdouble值打印到a时std::ostream,使用类模板std::num_put<>(C++03§22.2.2.2).它格式值,好像通过印刷printf与所述的一个%e,%E, %f,%g,或%G格式说明,这取决于流的标志(表58).

同样,在输入a floatdoublevalue时,它会像scanf使用格式说明符为%g(§22.2.2.1.2/ 5)的函数一样读取它.

那么,接下来的问题是为什么scanf没有正确解析1.#QNAN.C89标准在其对功能fprintffscanf功能的描述中没有提到NaN .它确实表示浮点数的表示是未指定的,因此这属于未指定的行为.

另一方面,C99确实指定了这里的行为.对于fprintf(C99§7.19.6.1/ 8):

double表示无穷大的参数在其中一个样式中转换 [-]inf[-]infinity- 哪个样式是实现定义的.double表示NaN 的 参数在其中一个样式中转换 [-]nan或- 哪个样式和任何n-char序列的含义是实现定义的.在F转换说明产生,或代替的,或分别.243)[-]nan(n-char-sequence)INFINFINITYNANinfinfinitynan

fscanf指定根据strtod(3)(C99§7.19.6.2/ 12)解析数字. strtod解析如下(§7.20.1.3/ 3):

主题序列的预期形式是可选的加号或减号,然后是以下之一:
- 非空的十进制数字序列(可选地包含小数点字符),然后是6.4.4.2中定义的可选指数部分;
- 一个0x或者0X一个非空的十六进制数字序列,可选地包含一个小数点字符,然后是6.4.4.2中定义的可选二进制指数部分;
- INF或者INFINITY,忽略大小写
- NAN或忽略NAN部分中的大小写,其中:NAN(n-char-sequenceopt)

n-char-sequence:
    digit
    nondigit
    n-char-sequence digit
    n-char-sequence nondigit
主题序列被定义为输入字符串的最长初始子序列,从第一个非空白字符开始,即预期形式.如果输入字符串不是预期的形式,则主题序列不包含任何字符.


因此,在完成所有操作后,最终结果是您的C标准库不符合C99,因为根据上述1.#QNAN内容不是有效输出fprintf.但是,众所周知,微软的C运行时不符合C99标准,而且据我所知,它并不打算很快成为合规用户.由于C89没有指定NaNs的行为,所以你运气不好.

您可以尝试切换到不同的编译器和C运行时(例如Cygwin + GCC),但这对于如此小的钉子来说是一个非常大的锤子.如果你真的需要这种行为,我建议为浮点数编写一个包装类,它能够正确地格式化和解析NaN值.


Fle*_*exo 7

使用C++ 03,您可以借助辅助类和您自己的运算符轻松解决此问题:

#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;
}
Run Code Online (Sandbox Code Playgroud)

这非常适合我的平台上的NaN,但也说明了其中固有的可移植性问题 - 您的库似乎以不同的方式表示它,如果您想支持两者,这可能会使问题复杂化.我用它测试了这个测试:

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;
}
Run Code Online (Sandbox Code Playgroud)

您还可以将此语义更改为可能更清晰的东西:

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;
}
Run Code Online (Sandbox Code Playgroud)

需要改变FloatNaNNHelper:

struct FloatNaNHelper {
  float& value;
  explicit FloatNaNHelper(float& f) : value(f) { }
};
Run Code Online (Sandbox Code Playgroud)

和运营商:

std::istream& operator>>(std::istream& in, const FloatNaNHelper& f);
Run Code Online (Sandbox Code Playgroud)