Арифметические операции. Беззнаковая арифметика в Java

В Java есть операторы сдвига. Операторы << и >> позаимствованы из С/C++. Кроме того, Java обладает своим новым оператором сдвига >>>.

Операторы сдвига присущи системам, которые могут выравнивать биты, прочтённые из IO портов или зартсываемые в IO порты. Это также быстрое умножение или деление на степень двойки. Преимущество операторов сдвига в Java - это независимость от платформы. Поэтому вы можете использовать их не беспокоясь ни о чём.

Основы сдвига

Сдвиг - это, по сути, простейшая операция: мы берём последовательность битов и двигаем её влево или вправо. Больше всего конфуза вызывает оператор >>>. Но о нём мы поговорим чуть позже.

Операторы сдвига могут применяться лишь к целым числам, то есть к типам int или long . Следующая таблица иллюстрирует базовый механизм сдвига.

Таблица 1: Идея сдвига

Исходные данные
Бинарное представление 00000000 00000000 00000000 11000000
Сдвиг влево на 1 бит 00000000 00000000 00000001 1000000?
Сдвиг вправо на бит ?0000000 00000000 00000000 01100000 0
Сдвиг влево на 4 бита 0000 00000000 00000000 00001100 0000????
Исходные данные
Бинарное представление 11111111 11111111 11111111 01000000
Сдвиг влево на 1 бит 11111111 11111111 11111110 1000000?
Сдвиг вправо на бит ?1111111 11111111 11111111 10100000 0

Таблица показывает фундаментальную идею сдвига: перемещение битов относительно их позиций. Это как очередь в магазине: как только один человек совершил покупку и отшёл, вся очередь сдвинулась и позиции всех участников очереди изменились.

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

  1. Что происходит, если мы сдвигаем влево и при этом часть бинарной записи выходит за границу слева, а часть - остаётся пустой справа?
  2. Что происходит, когда справа - выход за границы, а слева - пустое место?
  3. Какое истинное значение принимает знак "?"?.

Ответим на часть этих вопросов. Биты, вышедшие за границы, просто теряются. Мы о них забываем.

В некоторых языках, типа ассемблер, есть операция ротации , когда при сдвиге вышедшие за границы биты не теряются, но ставятся на освободившееся место (вместо вопросиков). Однако языки высокого уровня, типа Java, не имеют в своём арсенале такой операции.

Сдвиг отрицательных чисел

Ответ на вопрос о значении символов "?" в приведенной выше таблице требует отдельного рассмотрения.

В случае сдвига влево << и беззнакового сдвига вправо >>> новые биты просто устанавливаются в ноль. В случае сдвига вправо со знаком >> новые биты принимают значение старшего (самого левого) бита перед сдвигом. Следующая таблица демонстрирует это:

Таблица 2: Сдвиг положительных и отрицательных чисел

Исходные данные
Бинарное представление 00000000 00000000 00000000 11000000
Сдвиг вправо на 1 бит 00000000 00000000 00000000 01100000
Сдвиг вправо на 7 бит 00000000 00000000 00000000 00000001
Исходные данные
Бинарное представление 11111111 11111111 11111111 01000000
Сдвиг вправо на 1 бит 11111111 11111111 11111111 10100000
Сдвиг вправо на 7 бит 11111111 11111111 11111111 11111110

Заметьте: в том, случае, где старший бит был 0 перед сдвигом, новые биты стали тоже 0. Там где старший бит перед сдвигом был 1, новые биты тоже заполнились 1.

Это правило может показаться странным на первый взгляд. Но оно имеет под собой очень серьёзное обоснование. Если мы сдвигаем бинарное число влево на одну позицию, то в десятичной записи мы умножаем его на два. Если мы сдвигаем влево на n позиций, то умножение происходит на 2 n , то есть на 2, 4, 8, 16 и т.д.

Сдвиг вправо даёт деление на степени двойки. При этом, добавление слева нулей на появившиеся биты на самом деле даёт деление на степени двойки лишь в случае положительных чисел. Но для отрицательных чисел всё совсем по другому!

Как известно, старший бит отрицательных чисел равен единице, 1. Для того, чтобы сохранить старшинство единицы при сдвиге, то есть сохранить отрицательный знак результата деления отрицательного числа на положительное (степень двойки), нам нужно подставлять единицы на освободившиеся места.

