1. Обзор
В этом кратком руководстве мы собираемся показать, что такое оператор по модулю и как мы можем использовать его с Java для некоторых распространенных случаев использования.
2. Оператор по модулю
Начнем с недостатков простого деления в Java.
Если операнды с обеих сторон оператора деления имеют тип int
, результатом операции будет другой тип int:
@Test
public void whenIntegerDivision_thenLosesRemainder() {
assertThat(11 / 4).isEqualTo(2);
}
То же деление дает другой результат, когда хотя бы один из операндов имеет тип float
или double:
@Test
public void whenDoubleDivision_thenKeepsRemainder() {
assertThat(11 / 4.0).isEqualTo(2.75);
}
Мы можем заметить, что мы теряем остаток от операции деления при делении целых чисел.
Оператор по модулю дает нам именно этот остаток:
@Test
public void whenModulo_thenReturnsRemainder() {
assertThat(11 % 4).isEqualTo(3);
}
Остаток — это то, что остается после деления 11 (делимое) на 4 (делитель) — в данном случае 3.
По той же причине, по которой деление на ноль невозможно, невозможно использовать оператор по модулю, когда правый аргумент равен нулю.
И деление, и операция по модулю вызывают исключение ArithmeticException
, когда мы пытаемся использовать ноль в качестве правого операнда:
@Test(expected = ArithmeticException.class)
public void whenDivisionByZero_thenArithmeticException() {
double result = 1 / 0;
}
@Test(expected = ArithmeticException.class)
public void whenModuloByZero_thenArithmeticException() {
double result = 1 % 0;
}
3. Распространенные варианты использования
Наиболее распространенный вариант использования оператора по модулю — выяснить, является ли заданное число четным или нечетным.
Если результат операции по модулю между любым числом и двумя равен единице, это нечетное число:
@Test
public void whenDivisorIsOddAndModulusIs2_thenResultIs1() {
assertThat(3 % 2).isEqualTo(1);
}
С другой стороны, если результат равен нулю (т. е. нет остатка), это четное число:
@Test
public void whenDivisorIsEvenAndModulusIs2_thenResultIs0() {
assertThat(4 % 2).isEqualTo(0);
}
Еще одним хорошим применением операции по модулю является отслеживание индекса следующего свободного места в циклическом массиве.
В простой реализации циклической очереди для значений int
элементы хранятся в массиве фиксированного размера.
Каждый раз, когда мы хотим поместить элемент в нашу циклическую очередь, мы просто вычисляем следующую свободную позицию, вычисляя по модулю количество уже вставленных элементов плюс 1 и емкость очереди:
@Test
public void whenItemsIsAddedToCircularQueue_thenNoArrayIndexOutOfBounds() {
int QUEUE_CAPACITY= 10;
int[] circularQueue = new int[QUEUE_CAPACITY];
int itemsInserted = 0;
for (int value = 0; value < 1000; value++) {
int writeIndex = ++itemsInserted % QUEUE_CAPACITY;
circularQueue[writeIndex] = value;
}
}
Используя оператор по модулю, мы предотвращаем выпадение writeIndex
за границы массива, поэтому мы никогда не получим исключение ArrayIndexOutOfBoundsException
.
Однако, как только мы вставим больше элементов, чем QUEUE_CAPACITY
, следующий элемент перезапишет первый.
4. Вывод
Оператор по модулю используется для вычисления остатка от целочисленного деления, которое в противном случае теряется.
Полезно делать простые вещи, такие как определение, является ли заданное число четным или нечетным, а также более сложные задачи, такие как отслеживание следующей позиции записи в циклическом массиве.
Код примера доступен в репозитории GitHub .