Арифметические операции в java. Операции над примитивными типами в Java. Операции с числами с плавающей точкой

Nokia 11.04.2019

Тебе наверняка знакомо слово “бит”. Если же нет, давай познакомимся с ним:) Бит - минимальная единица измерения информации в компьютере. Его название происходит от английского “binary digit ” - “двоичное число”. Бит может быть выражен одним из двух чисел: 1 или 0. Существует специальная система счисления, основанная на единицах и нулях - двоичная .

Не будем углубляться в дебри математики и отметим лишь, что любое число в Java можно сконвертировать в его двоичную форму. Для этого нужно использовать классы-обертки. Например, вот как можно сделать это для числа int: public class Main { public static void main (String args) { int x = 342 ; System. out. println (Integer. toBinaryString (x) ) ; } } Вывод в консоль: 101010110 1010 10110 (я добавил пробел для удобства чтения) - это число 342 в двоичной системе. Мы фактически разделили это число на отдельные биты - нули и единицы. Именно с ними мы можем выполнять операции, которые называются побитовыми.

    ~ - побитовый оператор “НЕ”.

Он работает очень просто: проходится по каждому биту нашего числа и меняет его значение на противоположное: нули - на единицы, единицы - на нули. Если мы применим его к нашему числу 342, вот что получится: 101010110 - число 342 в двоичной системe 010101001 - результат выражения ~342 Попробуем выполнить это на практике: public class Main { public static void main (String args) { int x = 342 ; System. out. println (~ x) ; } } Вывод в консоль: -343 -343 - это наш результат (число 010101001) в привычной десятичной системе:)

    & - побитовый оператор “И”

Он, как видишь, довольно похож по написанию на логический “И” (&&). Оператор && , как ты помнишь, возвращает true только если оба операнда являются истинными. Побитовый & работает схожим образом: он сравнивает два числа по битам. Результатом этого сравнения является третье число. Для примера, возьмем числа 277 и 432: 100010101 - число 277 в двоичной форме 110110000 - число 432 в двоичной форме Далее оператор & сравнивает первый бит верхнего числа с первым битом нижнего. Поскольку это оператор “И”, то результат будет равен 1 только в том случае, если оба бита равны 1. Во всех остальных случаях результатом будет 0. 100010101 & 110110000 _______________ 100010000 - результат работы & Мы сравниваем сначала первые биты двух чисел друг с другом, потом вторые биты, третьи и т.д. Как видишь, только в двух случаях оба бита в числах были равны 1 (первый и пятый по счету биты). Результатом всех остальных сравнений стал 0. Поэтому в итоге у нас получилось число 100010000. В десятичной системе ему соответствует число 272. Давай проверим: public class Main { public static void main (String args) { System. out. println (277 & 432 ) ; } } Вывод в консоль: 272
  • | - побитовое “ИЛИ”. Принцип работы тот же - сравниваем два числа по битам. Только теперь если хотя бы один из битов равен 1, результат будет равен 1. Посмотрим на тех же числах - 277 и 432:
100010101 | 110110000 _______________ 110110101 - результат работы | Здесь уже результат другой: нулями остались только те биты, которые в обоих числах были нулями. Результат работы - число 110110101. В десятичной системе ему соответствует число 437. Проверим: public class Main { public static void main (String args) { System. out. println (277 | 432 ) ; } } Вывод в консоль: 437 Мы все посчитали верно! :)
  • ^ - побитовое исключающее “ИЛИ” (также известно как XOR)
