Типы данных и int в программировании: разрядность, диапазоны и операции

Типы данных и int в программировании: разрядность, диапазоны и операции Полезное

Тип данных — это классификация, которая задает для значения два свойства: множество допустимых значений (диапазон) и набор операций, которые с ним разрешены. Целочисленный тип int (от integer) хранит целые числа без дробной части и поддерживает арифметику, сравнение и побитовые операции.

Ниже разберем, зачем язык вообще делит данные на типы, чем примитивные типы отличаются от ссылочных, как устроена разрядность и диапазоны у int, что такое знаковые и беззнаковые целые, чем грозит переполнение и какие операции доступны над целыми. Примеры на Python и C запущены, вывод в тексте — реальный.

Что такое тип данных

Тип данных отвечает на два вопроса о значении: какие значения допустимы и что с ними можно делать. Например, целое число можно умножать и сравнивать, а к строке умножение обычной арифметики неприменимо.

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

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

Примитивные и ссылочные типы

Типы принято делить на две большие группы. Разница — в том, что хранит переменная: само значение или ссылку на него.

  • Примитивные типы встроены в язык и хранят значение напрямую: целые (int), дробные (float, double), логический (bool), символ (char). Целые числа — это как раз примитивный тип.
  • Ссылочные (составные) типы хранят ссылку на объект в памяти: строки, массивы, объекты классов, структуры. Их часто определяет сам разработчик.

Это деление заметнее всего в Java и C#, где оно закреплено в языке. В Python формально все является объектами, но целые числа ведут себя как простые значения, и на практике их удобно считать примитивами.

Целочисленный тип int: разрядность и диапазоны

Разрядность int — это число бит, отведенных под целое. От нее напрямую зависит диапазон: n бит со знаком дают значения от минус 2 в степени (n-1) до 2 в степени (n-1) минус 1.

Стандартные ширины целых и их диапазоны одинаковы во всех языках с фиксированной разрядностью:

Ширина Со знаком Без знака Типичный тип в C/C++
8 бит -128 .. 127 0 .. 255 signed char / unsigned char
16 бит -32 768 .. 32 767 0 .. 65 535 short
32 бита -2 147 483 648 .. 2 147 483 647 0 .. 4 294 967 295 int
64 бита -9,2 * 10^18 .. 9,2 * 10^18 0 .. 1,8 * 10^19 long long

Для 64 бит точные границы со знаком: от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807; без знака — от 0 до 18 446 744 073 709 551 615.

Важная оговорка: сама ширина типа int зависит от языка и платформы, а не только от названия. В C и C++ стандарт гарантирует лишь минимум 16 бит, но на современных системах int обычно 32-битный. Тип long по стандарту не меньше 32 бит: на 64-битных Linux и macOS он 64-битный, а на 64-битной Windows — 32-битный. Поэтому переносимый код полагается на фиксированные типы вроде int32_t и int64_t из заголовка stdint.h.

int в разных языках

Название int в разных языках означает разное. Полезно держать в голове сводку:

Язык Тип int Разрядность Беззнаковые целые
C / C++ int зависит от платформы, обычно 32 бита (минимум 16) есть (unsigned)
Java int всегда 32 бита нет отдельных типов
C# int всегда 32 бита (System.Int32) есть (uint)
Python int без фиксированной границы (bignum) не нужны

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

# Python: целое произвольной длины (bignum), аппаратного переполнения нет
big = 2 ** 100
print("2**100 =", big)
print("бит в этом числе:", big.bit_length())