Если мы посмотрим на Таблицу 2, то заметим, что 192, сдвинутое на 1 бит вправо - это 192/2=96, а сдвинутое на 7 битов вправо - это 192/2 7 =192/128=1 по законам целочисленной арифметики. С другой стороны, -192 сдвинутое на 1 бит вправо - это 192/2=-96 и т.д.

Есть, однако пример, когда реультат сдвига вправо отличается от результата целочисленного деления на 2. Это случай, когда аргумент = -1. При целочисленном делении мы имеем: -1/2=0. Но результат сдвига вправо нам даёт -1. Это можно трактовать так: целочисленное деление округляет к нулю, а сдвиг округляет к -1.

Таким образом, сдвиг вправо имеет две ипостаси: одна (>>>) просто сдвигает битовый паттерн "в лоб", а другая (>>) сохраняет эквивалентность с операцией деления на 2.

Зачем же Java потребовался беззнаковый сдвиг вправо (сдвиг "в лоб"), когда ни в С, ни в С++ его не существует? Ответ прост, потому что в С и С++ сдвиг всегда беззнаковый . То есть >>>> в Java - это и есть сдвиг вправо в C и C++. Но, поскольку в Java все численные типы со знаком (за исключением char ), то и результаты сдвигов должны иметь знаки.

Сокращение (reduction) правого операнда

На самом деле у операторов сдвига есть правый операнд - число позиций, на которое нужно произвести сдвиг. Для корректного сдвига это число должно быть меньше, чем количество битов в результате сдвига. Если число типа int (long) , то сдвиг не может быть сделан более, чем на 32 (64) бита.

Оператор же сдвига не делает никаких проверок данного условия и допускает операнды, его нарушающие. При этом правый операнд сокращается по модулю от нужного количества битов. Например, если вы захотите сдвинуть целое число на 33 бита, то сдвиг произойдёт на 33%32=1 бит. В результатае такого сдвига мы легко можем получить аномальные результаты, то есть результаты, которых мы не ожидали. Например, при сдвиге на 33 бита мы ожидаем получить 0 или -1 (в знаковой арифметике). Но это не так.

Почему Java сокращает правый операнд оператора сдвига или грустная история о заснувшем процессоре

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

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

Основным применением данных процессоров был контроль систем реального времени. В данных системах самый быстрый ответ на внешнее событие должно занимать не более задержки на прерывание (interrupt latency ). Отдельные инскрукции таких процессоров были неделимы. Поэтому выполнение длинных операций (сдвига на несколько бит и ротации) нарушало эффективную работу процессора.

Следующая версия процессора имплементировала эти операции уже по-другому: размер правого операнда сократился. Задержка на прерывание восстанавилась. И многие процессоры переняли данную практику.

Арифметическое распространение (promotion ) операндов

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

Следующая таблица показывает пример аномалии:

Таблица 3: Арифметическое распространение для беззнакового сдвига вправо, когда операнд меньше, чем int

Исходные данные (-64 в десятичной записи)
Распространение до int 11111111 11111111 11111111 11000000
Сдвиг вправо на 4 битa 00001111 11111111 11111111 11111100
Сокращение до байта 11111100
Ожидаемый результат был 00001100

Конспект лекций по Java. Занятие 4

(none) (none) ::
(none)
( В.Фесюнов )

Операции (operators) в языке Java

Большинство операций Java просты и интуитивно понятны. Это такие операции, как +, -, *, /, <,> и др. Операции имеют свой порядок выполнения и приоритеты. Так в выражении

сначала выполняется умножение, а потом сложение, поскольку приоритет у операции умножения выше, чем у операции сложения. В выражении

сначала вычисляется a + b, а потом от результата вычитается c, поскольку порядок выполнения этих операций слева направо.

Но операции Java имеют и свои особенности. Не вдаваясь в детальное описание простейших операций, остановимся на особенностях.

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

  • Операция присваивания обозначается символом "=". Она вычисляет значение своего правого операнда и присваивает его левому операнду, а также выдает в качестве результата присвоенное значение. Это значение может быть использовано другими операциями. Последовательность из нескольких операций присваивания выполняется справа налево.

В простейшем случае все выглядит как обычно.

Здесь происходит именно то, что мы интуитивно подразумеваем, — вычисляется сумма a и b, результат заносится в x. Но вот два других примера.

В первом сначала 1 заносится в b, результатом операции является 1, потом этот результат заносится в a. Во втором примере вычисляется сумма a и b и результат теряется. Это бессмысленно, но синтаксически допустимо.

Операции сравнения

