Любые проблемы, выполняющие умножение с потенциальным переполнением, а затем проверку с помощью деления?

Предположим, что у меня есть две переменные size_t, и мне нужно их умножить и получить результат как size_t.

size_t first = ...;
size_t second = ...;
size_t result = first * second;

Они могут переполняться, поэтому мне нужно проверить это.

"Чистым" способом было бы сначала проверить, что умножение возможно с помощью деления:

if( second != 0 && first > ((size_t)-1) / second ) {
   //handle overflow
}
//proceed with computing first * second

По-видимому, менее "чистый" способ состоит в том, чтобы сначала размножить, а затем проверить результат с делением:

size_t result = first * second;
if( second != 0 && result / second != first )
   //handle overflow
}

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

Есть ли потенциальные проблемы со вторым кодом? Всегда ли это будет так же хорошо, как и первое?

Ответ 1

Из математического POV, если

((a*b) mod (2^n)) / b == a  

истинно для некоторых переполняющих значений (это означает, что a и b оба не являются 0), то это проблема.
В дополнение к Wolfram Alpha, это не верно для переполнения, потому что для получения результата биттон переменной результата n должно быть больше требуемой длины бит a * b (log[2](a*b)).

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

Ответ 2

Я бы сказал, что вам нужно не только проверить деление, но и проверить остаток:

if( second != 0 && (result / second != first || (result % second) != 0))

Если c = b * a, то существует только одно значение c, такое как c/b = a. Попытка разделить любое другое значение на b не даст.

Однако это не целочисленная математика, а реальная математика. Поскольку целочисленное деление округляется, формула технически не будет истинна, так как при условии, что b не менее 2, (c + 1)/b также будет a для некоторых значений c.

Ответ 3

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

Второй метод, поскольку единственный способ, которым продукт может отличаться от умножения двух факторов, - это переполнение в середине, было бы правильным, но опять-таки переполнение может иметь дело с Undefined и как таковое, быть неверным (вы неявно думаете, что переполнение будет результатом операции умножения по модулю, а результат действительно равен undefined).

Одним из способов проверки возможного переполнения может быть:

if (MAX_UINT / one_of_the_factors < the_other_factor) ...

Это полностью определено и позволяет обнаруживать переполнение перед выполнением фактического умножения переносимым способом.