С таким оператором мы еще не сталкивались. Но ничего сложного в нем нет. Он похож на обычное “или”. Разница в одном: обычное “или” возвращает true , если хотя бы один операнд является истинным. Но не обязательно один - если оба будут true - то и результат true . А вот исключающее “или” возвращает true только если один из операндов является истинным. Если истинны оба операнда , обычное “или” вернет true (“хотя бы один истинный“), а вот исключающее или вернет false . Поэтому он и называется исключающим. Зная принцип предыдущих побитовых операций, ты наверняка и сам сможешь легко выполнить операцию 277^432. Но давай лучше лишний раз разберемся вместе:) 100010101 ^ 110110000 _______________ 010100101 - результат работы ^ Вот и наш результат. Те биты, которые были в обоих числах одинаковыми, вернули 0 (не сработала формула “один из”). А вот те, которые образовывали пару 0-1 или 1-0, в итоге превратились в единицу. В результате мы получили число 010100101. В десятичной системе ему соответствует число 165. Давай посмотрим, правильно ли мы посчитали: public class Main { public static void main (String args) { System. out. println (277 ^ 432 ) ; } } Вывод в консоль: 165 Супер! Все именно так, как мы и думали:) Теперь самое время познакомиться с операциями, которые называют битовыми сдвигами . Название, в принципе, говорит само за себя. Мы возьмем какое-то число и будем двигать его биты влево и вправо:) Давай посмотрим как это выглядит:

Сдвиг влево

