Разрешение целочисленных переполнений со знаком в C/C++
Я хочу, чтобы целые числа со знаком переполнялись, когда они становились слишком большими. Как мне достичь этого, не используя следующий по величине тип данных (или когда я уже на int128_t)?
Например, при использовании 8-битных целых чисел 19*12 обычно составляет 260, но я хочу получить результат 1 11 10 01 00
с отрезанным 9-м битом, таким образом, -27.
8 ответов
Переполнение со знаком не определено в C, и это по-настоящему.
Одно решение следующее:
signed_result = (unsigned int)one_argument + (unsigned int)other_argument;
Приведенное выше решение включает в себя поведение, определяемое реализацией, в конечном преобразовании из unsigned
в int
но не вызывайте неопределенное поведение. С большинством платформ компиляции, определяемых реализацией, результатом является именно тот результат, который вы ожидаете получить.
Наконец, оптимизирующий компилятор для одной из многочисленных платформ, на которых выбор, определенный реализацией, вынуждает компилятор дать вам ожидаемое поведение, скомпилирует приведенный выше код в очевидную инструкцию сборки.
Альтернативно, если вы используете gcc, то параметры -fwrapv
/-fno-strict-overflow
может быть именно то, что вы хотите. Они предоставляют дополнительную гарантию в отношении стандарта, на который распространяются подписанные переполнения. Я не уверен в разнице между ними.
Целочисленное переполнение со знаком не определено в соответствии со стандартами C и C++. Невозможно выполнить то, что вы хотите, без конкретной платформы.
Это можно сделать в правильной стандартной манере C, если у вас есть доступ к unsigned
шрифт той же ширины, что и ваш signed
тип (то есть имеет еще один бит значения). Чтобы продемонстрировать с int64_t
:
int64_t mult_wrap_2scomp(int64_t a, int64_t b)
{
uint64_t result = (uint64_t)a * (uint64_t)b;
if (result > INT64_MAX)
return (int64_t)(result - INT64_MAX - 1) - INT64_MAX - 1;
else
return (int64_t)result;
}
Это не дает никаких проблемных промежуточных результатов.
Вы можете создать объективную оболочку для int, но это потребует большого количества служебного кода.
Предполагая целочисленную арифметику со знаком в виде двух дополнений (что является разумным допущением в наши дни), для сложения и вычитания просто приведите к unsigned для выполнения вычисления. Для умножения и деления убедитесь, что операнды положительны, приведены к беззнаковым, вычислите и скорректируйте знаки.
Вы можете подождать, используя C++20 (после того, как он будет опубликован), и в этом случае поведение с двумя дополнительными компонентами, которое вы, похоже, хотите, будет обязательным, а не просто возможным.
Как видите, комитет по стандартам ISO C++ проголосовал за предложение об изменении языка, в котором определяется поведение со знаком целочисленного переполнения со знаком.
Звучит так, как будто вы хотите выполнить целочисленную арифметику без целых чисел, а затем поместить результат в целое число со знаком:
unsigned char a = 19;
unsigned char b = 12;
signed char c = (signed char)(a*b);
должен дать вам то, что вы ищете. Дайте нам знать, если это не так.
Используйте большие типы данных. С GMP у вас будет все необходимое место.