Это операции >, <, >=, <=, != и ==. Следует обратить внимание, что сравнение на равенство обозначается двумя знаками "=". Операндами этих операций могут быть арифметические данные, результат — типа boolean.

Операции инкремента, декремента

Это операции ++ и --. Так y++ (инкремент) является сокращенной записью y = y +1, аналогично и с операцией декремента (--).

Но с этими операциями есть одна тонкость. Они существуют в двух формах - префиксной (++y) и постфиксной (y++). Действие этих операций одно и то же — они увеличивают (операции декремента — уменьшают) свой операнд на 1, а вот результат у них разный. Префиксная форма в качестве результата выдает уже измененное на 1 значение операнда, а постфиксна -значение операнда до изменения.

A = 5; x = a++; y = ++a;

В этом фрагменте x получит значение 5, а y — 7.

Операция целочисленного деления

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

Остаток от деления (значение по модулю)

В Java имеется операция %, которая обозначает остаток от деления.

Расширенные операции присваивания

Кроме обычной операции "=" в Java существуют операции +=, -=, *=, /= и др. Это сокращенные записи. Так a += b полностью эквивалентна a = a + b. Аналогично и с другими такими операциями.

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

! — отрицание && — логическое "и" || — логическое "или"

Операнды этих операций должны быть типа boolean, результат — boolean. Операции && и || имеют одну особенность — их правый операнд может и не вычислиться, если результат уже известен по левому операнду. Так, если левый операнд операции && — ложь (false), то правый операнд вычисляться не будет, т.к. результат все равно — ложь.

Это свойство нужно учитывать, особенно тогда, когда правый операнд содержит вызов некоторой функции.

Побитовые логические операции

Это операции

& — побитовое "и" | — побитовое "или" ^ — побитовое "исключающее или" ~ — побитовое отрицание

Они выполняются для каждой пары битов своих операндов.

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

<< — сдвиг влево >> — сдвиг вправо >>> — беззнаковый сдвиг вправо

Эти операции сдвигают значение своего левого операнда на число бит, заданное правым операндом.

Условная операци

Это единственная тернарная операция, т.е. операция, имеющая три операнда. Соответственно, для нее используется не один знак операции, а два.

<условие> ? <выражение1> : < выражение2>

Если <условие> истинно, то результатом будет < выражение1>, иначе < выражение2>.

Например, "a < b ? a: b" вычисляет минимум из a и b.

Операция приведения типов

Это очень важная операция. По умолчанию все преобразования, которые могут привести к проблемам, в Java запрещены. Так, нельзя long-значение присвоить int-операнду. В тех случаях, когда это все же необходимо, нужно поставить явное преобразование типа.

Например, пусть метод f(...) выдает long.

int x = (int)f(10);

Здесь (int) — это операция преобразования типа. Операция преобразования типа обозначается при помощи имени типа, взятого в скобки.

Эта операция применима не только к базовым типам, но и к классам. Мы разберем это подробнее, когда будем рассматривать наследование.

Литералы (константы)

Арифметические

Примеры арифметических констант

10 - 010 — это 8 - 0123 — это 83 (1*64 + 2*8 + 3) - 0x10 — это 16 - 0x123 — это 291 (1*256 + 2*16 +3) - 1e5 — это 100000 - 1.23e-3 — это 0.00123

Для указания типа константы применяются суффиксы: l (или L) — long, f (или F) — float, d (или D) — double. Например,

1L — единица, но типа long.

Логические литералы

Логические литералы — это true (истина) и false (ложь)

Строковые литералы

Записываются в двойных кавычках, например

"это строка"

Символьные литералы

Записываются в апострофах, например "F", "ш".

В строковых и символьных литералах есть правила для записи спец. символов. Во-первых, есть набор предопределенных спец. символов. Это

- "\n" — конец строки (перевод строки) - "\r" — возврат каретки - "\t" — табуляция

и ряд других.

Во-вторых, можно явно записать код символа (нужно только знать его). Запись кода обычно выполняется в восьмеричной системе: "\001" — символ с кодом 1 и т.п.

Операторы (statements)

Оператор — выражение

Синтаксис

<выражение>;

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

A = 0; x = (a > b ? a: b); cnt++;

Условный оператор (if)

Синтаксис

If (<условие>) <оператор1>

Здесь <условие> — это логическое выражение, т.е. выражение, возвращающее true или false. Как видно из синтаксиса, часть else вляется необязательной. После if и после else стоит по одному оператору. Если нужно поместить туда несколько операторов, то нужно поставить блок . Блок начинается с "{" и заканчивается "}".