Сдвиг битов влево обозначается знаком << Пример: public class Main { public static void main (String args) { int x = 64 ; //значение int y = 3 ; //количество int z = (x << y) ; System. out. println (Integer. toBinaryString (x) ) ; System. out. println (Integer. toBinaryString (z) ) ; } } В этом примере число x=64 называется значением . Именно его биты мы будем сдвигать. Сдвигать биты мы будем влево (это можно определить по направлению знака <<) В двоичной системе число 64 = 1000000 Число y=3 называется количеством . Количество отвечает на вопрос “на сколько бит вправо/влево нужно сдвинуть биты числа x ” В нашем примере мы будем сдвигать их на 3 бита влево. Чтобы процесс сдвига был более понятен, посмотрим на картинке. У нас в примере используются числа типа int. Int ’ы занимают в памяти компьютера 32 бита. Вот так выглядит наше изначальное число 64:

А теперь мы, в прямом смысле слова, берем каждый из наших битов и сдвигаем влево на 3 ячейки:

Вот что у нас получилось. Как видишь, все наши биты сдвинулись, а из-за пределов диапазона добавились еще 3 нуля. 3 - потому что мы делали сдвиг на 3. Если бы мы сдвигали на 10, добавилось бы 10 нулей. Таким образом, выражение x << y означает “сдвинуть биты числа х на y ячеек влево”. Результатом нашего выражения стало число 1000000000, которое в десятичной системе равно 512. Проверим: public class Main { public static void main (String args) { int x = 64 ; //значение int y = 3 ; //количество int z = (x << y) ; System. out. println (z) ; } } Вывод в консоль: 512 Все верно! Теоретически, биты можно сдвигать до бесконечности. Но поскольку у нас число int , в распоряжении есть всего 32 ячейки. Из них 7 уже заняты числом 64 (1000000). Поэтому если мы сделаем, например, 27 сдвигов влево, наша единственная единица выйдет за пределы диапазона и “затрётся”. Останутся только нули! public class Main { public static void main (String args) { int x = 64 ; //значение int y = 26 ; //количество int z = (x << y) ; System. out. println (z) ; } } Вывод в консоль: 0 Как мы и предполагали, единичка вышла за пределы 32 ячеек-битов и исчезла. У нас получилось 32-битное число, состоящее из одних нулей.

Естественно, в десятичной системе ему соответствует 0. Простое правило для запоминания сдвигов влево: При каждом сдвиге влево выполняется умножение числа на 2. Например, попробуем без картинок с битами посчитать результат выражения 111111111 << 3 Нам нужно трижды умножить число 111111111 на 2. В результате у нас получается 888888888. Давай напишем код и проверим: public class Main { public static void main (String args) { System. out. println (111111111 << 3 ) ; } } Вывод в консоль: 888888888

Сдвиги вправо

Они обозначаются знаком >> . Делают то же самое, только в другую сторону! :) Не будем изобретать велосипед и попробуем сделать это с тем же числом int 64. public class Main { public static void main (String args) { int x = 64 ; //значение int y = 2 ; //количество int z = (x >> y) ; System. out. println (z) ; } }

В результате сдвига на 2 вправо два крайних нуля нашего числа вышли за пределы диапазона и затерлись. У нас получилось число 10000, которому в десятичной системе соответствует число 16 Вывод в консоль: 16 Простое правило для запоминания сдвигов вправо: При каждом сдвиге вправо выполняется деление на два с отбрасыванием любого остатка. Например, 35 >> 2 означает, что нам нужно 2 раза разделить 35 на 2, отбрасывая остатки 35/2 = 17 (отбросили остаток 1) 17:2 = 8 (отбросили остаток 1) Итого, 35 >> 2 должно быть равно 8. Проверяем: public class Main { public static void main (String args) { System. out. println (35 >> 2 ) ; } } Вывод в консоль: 8

Приоритет операций в Java

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

Operator Precedence

Operators Precedence
postfix expr++ expr--
unary ++expr --expr +expr ~ !
Multiplicative * / %
additive + -
shift << >> >>>
relational < > <= >= instanceof
equality == !=
bitwise AND &
bitwise exclusive OR ^
bitwise inclusive OR |
logical AND &&
logical OR ||
ternary ? :
assignment = += -= *= /= %= &= ^= |= <<= >>= >>>=
Все операции выполняются слева направо , однако с учетом своего приоритета. Например, если мы пишем: int x = 6 - 4/2; вначале будет выполнена операция деления (4/2). Хоть она и идет второй по счету, но у нее выше приоритет . Круглые или квадратные скобки меняют любой приоритет на максимальный. Это ты наверняка помнишь еще со школы. Например, если добавить их к выражению: int x = (6 - 4)/2; первым выполнится именно вычитание, поскольку оно вычисляется в скобках. У логического оператора && приоритет довольно низкий, что видно из таблицы. Поэтому чаще всего он будет выполняться последним. Например: boolean x = 6 - 4/2 > 3 && 12*12 <= 119; Это выражение будет выполняться так:
  • boolean x = 6 - 2 > 3 && 12 * 12 <= 119 ;

  • 12*12 = 144

    boolean x = 6 - 2 > 3 && 144 <= 119 ;
  • boolean x = 4 > 3 && 144 <= 119 ;

  • 4 > 3 = true

    boolean x = true && 144 <= 119 ;

  • 144 <= 119 = false

    boolean x = true && false ;

  • И, наконец, последним, будет выполнен оператор “И” && .

    boolean x = true && false;
    boolean x = false;

    Оператор сложения (+), например, имеет более высокий приоритет, чем оператор сравнения!= (“не равно”);

    Поэтому в выражении:

    Boolean x = 7 != 6+1;

    сначала будет выполнена операция 6+1, потом проверка 7!=7 (false), а в конце - присваивания результата false переменной x . У присваивания вообще самый маленький приоритет из всех операций - посмотри в таблице.

Фух! Лекция у нас получилась большая, но ты справился! Если ты не до конца понял какие-то части этой и предыдущей лекций - не переживай, мы еще не раз коснемся данных тем в будущем. Вот тебе несколько полезных ссылок:
  • Отличная в картинках про побитовые операции


  • - лекция JavaRush о логических и числовых операциях. Мы до них еще нескоро дойдем, но почитать можно уже сейчас, вреда не будет

Последнее обновление: 30.10.2018

Побитовые или поразрядные операции выполняются над отдельными разрядами или битами чисел. В данных операциях в качестве операндов могу выступать только целые числа.

Каждое число имеет определенное двоичное представление. Например, число 4 в двоичной системе 100, а число 5 - 101 и так далее.

К примеру, возьмем следующие переменны:

Byte b = 7; // 0000 0111 short s = 7; // 0000 0000 0000 0111

Тип byte занимает 1 байт или 8 битов, соответственно представлен 8 разрядами. Поэтому значение переменной b в двоичном коде будет равно 00000111 . Тип short занимает в памяти 2 байта или 16 битов, поэтому число данного типа будет представлено 16 разрядами. И в данном случае переменная s в двоичной системе будет иметь значение 0000 0000 0000 0111 .

Для записи чисел со знаком в Java применяется дополнительный код (two’s complement), при котором старший разряд является знаковым. Если его значение равно 0, то число положительное, и его двоичное представление не отличается от представления беззнакового числа. Например, 0000 0001 в десятичной системе 1.

Если старший разряд равен 1, то мы имеем дело с отрицательным числом. Например, 1111 1111 в десятичной системе представляет -1. Соответственно, 1111 0011 представляет -13.

Логические операции

Логические операции над числами представляют поразрядные операции. В данном случае числа рассматриваются в двоичном представлении, например, 2 в двоичной системе равно 10 и имеет два разряда, число 7 - 111 и имеет три разряда.

    & (логическое умножение)

    Умножение производится поразрядно, и если у обоих операндов значения разрядов равно 1, то операция возвращает 1, иначе возвращается число 0. Например:

    Int a1 = 2; //010 int b1 = 5;//101 System.out.println(a1&b1); // результат 0 int a2 = 4; //100 int b2 = 5; //101 System.out.println(a2 & b2); // результат 4

    В первом случае у нас два числа 2 и 5. 2 в двоичном виде представляет число 010, а 5 - 101. Поразрядное умножение чисел (0*1, 1*0, 0*1) дает результат 000.

    Во втором случае у нас вместо двойки число 4, у которого в первом разряде 1, так же как и у числа 5, поэтому здесь результатом операции (1*1, 0*0, 0 *1) = 100 будет число 4 в десятичном формате.

    | (логическое сложение)

    Данная операция также производится по двоичным разрядам, но теперь возвращается единица, если хотя бы у одного числа в данном разряде имеется единица (операция "логическое ИЛИ"). Например:

    Int a1 = 2; //010 int b1 = 5;//101 System.out.println(a1|b1); // результат 7 - 111 int a2 = 4; //100 int b2 = 5;//101 System.out.println(a2 | b2); // результат 5 - 101

    ^ (логическое исключающее ИЛИ)

    Также эту операцию называют XOR, нередко ее применяют для простого шифрования:

    Int number = 45; // 1001 Значение, которое надо зашифровать - в двоичной форме 101101 int key = 102; //Ключ шифрования - в двоичной системе 1100110 int encrypt = number ^ key; //Результатом будет число 1001011 или 75 System.out.println("Зашифрованное число: " +encrypt); int decrypt = encrypt ^ key; // Результатом будет исходное число 45 System.out.println("Расшифрованное число: " + decrypt);

    Здесь также производятся поразрядные операции. Если у нас значения текущего разряда у обоих чисел разные, то возвращается 1, иначе возвращается 0. Например, результатом выражения 9^5 будет число 12. А чтобы расшифровать число, мы применяем обратную операцию к результату.

    ~ (логическое отрицание)

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

    Byte a = 12; // 0000 1100 System.out.println(~a); // 1111 0011 или -13

Операции сдвига

Операции сдвига также производятся над разрядами чисел. Сдвиг может происходить вправо и влево.

    a<

    a>>b - смещает число a вправо на b разрядов. Например, 16>>1 сдвигает число 16 (которое в двоичной системе 10000) на один разряд вправо, то есть в итоге получается 1000 или число 8 в десятичном представлении.

    a>>>b - в отличие от предыдущих типов сдвигов данная операция представляет беззнаковый сдвиг - сдвигает число a вправо на b разрядов. Например, выражение -8>>>2 будет равно 1073741822.

Таким образом, если исходное число, которое надо сдвинуть в ту или другую строну, делится на два, то фактически получается умножение или деление на два. Поэтому подобную операцию можно использовать вместо непосредственного умножения или деления на два, так как операция сдвига на аппаратном уровне менее дорогостоящая операция в отличие от операции деления или умножения.

Сдвиг вправо без учета знака

Как было показано, при каждом выполнении операция » автоматически заполняет старший бит его предыдущим содержимым. В результате знак значения сохраняется. Однако иногда это нежелательно. Например, при выполнении сдвига вправо в каком-либо значении, которое не является числовым, использование дополнительных знаковых разрядов может быть нежелательным. Эта ситуация часто встречается при работе со значениями пикселей и графическими изображениями. Как правило, в этих случаях требуется сдвиг нуля в позицию старшего бита независимо от его первоначального значения. Такое действие называют сдвигом вправо без учета знака. Для его выполнения используют операцию сдвига вправо без учета знака Java, >>>, которая всегда вставляет ноль в позицию старшего бита.

Следующий фрагмент кода демонстрирует применение операции >>>. В этом примере значение переменной а установлено равным -1, все 32 бита двоичного представления которого равны 1. Затем в этом значении выполняется сдвиг вправо на 24 бита с заполнением старших 24 битов нулями и игнорированием обычно используемых дополнительных знаковых разрядов. В результате значение а становится равным 255.

int а = -1;
а = а >>> 24;

Часто операция >>> не столь полезна, как хотелось бы, поскольку она имеет смысл только для 32- и 64-разрядных значений. Помните, что в выражениях тип меньших значений автоматически повышается до int. Это означает применение дополнительных знаковых разрядов и выполнение сдвига по отношению к 32-разрядным, а не 8- или 16-разрядным значениям. То есть программист может подразумевать выполнение сдвига вправо без учета знака применительно к значению типа byte и заполнение нулями, начиная с бита 7.

Однако в действительности это не так, поскольку фактически сдвиг будет выполняться в 32-разрядном значении. Этот эффект демонстрирует следующая программа.

// Сдвиг без учета знака значения типа byte.
class ByteUShift {
static public void main(String args) {
char hex = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
byte b = (byte) 0xfl;
byte с = (byte) (b » 4);
byte d = (byte) (b >» 4) ;
byte e = (byte) ((b & 0xff) » 4) ;
System.out.println (" b = 0x" + hex [ (b » 4) & 0x0f] + hex ) ;
System, out .println (" b » 4 = 0x" + hex[ (c » 4) & 0x0f] + hex) ;
System, out .println (" b »> 4 = 0x" + hex[ (d » 4) & 0x0f] + hex) ;
System.out.println(" (b & 0xff) » 4 = 0x" + hex[(e » 4) S 0x0f] + hex) ;
}
}

Из следующего вывода этой программы видно, что операция »> не выполняет никаких действий по отношению к значениям типа byte. Для этого примера в качестве значения переменной b было выбрано произвольное отрицательное значение типа byte. Затем переменной с присваивается значение переменной b типа byte, сдвинутое вправо на четыре позиции:, которое в связи с применением дополнительных знаковых разрядов равно Oxff. Затем переменной d присваивается значение переменной b типа byte, сдвинутое вправо на четыре позиции без учета знака, которым должно было бы быть значение OxOf, но в действительности, из-за применения дополнительных знаковых разрядов во время повышения типа b до int перед выполнением сдвига, равное Oxff. Последнее выражение устанавливает значение переменной е равным значению типа byte переменной Ь, замаскированному до 8 бит с помощью операции AND и затем сдвинутому вправо на четыре позиции, что дает ожидаемое значение, равное OxOf. Обратите внимание, что операция сдвига вправо без учета знака не применялась к переменной d, поскольку состояние знакового бита после выполнения операции AND было известно.

Побитовые составные операции с присваиванием

Подобно алгебраическим операциям, все двоичные побитовые операции имеют составную форму, которая объединяет побитовую операцию с операцией присваивания. Например, следующие два оператора, выполняющие сдвиг вправо на четыре позиции в значении переменной а, эквивалентны:

Аналогично, эквивалентны и следующие два оператора, которые присваивают переменной а результат выполнения побитовой операции a OR b:

а = а | b;
а |= b;

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

class OpBitEquals public static void main(String args){ int a = 1;
int b = 2;
int c = 3;
a |= 4;
b >= 1;
c a ^= c;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}

Эта программа создает следующий вывод.



Рекомендуем почитать

Наверх