парсинг строк с модификаторами значения (‘-‘, ‘%’) в конце

Я пытаюсь разобраться с разбором.

У меня есть некоторые данные, которые поступают в de-de формат с дополнительной информацией в конце строки.

Мне удалось получить правильную часть, но я изо всех сил в получении - а также % разобрали правильно. Я читаю на codecvt но я не понимаю тему.

Вот отражение того, что я понимаю, и пример того, что мне нужно сделать.

#include <string>
#include <locale>
#include <iostream>
#include <sstream>

using namespace std;

#define EXPECT_EQ(actual, expected) { \
if (actual != expected) \
{ \
cout << "expected " << #actual << " to be " << expected << " but was " << actual << endl; \
} \
}

double parse(wstring numstr)
{
double value;
wstringstream is(numstr);
is.imbue(locale("de-de"));
is >> value;
return value;
}

int main()
{
EXPECT_EQ(parse(L"123"), 123); //ok
EXPECT_EQ(parse(L"123,45"), 123.45); //ok
EXPECT_EQ(parse(L"1.000,45"), 1000.45); //ok
EXPECT_EQ(parse(L"2,390%"), 0.0239); //% sign at the end
EXPECT_EQ(parse(L"1.234,56-"), -1234.56); //- sign at the end
}

Выход:

expected parse(L"2,390%") to be 0.0239 but was 2.39
expected parse(L"1.234,56-") to be -1234.56 but was 1234.56

Как я могу наполнить свой поток так, чтобы он читал - а также % знак, как мне это нужно?

3

Решение

Я бы занялся этим в лоб: давайте разберемся с разбором здесь.

В конце концов, вы все равно это напишете, поэтому я забуду о необходимости сначала создать (дорогой) поток строк.

Оружие выбора: Повышение духа

Заметка,

  • Я анализирую строку, используя ее итераторы напрямую. Мой код довольно
    универсальный тип используемого числа с плавающей запятой.

  • Вы можете в значительной степени искать заменить double например,
    boost::multiprecision::cpp_dec_float (или сделайте это шаблоном
    аргумент) и будет разбор. Потому что я предсказываю, что вам нужно было разобрать
    десятичный числа с плавающей запятой, а не двоичный числа с плавающей запятой. Вы теряете точность в конверсии.

ОБНОВИТЬ: расширенный образец Жить на Колиру

Простая грамматика

По сути, грамматика действительно проста:

if (parse(numstr.begin(), numstr.end(), mynum >> matches['-'] >> matches['%'],
value, sign, pct))
{
if (sign) value = -value;
if (pct)  value /= 100;

return value;
}

Там у вас есть это. Конечно, нам нужно определить mynum так что разбирает неподписанный реальные цифры, как и ожидалось:

using namespace qi;
real_parser<double, de_numpolicy<double> > mynum;

Магия: real_policies<>

Документация в значительной степени объясняет, как настроить разбор реальных чисел с помощью real_policies. Вот политика, которую я придумал:

template <typename T>
struct de_numpolicy : qi::ureal_policies<T>
{
//  No exponent
template <typename It>                static bool parse_exp(It&, It const&)          { return false; }
template <typename It, typename Attr> static bool parse_exp_n(It&, It const&, Attr&) { return false; }

//  Thousands separated numbers
template <typename It, typename Attr>
static bool parse_n(It& first, It const& last, Attr& attr)
{
qi::uint_parser<unsigned, 10, 1, 3> uint3;
qi::uint_parser<unsigned, 10, 3, 3> uint3_3;

if (parse(first, last, uint3, attr)) {
for (T n; qi::parse(first, last, '.' >> uint3_3, n);)
attr = attr * 1000 + n;

return true;
}

return false;
}

template <typename It>
static bool parse_dot(It& first, It const& last) {
if (first == last || *first != ',')
return false;
++first;
return true;
}
};

Полная демонстрация

Жить на Колиру

#include <boost/spirit/include/qi.hpp>
#include <iostream>#define EXPECT_EQ(actual, expected) { \
double v = (actual); \
if (v != expected) \
{ \
std::cout << "expected " << #actual << " to be " << expected << " but was " << v << std::endl; \
} \
}

