LINUX.ORG.RU

Чем парсить целые числа?

 , ,


0

3

Ситуация такова, что даже хваленый boost::lexical_cast может удивить: https://wandbox.org/permlink/PP0RTfmifVO816U0

std::cout << boost::lexical_cast<unsigned int>("-1"); // исключения не будет

Спойлер: http://www.boost.org/doc/libs/1_60_0/doc/html/boost_lexical_cast/frequently_a...

Скажите, есть ли вообще в природе средства для парсинга целых чисел (пускай даже ограниченные до radix=10) которые не удивляют?

Т.е. :

  • Не пропускают пробелов вначале строки
  • Интерпретируют всю строчку (а не до первого непонятного символа)
  • Нет автоматического определения radix (т.е. "012" будет 12 а не 10)
  • Могут интерпретировать подстроку (но польностью). Как boost::lexical_cast(str, len)
  • Трактуют 8-ми битные типы как числа (а не как символ/байт, ибо первый байт строки я и сам могу взять)
  • ... не имеют других подводных камней
★★★★★

Последнее исправление: KennyMinigun (всего исправлений: 1)

хваленый boost::lexical_cast может удивить

А ещё он удивляет нулевой производительностью.

которые не удивляют?

Qt? stoi?

RazrFalcon ★★★★★
()
Последнее исправление: RazrFalcon (всего исправлений: 1)

Давай я задам тебе направление, а ты самостоятельно допилишь:

#include <cctype>
#include <stdexcept>

template<typename INT, typename InputIterator>
INT my_atoi(InputIterator p, InputIterator e)
{
  if (p != e) throw std::runtime_error("empty string");
  INT val = 0;
  while (p != e) {
    char d = *p;
    if (!isdigit(d)) throw std::runtime_error("bad symbol");
    INT next = val * 10 + d - '0';
    if (next < val) throw std::runtime_error("overflow");
    val = next;
    ++p;
  }
  return val;
}

Но можешь пойти по пути жабоскрипистов и на каждый пук искать myCoolLiba, где кто-то раз и навсегда решит весьма специфичную проблему вместо тебя.

kawaii_neko ★★★★
()
Ответ на: комментарий от RazrFalcon

Конечно же лучше написать свой велосипед, который даже arithmetic overflow не учитывает.

Чье-то желание комментировать явно опережает способность читать (понимать?) код.

kawaii_neko ★★★★
()
Ответ на: комментарий от RazrFalcon

Лул. Конечно же лучше написать свой велосипед, который даже arithmetic overflow не учитывает. Ох уж эти сишники.

Гыг! Это в принципе всё что вам нужно знать о великом профессионале RazrFalcon :) Читая его заумствования на тему - вспоминайте этот пост.

anonymous
()
Ответ на: комментарий от kawaii_neko

Если под проверкой подразумевается, что входные числа только unsigned - тогда да. Ну и полагаться на знаковое переполнение - это какая-то особая уличная магия.

PS: в первом условии должно быть ==

RazrFalcon ★★★★★
()
Ответ на: комментарий от RazrFalcon

Если под проверкой подразумевается, что входные числа только unsigned

А что будет со знаковыми? inb4 UB.

PS: в первом условии должно быть ==

Действительно.

kawaii_neko ★★★★
()
Ответ на: комментарий от RazrFalcon

Qt?

А что, в Qt есть что-то стоящее в этом плане?

stoi?

Ну...

Discards any whitespace characters (as identified by calling isspace())
then takes as many characters as possible to form a valid base-n

... короче, не фортан.

А еще нет std::stoui (по аналогии std::stoul). Невелика проблема, но негативный осадочек имеется.

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от kawaii_neko

Да я вот как раз и спрашиваю, ибо не хочу комитить в проект такой вот велосипед (уже написал).

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от Dendy

Кстати, видел. Спасибо. Вот бы еще оно умело в во внешние парсеры (т.е. расширяемость). В таком смысле, чтоб boilerplate был со стандартной библиотеки, а функция-предикат (или критерия?) была пользовательская.

KennyMinigun ★★★★★
() автор топика
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от zamazan4ik

Кстати, есть актуальные архитектуры (микроконтроллеры сгодятся тоже), на которых переполнение знаковых даёт что-то принципиально не похожее всем привычное?

KivApple ★★★★★
()
Ответ на: комментарий от KivApple

Не обладаю такими знаниями. Но никому не советую полагаться на такое - UB он всегда UB.

zamazan4ik ★★
()
Ответ на: комментарий от KennyMinigun

Добро пожаловать в C++, где «есть либа на любой случай».

RazrFalcon ★★★★★
()
Ответ на: комментарий от KennyMinigun

А что, в Qt есть что-то стоящее в этом плане?

QString("123").toUInt()?

RazrFalcon ★★★★★
()
Ответ на: комментарий от KennyMinigun

