Подтвердить что ты не робот

Почему int я = 400 * 400/400 дает результат 72, является ли тип данных круговым?

Я думаю, что сначала 400*400=160000 преобразуется в 28928, начиная с 0 и проходя 160000 раз круговым способом для типа int (например sizeof(int)= 2 байта), предполагая, что он выглядит так:

enter image description here

А затем 28928 делится на 400, пол которого дает 72, и результат варьируется в зависимости от типа переменной. Является ли мое предположение правильным или есть какое-либо другое объяснение?

4b9b3361

Ответ 1

Предполагая, что вы используете довольно ужасно старый компилятор, где int всего 16 бит. Тогда да, ваш анализ правильный. *

400 * 400 = 160000 

//  Integer overflow wrap-around.
160000 % 2^16 = 28928

//  Integer Division
28928 / 400 = 72 (rounded down)

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

* Это поведение обертывания гарантируется только для целых типов без знака. Для целых чисел со знаком это технически undefined поведение в C и С++.

Во многих случаях целые числа со знаком будут по-прежнему демонстрировать одинаковое поведение обхода. Но вы просто не можете рассчитывать на это. (Таким образом, ваш пример с подписанным 16-битным целым не гарантируется.)


Хотя это редко, здесь приведены примеры того, где знаковое целочисленное переполнение не обертывается, как ожидалось:

Ответ 2

Кажется, вы правильно поняли.

Если int - это 16-разрядный тип, он будет вести себя точно так, как вы описали. Ваша операция выполняется последовательно, а 400 * 400 - 160000, что составляет 10 0111 0001 0000 0000

когда вы храните его в 16-битном регистре, верхняя часть "10" будет отрублена, и вы получите 0111 0001 0000 0000 (28,928).... и вы догадались об остальном.

Какой компилятор/платформа вы строите? Типичный рабочий стол должен быть не менее 32 бит, поэтому вы не увидите эту проблему.

ОБНОВЛЕНИЕ # 1

ПРИМЕЧАНИЕ.. Это объясняет ваше поведение с вашим конкретным компилятором. Как и многие другие, было бы быстро указать, НЕ НЕ принять этот ответ, чтобы предположить, что все компиляторы ведут себя таким образом. Но ВАША конкретная, безусловно, делает.

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

Ответ 3

Первое, что вам нужно знать, - это C целочисленные переполнения undefined.

(C99, 6.5.5p5) "Если при оценке выражения возникает исключительное условие (т.е. если результат не определен математически или нет в диапазоне представляемых значений для его типа), то поведение undefined".

C говорит это очень ясно и повторяет его здесь:

(C99, 3.4.3p3) "ПРИМЕР Пример поведения undefined - это поведение при переполнении целых чисел."

Обратите внимание, что целочисленные переполнения учитывают только целочисленное целое число со знаком как беззнаковое целое, никогда не переполняемое:

(C99, 6.2.5p9) "Вычисление с использованием неподписанных операндов никогда не может переполняться, потому что результат, который не может быть представлен результирующим беззнаковым целочисленным типом, уменьшается по модулю число, которое больше одного наибольшего значения, которое может быть представлено по результирующему типу."

Ваше объявление таково:

int i = 400 * 400 / 400;

Предполагая, что int - 16 бит в вашей платформе, а подписанное представление - это два дополнения, 400 * 400 равно 160000, который не может быть представлен как int, INT_MAX значение 32767. Мы находимся в наличии целочисленного переполнения, и реализация может выполнять независимо от.

Обычно в этом конкретном примере компилятор выполнит одно из двух решений ниже:

  • Рассмотрим переполнение обтекания по модулю размера слова, как с целыми без знака, тогда результат 400 * 400 / 400 равен 72.
  • Используйте undefined поведение, чтобы уменьшить выражение 400 * 400 / 400 до 400. Это делается хорошими компиляторами, когда параметры оптимизации включены.

Обратите внимание, что это целочисленное переполнение undefined поведение специфично для языка C. В большинстве языков (например, Java) они обтекают по модулю размер слова, например, целые числа без знака в C.

В gcc, чтобы переполнение всегда было завершено, есть опция -fno-strict-overflow, которая может быть включена (по умолчанию отключена). Это, например, выбор ядра Linux; они собираются с этим вариантом, чтобы избежать неприятных сюрпризов. Но это также ограничивает компилятор не выполнять все оптимизации, которые он может выполнять.