Как я могу правильно проверить, что целочисленное переполнение и переполнение не произойдет до вычитания? - PullRequest
2 голосов
/ 29 января 2020

В настоящее время я работаю над безопасной целочисленной библиотекой для C ++. Я сталкивался с некоторыми проблемами при реализации вычитания.

Вот что я начал с:

#include <limits>
#include <stdexcept>

template<typename I>
class safe_int
{
    I val;

public:
    typedef I value_type;
    static constexpr I max = std::numeric_limits<I>::max();
    static constexpr I min = std::numeric_limits<I>::min();

    safe_int(I i) : val { i } { };

    safe_int &operator+=(I rhs)
    {
        if( val > 0 && rhs > max - val )
            throw std::overflow_error("");
        else if( val < 0 && rhs < min - val )
            throw std::underflow_error("");

        val += rhs;
        return *this;
    }
};

Сначала я попытался написать operator-= так:

safe_int &operator-=(I rhs)
{
    return operator+=(-rhs);
}

но очевидно, что это не удастся, если ввести -0x80000000 в системе дополнения до двух.

Затем я попытался сделать это так:

safe_int &operator-=(I rhs)
{
    if(rhs < -max)
        throw std::overflow_error("");

    return operator+=(-rhs);
}

Но это не работает для все, что меньше 0 (например, -1 - -0x80000000 должно быть 0x7fffffff, но вместо этого сообщается о переполнении).

Затем я попытался это сделать:

safe_int &operator-=(I rhs)
{
    if( rhs < -max && val > 0 )
        throw std::overflow_error("");

    return operator+=(-rhs);
}

Но теперь, хотя он правильно ловит В случае, когда происходит переполнение, оно само вызывает переполнение в допустимом случае (например, -1 - -0x80000000, где - -0x80000000 переполняется).

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

Как правильно проверить, что целочисленное переполнение не произойдет до вычитания?

Вот небольшая тестовая программа:

int main(void)
{
    safe_int<int> i = -1;

    i -= -2147483648;

    return 0;
}

Предполагается, что нет конкретного размера целого числа. Не полагайтесь на неопределенное поведение.

1 Ответ

1 голос
/ 29 января 2020
template<class I>
bool valid_add( I lhs, I rhs ) {
  static constexpr I max = std::numeric_limits<I>::max();
  static constexpr I min = std::numeric_limits<I>::min();

  if( rhs > 0 && lhs > max - rhs ) return false;
  if( rhs < 0 && lhs < min - rhs ) return false;

  return true;
}
template<class I>
bool valid_subtract( I lhs, I rhs ) {
  static constexpr I max = std::numeric_limits<I>::max();
  static constexpr I min = std::numeric_limits<I>::min();

  if ((rhs < 0) && (lhs > max + rhs)) return false;
  if ((rhs > 0) && (lhs < min + rhs)) return false;

  return true;
}

обратите внимание, что две функции в основном задают один и тот же вопрос. Сначала мы спрашиваем, "в каком направлении rhs переместит наш результат с lhs". Затем мы проверяем, находится ли lhs «достаточно далеко» от min или max в этом направлении.

В вашем коде просто введите:

if (!valid_add(val, rhs))
   throw "whatever";

и

if (!valid_subtract(val, rhs))
   throw "whatever";

вам нужно изменить этот код, если он не используется на целочисленных типах. С типами с плавающей запятой есть и другие сложности.

После проверки правильности операции просто сделайте это . Не вызывайте другую функцию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...