namespace mylib {
namespace qi = boost::spirit::qi;

template <typename T>
struct de_numpolicy : qi::ureal_policies<T>
{
//  No exponent
template <typename It>                static bool parse_exp(It&, It const&)          { return false; }
template <typename It, typename Attr> static bool parse_exp_n(It&, It const&, Attr&) { return false; }

//  Thousands separated numbers
template <typename It, typename Attr>
static bool parse_n(It& first, It const& last, Attr& attr)
{
qi::uint_parser<unsigned, 10, 1, 3> uint3;
qi::uint_parser<unsigned, 10, 3, 3> uint3_3;

if (parse(first, last, uint3, attr)) {
for (T n; qi::parse(first, last, '.' >> uint3_3, n);)
attr = attr * 1000 + n;

return true;
}

return false;
}

template <typename It>
static bool parse_dot(It& first, It const& last) {
if (first == last || *first != ',')
return false;
++first;
return true;
}
};

template<typename Char, typename CharT, typename Alloc>
double parse(std::basic_string<Char, CharT, Alloc> const& numstr)
{
using namespace qi;
real_parser<double, de_numpolicy<double> > mynum;

double value;
bool sign, pct;

if (parse(numstr.begin(), numstr.end(), mynum >> matches['-'] >> matches['%'],
value, sign, pct))
{
// std::cout << "DEBUG: " << std::boolalpha << " '" << numstr << "' -> (" << value << ", " << sign << ", " << pct << ")\n";
if (sign) value = -value;
if (pct)  value /= 100;

return value;
}

assert(false); // TODO handle errors
}

} // namespace mylib

int main()
{
EXPECT_EQ(mylib::parse(std::string("123")),       123);      // ok
EXPECT_EQ(mylib::parse(std::string("123,45")),    123.45);   // ok
EXPECT_EQ(mylib::parse(std::string("1.000,45")),  1000.45);  // ok
EXPECT_EQ(mylib::parse(std::string("2,390%")),    0.0239);   // %  sign at the end
EXPECT_EQ(mylib::parse(std::string("1.234,56-")), -1234.56); // -  sign at the end
}

Если вы раскомментируете строку «DEBUG», она напечатает:

DEBUG:  '123' -> (123, false, false)
DEBUG:  '123,45' -> (123.45, false, false)
DEBUG:  '1.000,45' -> (1000.45, false, false)
DEBUG:  '2,390%' -> (2.39, false, true)
DEBUG:  '1.234,56-' -> (1234.56, true, false)
2

Другие решения

codecvt Фасет не то место, чтобы посмотреть здесь. codecvt Фасет предназначен только для преобразования внешнего представления символа во внутреннее представление того же символа (например, UTF-8 в файле, UTF-32 / UCS-4 внутри).

Для разбора чисел, как это, вы ищете num_get фаска. Основная идея заключается в том, что вы создадите класс, производный от std::num_get что переопределяет do_get (по крайней мере) для типов номеров, которые вам небезразличны.

В типичном случае вы выполняете «реальную» реализацию только для нескольких типов (например, long long и long double) и передаете функции для всех меньших типов, а затем преобразуете результат в целевой тип.

Вот довольно простой num_get фаска. На данный момент он только пытается обеспечить специальную обработку для типа double, Чтобы пример не был слишком длинным, я немного упростил обработку:

  • Он не пытается анализировать показатели по числам (например, «99» в 1e99).
  • Он не пытается справиться с суффиксом %- (но сделаю -%).
  • Это трудно закодировать, чтобы трактовать «,» как десятичную точку и «.» как разделитель тысяч.
  • Он не пытается проверить здравомыслие тысяч разделителей. например., 1,,,3 будет разбирать как 13,

В рамках этих ограничений вот код:

#include <ios>
#include <string>
#include <locale>
#include <iostream>
#include <sstream>
#include <iterator>
#include <cctype>

using namespace std;

template <class charT, class InputIterator = istreambuf_iterator<charT> >
class read_num : public std::num_get < charT > {
public:
typedef charT char_type;
typedef InputIterator iter_type;
protected:
iter_type do_get(iter_type in, iter_type end, ios_base& str, ios_base::iostate& err, double& val) const {
double ret = 0.0;

bool negative = false;
using uc = std::make_unsigned<charT>::type;

while (std::isspace((uc)*in))
++in;
if (*in == '-') {
negative = true;
++in;
while (std::isspace((uc)*in))
++in;
}
while (std::isdigit((uc)*in)) {
ret *= 10;
ret += *in - '0';
++in;
if (*in == '.')
++in;
}
if (*in == ',') {
++in;
double place = 10.0;
while (std::isdigit((uc)*in)) {
ret += (*in - '0') / place;
place *= 10;
++in;
}
}
if (*in == '-') {
negative = true;
++in;
}
if (*in == '%') {
ret /= 100.0;
++in;
}
if (negative)
ret = -ret;
val = ret;
return in;
}
};

Реально, при таких обстоятельствах вы, вероятно, не хотите делать вещи таким образом — вы, вероятно, хотите делегировать существующему фасету, чтобы прочитать собственно число, а затем в конце того, что он анализирует, искать - и / или % и реагировать соответствующим образом (и, возможно, диагностировать ошибку, если, например, вы найдете и ведущий, и конечный ‘-‘).

2