В Java принято блок ставить всегда, даже если после if или else стоит один оператор.

If (a > b) { x = a; } else { x = b; } if (flag) { flag = false; init(); }

В последнем примере flag — логическая переменная или поле, init() -метод, вызываемый, если флаг равен true (говорят, "если flag установлен").

Оператор return (уже рассматривали)

Оператор цикла по предусловию (while)

Синтаксис

While (<условие>) <оператор>

Как и в случае оператора if, в Java принято <оператор> заключать в фигурные скобки.

Int i = 0; while (more) { x /= 2; more = (++i < 10); }

В этом примере more должна быть логической переменной или полем, x — некоторой арифметической переменной или полем. Цикл выполняется 10 раз.

Оператор цикла по постусловию (do while)

Синтаксис

Do <оператор> while (<условие>);

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

Int i = 0; do { x \= 2; more = (++i < 10); } while (more);

Оператор цикла "со счетчиком" (for)

Синтаксис

For (<инициализация>; <условие>; <инкремент>) <оператор>

Заголовок такого цикла содержит три выражения (в простейшем случае). Из наименования оператора можно понять, что он служит для организации цикла со счетчиком. Поэтому выражение <инициализация> выполняется один раз перед первым витком цикла. После каждого витка цикла выполняется выражение <инкремент>, а потом выражение <условие>. Последнее выражение должно быть логическим и служит для задания условия продолжения цикла. Т.е. пока оно истинно, витки цикла будут продолжаться.

Для удобства составления операторов цикла со счетчиком в данной конструкции введено множество расширений.

  • <инициализация> может быть не выражением, а описанием с инициализацией типа "int i = 0".
  • <инициализация> может быть списком выражений через запятую, например, "i = 0, r = 1" .
  • <инкремент> тоже может быть списком выражений, например, "i++, r*=2"
  • Все составляющие (<инициализация>, <условие> и <инкремент>) являются необязательными. Для выражения <условие> это означает, что условие считается всегда истинным (т.е. выход из цикла должен быть организован какими-то средствами внутри самого цикла).

Т.е. допустим и такой цикл (бесконечный цикл):

For (;;) { . . . }

For (int i = 0; i < 10; i++) x /=2;

Это самая "экономичная" реализация цикла из предыдущих примеров.

Операторы break и continue

В Java нет операторов goto. Как известно, goto приводит к появлению неструктурированных программ. Операторы break и continue являются структурированными аналогами goto.

Они могут применяться в циклах, а break еще и в операторе выбора (switch). Выполнение оператора break приводит к немедленному завершению цикла. Оператор continue вызывает окончание текущего витка цикла и начало нового. Проверка условия в этом случае все же выполняется, так что continue может вызвать и окончание цикла.

For (int i = 0; ;i++) { if (oddOnly && i%2 == 0) continue; y = (x + 1)/x; if (y — x < 0.001) break; x = y; }

Здесь oddOnly — логическая переменная. Если она установлена, то все витки цикла с четными номерами пропускаются с использованием оператора continue;

Условие окончания цикла в данном примере проверяется в середине цикла и, если оно выполнено, то цикл прекращается при помощи оператора break.

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

Lbl: while (...) { . . . for (...) { . . . if (...) break lbl; } . . . }

Здесь оператор break вызовет прекращение как цикла for, так и while.

Оператор выбора (switch)

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

Синтаксис

Switch (<выражение>) { case <константа1>: <операторы1> case <константа2>: <операторы2> . . . }

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

Элементы "case <константа>:" являются метками перехода, если значение выражения совпадает с константой, то будет осуществлен переход на эту метку. Если значение выражения не совпадает ни с одной из констант, то все зависит от наличия фрагмента default. Если он есть, то переход происходит на метку default, если его нет, то весь оператор switch пропускается.

  • В операторе switch фрагменты case не образуют какие-либо блоки. Если после последнего оператора данного case-фрагмента стоит следующий case, то выполнение будет продолжено, начиная с первого оператора этого case-фрагмента.
  • В силу этого в операторе switch обычно применяется оператор break. Он ставится в конце каждого case-фрагмента.

Пример (файл SymbolTest.java)

Рассмотрим демонстрационную программу, в которой использованы операторы for и switch.