Про to_chars/from_chars они как раз и создавались как лёгкий конвертор чисел в сишной локали на замену sstream и сишным функциям. Они задумывались как рабочие блоки для высокоуровневых парсеров, который каждый для себя пишет сам. Без constexpr, исключений и сложного поведения.

Dendy ★★★★★
()
Ответ на: комментарий от RazrFalcon

А разве кодинг на си это не изобретение велосипедов? А то каждый второй с гордостью пишет говнокод, но на си.

NextGenenration ★★
()
Ответ на: комментарий от dzidzitop

Всегда интересно посмотреть, как другие решают проблему, которая стояла перед самим тобой.

Я все же решил за основу взять стандартную библиотеку. Поскольку strtoll может «убежать» за конец строки, решил использовать sscanf.

Вот что-то такая зарисовка: https://wandbox.org/permlink/eMENa8Q4YG8QF89d

Остальные типы unsigned получатся простой проверкой

T value = NumericUtil::parse<unsigned long long>(str, length);
if (value > std::numeric_limits<T>::max())
    throw std::invalid_argument("conversion would overflow"); 

Ну а signed аналогично, только код проще

P.S. Да, я таки решил оставить пропускание пробелов вначале

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от anonymous

парсер-комбинаторах

Это кто такие? Не слышал

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от KennyMinigun

Там я заморочился портабельной реализацией парсинга на всех возможных целых типах, знаковых и беззнаковых, без переполнения, без undefined behaviour, с исключениями или без (по выбору пользователя), на любых входных данных и с оптимизацией процесса. Единственное ограничение - сознательная поддержка только ASCII (остальное не будет компилироваться).

Но, в общем, каждый подобный код велосипедит под себя :)

dzidzitop ★★
()
Ответ на: комментарий от KennyMinigun

Не совсем понятно назначение snprintf и переменной format. Но наверное работает, если граничные случаи типа переполнения проверены.

dzidzitop ★★
()
Ответ на: комментарий от KennyMinigun

я таки решил оставить пропускание пробелов вначале

Я недавно парсил строку, где хранилось 4 действительных числа впритык, каждое занимало строго 15 позиций и на первой позиции знак + мог стоять, а мог и не стоять. Во втором случае при использовании scanf он отхватывал кусок второго числа, что было неверным. Поэтому просто использовал .substr в паре со stod. Мне это этой же цели показывали решение на strncpy + atoi.

grem ★★★★★
()

Не благодари

#include <string>
#include <iostream>
#include <map>

template<typename T, bool _signed>
bool parse_int(const std::string& s, T& out, unsigned char base)
{
    static std::map<char, T> letters = 
        {{'0', 0},{'1', 1},{'2', 2},{'3', 3},
         {'4', 4},{'5', 5},{'6', 6},{'7', 7},
         {'8', 8},{'9', 9},{'A', 10},{'B', 11},
         {'C', 12},{'D', 13},{'E', 14},{'F', 15}};

    if(base > 16) return false;

    T val = 0;
    int i = 0;
    char sign = 1;
    char c;
    auto len = s.length();
    auto it = letters.end();
    auto notfound = letters.end();

    if(len == 0) return false;

start:
    c = s[i];
    switch(c)
    {
        case '+':
            ++i;
            goto first;
        case '-':
            if(!_signed)
                return false;
            sign = -1;
            ++i;
            goto first;
    }
first:
    if(i >= len) return false;
    c = s[i];
    it = letters.find(c);
    if(it == notfound || it->second >= base) return false;
    val = val * base + it->second;
    ++i;
digits:
    if(i >= len)
    {
        out = (_signed ? sign * val : val);
        return true;
    }
    c = s[i];
    it = letters.find(c);
    if(it == notfound || it->second >= base) return false;
    val = val * base + it->second;
    ++i;
    goto digits;
}

template<typename T>
bool parse_int(const std::string& s, T& out, unsigned char base);

template<>
bool parse_int<char>(const std::string& s, char& out, unsigned char base)
{
    return parse_int<char, true>(s, out, base);
}

template<>
bool parse_int<unsigned char>(const std::string& s, unsigned char& out, unsigned char base)
{
    return parse_int<unsigned char, false>(s, out, base);
}

template<>
bool parse_int<short>(const std::string& s, short& out, unsigned char base)
{
    return parse_int<short, true>(s, out, base);
}

template<>
bool parse_int<unsigned short>(const std::string& s, unsigned short& out, unsigned char base)
{
    return parse_int<unsigned short, false>(s, out, base);
}

template<>
bool parse_int<int>(const std::string& s, int& out, unsigned char base)
{
    return parse_int<int, true>(s, out, base);
}

template<>
bool parse_int<unsigned int>(const std::string& s, unsigned int& out, unsigned char base)
{
    return parse_int<unsigned int, false>(s, out, base);
}

template<>
bool parse_int<long>(const std::string& s, long& out, unsigned char base)
{
    return parse_int<long, true>(s, out, base);
}

