NaN ASCII I/O с Visual С++
Я хочу читать и записывать значения NaN из/в текстовые файлы, используя iostream и Visual С++. При написании значения NaN я получаю 1.#QNAN
. Но, читая его, выходы 1.0
.
float nan = std::numeric_limits<float>::quiet_NaN ();
std::ofstream os("output.txt");
os << nan ;
os.close();
Выходной сигнал 1.#QNAN
.
std::ifstream is("output.txt");
is >> nan ;
is.close();
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;
}
Минимальный рабочий пример: конечный float и 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"
}
Ответы
Ответ 1
С С++ 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;
}
Это работает для 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;
}
Вы также можете изменить семантику этого на что-то, возможно, немного чище:
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;
}
Требуется изменить FloatNaNNHelper
:
struct FloatNaNHelper {
float& value;
explicit FloatNaNHelper(float& f) : value(f) { }
};
И оператор:
std::istream& operator>>(std::istream& in, const FloatNaNHelper& f);
Ответ 2
При печати значения float
или double
в std::ostream
используется шаблон класса std::num_put<>
(С++ 03 §22.2.2.2). Он форматирует значение, как если бы оно было напечатано printf
с одним из спецификаторов формата %e
, %E,
%f
, %g
или %g
, в зависимости от флагов потока (Таблица 58).
Аналогично, при вводе значения float
или double
он считывает его, как если бы с помощью функции scanf
с спецификатором формата %g
(§22.2.2.1.2/5).
Итак, следующий вопрос: почему scanf
неправильно анализирует 1.#QNAN
. В стандарте C89 не упоминаются NaN в описаниях функций fprintf
и fscanf
. Он говорит, что представление чисел с плавающей запятой неуказано, поэтому это приводит к неуказанному поведению.
C99, с другой стороны, определяет здесь поведение. Для fprintf
(C99 §7.19.6.1/8):
A double
аргумент, представляющий бесконечность, преобразуется в один из стилей [-]inf
или [-]infinity
- этот стиль определяется реализацией. Аргумент double
, представляющий NaN, преобразуется в один из стилей [-]nan
или [-]nan(n-char-sequence)
- какой стиль и значение любая n- char -последовательность, определяется реализацией. Спецификатор преобразования F производит INF
, INFINITY
или NAN
вместо INF
, INFINITY
или NAN
, соответственно. 243)
fscanf
указан для анализа числа в соответствии с strtod(3)
(C99 §7.19.6.2/12). strtod
анализируется следующим образом (§7.20.1.3/3):
Ожидаемая форма субъектной последовательности - необязательный знак плюс или минус, затем один из следующее:
- непустая последовательность десятичных цифр, необязательно содержащая десятичную точку символ, затем необязательную часть экспоненты, как определено в 6.4.4.2;
- a 0x
или 0x
, то непустая последовательность шестнадцатеричных цифр, необязательно содержащих знак десятичной точки, затем необязательную двоичную часть экспоненты, как определено в 6.4.4.2;
- INF
или INFINITY
, игнорируя случай - NAN
или NAN(n-char-sequenceopt)
, игнорируя случай в части NAN, где:
n-char-sequence:
digit
nondigit
n-char-sequence digit
n-char-sequence nondigit
Последовательность субъекта определяется как самая длинная начальная подпоследовательность входной строки, начиная с первого символа небелого пробела, который имеет ожидаемую форму. Тема последовательность не содержит символов, если строка ввода не соответствует ожидаемой форме.
Итак, после всего этого, конечный результат заключается в том, что ваша стандартная библиотека C не совместима с C99, так как 1.#QNAN
не является допустимым выходом fprintf
в соответствии с вышеизложенным. Но хорошо известно, что среда исполнения Microsoft C не совместима с C99, и, насколько мне известно, она не собирается становиться совместимой в ближайшее время. Поскольку C89 не определяет поведение здесь в отношении NaN, вам не повезло.
Вы можете попробовать переключиться на другой компилятор и C runtime (например, Cygwin + GCC), но это ужасно большой молот для такого маленького гвоздя. Если вам действительно нужно это поведение, я бы рекомендовал написать класс-оболочку для float, который способен правильно форматировать и анализировать значения NaN.