Введите осведомленную строку для преобразования числа в C++

Давайте предположим, что все числа в этом мире являются положительными целыми числами, и они могут быть представлены типами C++ uintX_t.

Давайте рассмотрим следующий потрясающий код для преобразования std::string в число:

#include <string>
#include <cstdint>
#include <iostream>

template <typename T>
T MyAwsomeConversionFunction(const std::string& value)
{
    T result = 0;
    for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
    {
        result = result * 10 + *it - '0';
    }

    return result;
}

int main(int argc, const char * argv[])
{
    std::cout<<MyAwsomeConversionFunction<uint16_t>("1234")<<std::endl;
    std::cout<<MyAwsomeConversionFunction<uint16_t>("123456")<<std::endl;

    return 0;
}

Как вы можете видеть, в этой функции есть несколько ошибок, но меня интересует конкретная: как определить, когда мой тип недостаточно велик, чтобы содержать значение (второй вызов преобразования в качестве примера), и избежать UB при создании result = result * 10 + *it - '0';, Я хотел бы знать, превысит ли эта операция максимальное значение T прежде чем сделать это. Это возможно?

РЕДАКТИРОВАТЬ: пожалуйста, проверьте Является ли целочисленное переполнение со знаком все еще неопределенным поведением в C++? для получения дополнительной информации о UB по арифметическим операциям в C++. Я хочу избежать выполнения строки result = result * 10 + *it - '0'; когда результат переполнится. В ответе строка еще выполняется...

EDIT2: я нашел ответ здесь: Как обнаружить целочисленное переполнение?

EDIT3: принятый ответ применяется для подписанных типов. Для неподписанных типов Cheers и HTH. - Альф ответ правильный.

3 ответа

Решение

Я попробую это сделать, хотя меня могут забрать за ошибки. Это не относится к отрицательным значениям в строке (ваш исходный код тоже нет). И это ограничено цифрами ASCII, как упомянул Альф в комментарии к своему ответу.

template <typename T>
T MyAwsomeConversionFunction(const std::string& value)
{
    T maxBeforeMult = std::numeric_limits<T>::max / 10;
    T result = 0;
    for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
    {
        // Check if multiplying would overflow
        if (result > maxBeforeMult)
        {
            // throw overflow
        }

        result = result * 10;
        T digit = *it - 0;

        // Check if adding would overflow
        if (std::numeric_limits<T>::max - result < digit)
        {
            // throw overflow
        }

        result += digit;
    }

    return result;
}

Вам просто нужно работать в обратном направлении, спрашивая, не переполнится ли данная цифра:

// When result exceeds this thresh, appending a digit will always overflow.
static const T thresh = std::numeric_limits<T>::max() / 10;
// When result equals this thresh, appending a digit larger than
// thresh_last_digit will overflow.
static const T thresh_last_digit = std::numeric_limits<T>::max() - 10 * thresh;

for(auto it = value.begin(); it != value.end() && std::isdigit(*it); ++it)
{
    if(result > threshold)
        throw std::overflow_error(value);
    T digit = *it - '0';
    if(result == threshold && digit > thresh_last_digit)
        throw std::overflow_error(value);
    result = result * 10 + digit;
}

Для неподписанного типа T ты всегда можешь сделать

T const original = result;
result = result * 10 + *it - '0';
if( result / 10 != original ) { throw 666; }

За исключением замены throw 666 с чем-то.


Для очевидной исходной проблемы преобразования строки → целого числа с обнаружением переполнения см. strtol и семья.

Другие вопросы по тегам