# целочисленное деление и остаток (в Python деление округляется вниз)
print("17 // 5 =", 17 // 5, "; 17 % 5 =", 17 % 5)
print("-17 // 5 =", -17 // 5, "; -17 % 5 =", -17 % 5)

# побитовые операции
print("6 & 3 =", 6 & 3, "; 6 | 3 =", 6 | 3, "; 6 ^ 3 =", 6 ^ 3)
print("1 << 10 =", 1 << 10)

# для сравнения - границы int32 и uint32 в C-стиле
print("верх int32:", 2 ** 31 - 1)
print("верх uint32:", 2 ** 32 - 1)

Вывод программы:

2**100 = 1267650600228229401496703205376
бит в этом числе: 101
17 // 5 = 3 ; 17 % 5 = 2
-17 // 5 = -4 ; -17 % 5 = 3
6 & 3 = 2 ; 6 | 3 = 7 ; 6 ^ 3 = 5
1 << 10 = 1024
верх int32: 2147483647
верх uint32: 4294967295

Число 2 в степени 100 заняло 101 бит — в 32-битный int оно бы не поместилось, а Python держит его целиком. Обратите внимание на деление минус 17 на 5: Python округляет частное вниз (к минус бесконечности), поэтому остаток получается положительным. В C и Java та же операция округляет к нулю, и знаки будут другими — это частая ловушка при переносе кода.

Знаковые и беззнаковые целые

Целые бывают знаковыми (signed) и беззнаковыми (unsigned). Знаковый тип отводит один бит под знак и хранит и отрицательные, и положительные значения. Беззнаковый хранит только неотрицательные, зато его верхняя граница вдвое больше при той же ширине.

Беззнаковые типы есть в C, C++ и C#, но их нет как отдельных типов в Java: там все целые знаковые (исключение — 16-битный char, он беззнаковый). С Java 8 появились статические методы для беззнаковой интерпретации (например, Integer.toUnsignedLong), но отдельного типа unsigned int в языке по-прежнему нет.

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

Переполнение int

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

В беззнаковых типах C и C++ переполнение определено стандартом: значение заворачивается по модулю (для 8 бит — по модулю 256). Для знаковых типов в C и C++ переполнение — это неопределенное поведение (undefined behavior), результат не гарантирован. В Java и C# знаковое переполнение определено и заворачивается по правилам дополнительного кода. В Python переполнения нет вовсе. Посмотрим на заворот в C:

#include <stdio.h>
#include <limits.h>

int main(void) {
    printf("sizeof: char=%zu short=%zu int=%zu long=%zu long long=%zu\n",
           sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(long long));
    printf("int:  %d .. %d\n", INT_MIN, INT_MAX);
    printf("uint: 0 .. %u\n", UINT_MAX);

    unsigned char uc = 255;
    uc = uc + 1;               /* беззнаковое переполнение: заворот по модулю 256 */
    printf("255 + 1 в unsigned char = %u\n", uc);

    unsigned int b = (unsigned int)INT_MAX + 1U;  /* считаем в unsigned - определено */
    printf("INT_MAX + 1 в unsigned = %u\n", b);
    return 0;
}

Вывод программы (собрано и запущено на 64-битной macOS):

sizeof: char=1 short=2 int=4 long=8 long long=8
int:  -2147483648 .. 2147483647
uint: 0 .. 4294967295
255 + 1 в unsigned char = 0
INT_MAX + 1 в unsigned = 2147483648

Прибавление единицы к 255 в unsigned char дало 0 — значение завернулось. Именно так работают счетчики, которые обнуляются при переполнении. Обратите внимание: sizeof выдал long равным 8 байтам, потому что программа собрана на macOS; на Windows тот же тип занял бы 4 байта.

Операции над int

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

  • Арифметические: сложение (+), вычитание (-), умножение (*), целочисленное деление и остаток (в Python — // и %, в C и Java — / и % для целых). Инкремент (++) и декремент (—) есть в C, C++, Java и C#, но не в Python.
  • Сравнения: равно (==), не равно (!=), больше (>), меньше (<), больше или равно (>=), меньше или равно (<=). Результат — логическое значение.
  • Побитовые: и (&), или (|), исключающее или (^), инверсия (~), сдвиги влево (<<) и вправо (>>). Они работают с двоичным представлением числа и обычно быстрее арифметики.

Здесь стоит развести частую путаницу: оператор ^ во всех перечисленных языках — это побитовое исключающее или (XOR), а не возведение в степень. Возведение в степень в Python записывается как **, а в C, C++ и Java отдельного оператора нет — используют функцию pow из стандартной библиотеки.

Выводы

  • Тип данных задает значению две вещи: диапазон допустимых значений и разрешенные операции; целые числа — примитивный тип.
  • Разрядность int определяет диапазон: n бит со знаком дают от минус 2^(n-1) до 2^(n-1) минус 1; ширина int зависит от языка и платформы.
  • Беззнаковые целые есть в C, C++, C#, но не в Java; в Python целые не имеют фиксированной границы и не переполняются.
  • Переполнение ведет себя по-разному: заворот в беззнаковых типах C и в Java, неопределенное поведение для знаковых в C и C++, отсутствие переполнения в Python.

Где применяется и что учить дальше

Целые числа — основа почти любой программы: счетчики циклов, индексы массивов, идентификаторы записей в базе, коды состояния, битовые флаги. Понимание разрядности и переполнения нужно везде, где важны производительность и корректность, — от финансовых расчетов до работы с сетевыми протоколами и хешами.

Освойте тему на практике

Начинать удобнее с языка, где не нужно вручную следить за разрядностью и переполнением. В Python целые ведут себя предсказуемо, и на них проще разобрать сами операции, а разрядность и знаковость освоить уже осознанно. Системно пройти путь от переменных и типов до практических задач помогает курс Python Basic. Оценить формат и уровень до старта можно на бесплатных открытых уроках Otus — это живые занятия с преподавателями.

Смежные темы: Типы данных в языках программирования, Типизация в программировании, Структуры и типы данных.

FAQ

Чем int отличается от float? int хранит целые числа точно, без дробной части. float и double хранят числа с плавающей запятой и приблизительно — у них ограничена точность, поэтому для денег и счетчиков берут целые, а не дробные типы.

Что выбрать — int или long? Если значение гарантированно помещается в 32 бита (примерно до двух миллиардов), достаточно int. Для больших величин — количества байт в большом файле, времени в миллисекундах — берут 64-битный тип (long в Java, long long или int64_t в C/C++).

Почему в C рекомендуют int32_t вместо int? Потому что ширина int зависит от платформы, а int32_t из stdint.h гарантирует ровно 32 бита. Это делает поведение кода предсказуемым при переносе между системами и компиляторами.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)