Эта программа генерирует случайным образом 100 символов латинского алфавита и классифицирует их как "гласные", "согласные" и "иногда гласные". В последнюю категорию отнесены символы "y" и "w".

Public class SymbolTest { public static void main(String args) { for (int i = 0; i < 100; i++) { char c = (char)(Math.random()*26 + "a"); System.out.print(c + ": "); switch (c) { case "a": case "e": case "i": case "o": case "u": System.out.println("гласная"); break; case "y": case "w": System.out.println("иногда гласная"); break; default: System.out.println("согласная"); } } } }

В данном примере есть несколько новых для нас элементов.

  • Используется метод random() класса Math. Посмотрим документацию по классу Math и разберемся, что он делает.
  • В операторе char c = (char)(Math.random()*26 + "a"); производится сложение арифметического значения с символом. При таком сложении в Java символ преобразуется в число, которое равно коду этого символа.
  • В операторе System.out.print(c + ": "); используется не println(...), а print(...). Метод print(...) отличается от println(...) только тем, что не переводит печать на новую строку, так что следующий оператор print(...) или println(...) продолжит печать в той же строке.

Следует также обратить внимание на фрагменты case. Формально здесь 7 таких фрагментов, но 5 из них не содержат никаких операторов. Так что, можно считать, что здесь 2 case-фрагмента, но каждый из них имеет несколько меток case.

Задание на дом

  • 1 Изменить SymbolTest.java так, чтобы количество генерируемых символов задавалось параметром вызова программы.
  • 2 Написать программу, которая в качестве параметров вызова принимает два числа - длины катетов прямоугольного треугольника, а в качестве результата печатает углы в градусах.


(none)

1. Основные арифметические операции

В следующей таблице перечислены основные арифметические операции, применяемые в языке Java:

Рассмотрим некоторые правила работы с арифметическими операциями:

  • Выражения вычисляются слева направо, если не добавлены круглые скобки или одни операции имеют более высокий приоритет.
  • Операции *, /, и % имеют более высокий приоритет чем + и -.

Пример 1. Арифметические операции над целочисленными значениями

Например, в этом коде, переменные a и b будут иметь разные значения:

Public class BasicIntMath { public static void main(String args) { int a = 4 + 5 - 2 * 3; int b = 4 + (5 - 2) * 3; System.out.println("a = " + a); System.out.println("b = " + b); } }

Результат выполнения:

A = 3 b = 13

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

Пример 2. Унарные операции сложения и вычитания

public class UnarySignOperation { public static void main(String args) { double a = -6; double b = +6; System.out.println(a); System.out.println(b); } }
  • Когда операция деления выполняется над целочисленным типом данных, ее результат не будет содержать дробный компонент.

Пример 3. Деление целочисленных чисел

public class IntDivision { public static void main(String args) { int a = 16 / 5; System.out.println(a); } }

Результат выполнения этой программы:

  • Операнды арифметических операций должны иметь числовой тип. Арифметические операции нельзя выполнять над логическими типами данных, но допускается над типами данных char , поскольку в Java этот тип, по существу, является разновидностью типа int .

Пример 4. Арифметические операции над переменными типа char

public class BasicCharMath1 { public static void main(String args) { char c = "n"; System.out.println(c); System.out.println(c + 1); System.out.println(c / 5); } }

Результат выполнения:

N 111 22

Пример 5. Арифметические операции над переменными типа char

public class BasicCharMath2 { public static void main(String args) { char c1 = "1"; char c2 = "\u0031"; char c3 = 49; System.out.println(c1 + c2 + c3); } }

Результат выполнения:

    Оператор деления по модулю — обозначается символом %. Этот оператор возвращает остаток от деления первого числа на второй. При делении целого числа результатом будет тоже целое число.

Пример 6. Деление по модулю

public class DivisionByModule { public static void main(String args) { int a = 6 % 5; double b = 6.2 % 5.0; System.out.println(a); System.out.println(b); } }

Результат выполнения:

1 1.2000000000000002

2. Составные арифметические операции с присваиванием

В Java имеются специальные операции, объединяющие арифметические операции с операцией присваивания. Рассмотрим следующее выражение:

А = а + 4;

B Java эту операцию можно записать следующим образом:

А += 4;

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

Пример 5. Составные арифметические операции с присваиванием

public class CompoundOperations { public static void main(String args) { int a = 1; int b = 2; int c = 3; a += 3; b *= 2; c += a * b; System.out.println(a); System.out.println(b); System.out.println(c); } }

Последнее обновление: 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.

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