template<>
bool parse_int<unsigned long>(const std::string& s, unsigned long& out, unsigned char base)
{
    return parse_int<unsigned long, false>(s, out, base);
}

template<>
bool parse_int<long long>(const std::string& s, long long& out, unsigned char base)
{
    return parse_int<long long, true>(s, out, base);
}

template<>
bool parse_int<unsigned long long>(const std::string& s, unsigned long long& out, unsigned char base)
{
    return parse_int<unsigned long long, false>(s, out, base);
}

int main(void)
{
    uint32_t x;
    if(parse_int("FF", x, 16))
        std::cout << x << std::endl;
    else
        std::cerr << "Error" << std::endl;
    return 0;
}

lovesan ★★
()
Ответ на: комментарий от dzidzitop

назначение snprintf и переменной format.

чтоб не позволить scanf читать за границами length (на случай когда надо парсить подстроку или просто не null-terminated).

Можно было спользовать просто sprintf, всего лишь паранойя :)

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от KennyMinigun

Непонятно скорее чем статический формат для sscanf «число + null-terminated» не работает вместо какой-то магии с format = xxx. Наверное в дебаге станет понятно, но просто так не распарсил.

dzidzitop ★★
()
Ответ на: комментарий от lovesan

Так вот он какой - «Си c классами».

Некорректно обрабатываются переполнения. Так что код нужно фиксить.

dzidzitop ★★
()
Ответ на: комментарий от anonymous

lovesan тебе не задрот, чтобы знать о std::numeric_limits<T>::is_signed. Только задроты и неудачники знают об intrusive_ptr и std::numeric_limits<T>::is_signed.

anonymous
()
Ответ на: комментарий от KivApple

Тут проблема даже не в микроконтролерах, а в компиляторе. Если написано UB, значит компилятор смело считает, что такого быть не может и использует это знание для оптимизации кода. А если вдруг будет — ну это уже не проблемы компилятора. Что может привести к очень неожиданному поведению, когда это переполнение случится.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

Если написано UB, значит компилятор смело считает,

Проще говоря, каждый раз когда кто-то компилирует код с UB в мире находится под угрозой один милый котеночек (потому что компилятор имеет право его убить).

KennyMinigun ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Шаблоны и специализацию использовал при этом, если угораешь по крестам, то угорай до конца

anonymous
()
Ответ на: комментарий от pftBest

Такое (говно) и в стандартных плюсах есть: tp://en.cppreference.com/w/cpp/string/basic_string/stol

А вот нормального парсинга не из std::string - нет.

=====

Хотя оно ещё и останавливается посреди строки. Так что руст победил в состязании.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 2)
Ответ на: комментарий от pftBest

А чего документация такая убогая?

https://doc.rust-lang.org/std/str/trait.FromStr.html

Из неё вообще не следует ничего (например, что вся строка должна быть числом), и поддержки основания != 10 нет.

====

i32 получше: https://doc.rust-lang.org/std/primitive.i32.html#method.from_str_radix

==

FromStr::from_str(a) - а это вообще атас. Непонятно в принципе во что парсится эта строка. Говно этот rust как язык (может, не как идея). Видать это как плюсовые пляски вокруг вывода типов: auto f(void); Что за убогая мода -непонятно.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 3)
Ответ на: комментарий от dzidzitop

Непонятно в принципе во что парсится эта строка.

Как это не понятно, какого типа переменная так и будет парсится. FromStr это просто стандартный интерфейс который может реализовать любой тип. Напишешь например свой тип «Color», не забудь добавить ему функцию from_str из трейта FromStr. Тогда кто будет пользоваться твоим типом сможет использовать его в шаблонных функциях. Как пример вот функция которая парсит пару T разделенных запятой, без единой аллокации на куче:

fn parse_pair<T>(input: &str) -> Result<(T, T), T::Err>
    where T: FromStr
{
    let mut items = input.splitn(2, ',')
        .map(str::trim)
        .map(T::from_str);
    let n1 = try!(items.next().unwrap());
    let n2 = try!(items.next().unwrap());
    Ok((n1, n2))
}

Все что требуется от T - реализовать FromStr, а стандартные типы это уже делают, так что эта функция будет работать со всеми числами и даже ip адресами.

    let a = "-11, -12";
    let x: (i32, i32) = parse_pair(a).unwrap();

    let b = "true, false";
    let y: (bool, bool) = parse_pair(b).unwrap();

https://is.gd/SvLKP3

pftBest ★★★★
()
Ответ на: комментарий от pftBest

Вот так - непонятно.

float pow(float, float);
float pow(doble, double);
float pow(int, int);

float val = pow(from_str("123"), from_str("5"));

Где это видано, где это слыхано, что вызываемая _функция_ резолвится через тип значения? Это бред - разгадывать ребусы какая функция будет вызвана в примере выше. Такие фичи языка нужно выжигать клеймом на задницах хипстеров, которые их реквестируют.

dzidzitop ★★
()
Последнее исправление: dzidzitop (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.