Bytes and bytes:
How computers (and Java) represent numbers
For many simple programs, it’s not too important to know exactly how computers represent numbers. It’s often enough to know things in basic terms, such as:
- «a Java int can store a moderately large whole number, positive or negative; it’s enough for most loop counters, counting the number of words in a document, storing pixel coordinates etc»
- «a Java long can store a whole number with twice the magnitude of an int; it’s more appropriate for larger numbers such as the number of potential bytes in a file, the numbe of milliseconds since a particular point in time etc»
When do you need to know about how computers represent numbers.
Sometimes it’s useful to have a more precise idea of just how numbers are represented by the computer «under the hood». This can be important in cases such as:
- understanding certain algorithms, such as how to create a hash code or random number generator;
- understanding certain optimisations that are possible, such as using an AND operation instead of a division/modulus, using bit tests to calculate a logarithm, or combining a test for various variables being negative into a single comparison;
- manipulating data, such as when the red/green/blue values of the pixel in an image are combined into a single integer;
- performing data conversion, for example converting data from a system that stores numbers in one format to be compatible with another system that expects them in another format;
- for reducing storage space required by data.
On the next page, we look at binary representation used by computers to store and manipulate numbers. If you’re already familiar with binary notation in principle (e.g. because you’ve studied mathematics), you may prefer to skip straight on to look at how binary digits are grouped into units in computing, starting with bits and bytes.
Written by Neil Coffey. Copyright © Javamex UK 2008. All rights reserved.
Java: bits and bytes
Если люди считают в десятичной системе счисления, то компьютеры — в двоичной. А программист должен понимать,как говорить и с людьми, и с компьютерами. Данный обзор должен помочь в этом деле. Порой за очевидными вещами кроется целый мир. Предлагаю об этом мире поговорить. Например, в неделе 7 дней. А теперь, ответим на вопрос: Что такое число «7»? ) Во-первых, это целое (integer) положительное (positive) натуральное (natural) число. А ещё это десятичное число (decimal number). Десятичное число — это число в десятичной системе счисления (Decimal System). Когда мы говорим «десятичная система счисления», это означает, что у системы счисления основание (base) равно 10. Основание показывает, сколько цифр может быть использовано в данной системе счисления для представления числа. Отсчёт ведётся от нуля. Соответственно, для представления чисел в десятичной системе счисления мы используем цифры от 0 до 9. Это хорошо, но считать нужно не только до 9, но и дальше. Как же быть? Например, число 10. Для записи данного числа мы используем уже целых 2 цифры. Позиция каждой цифры в десятичной системе называется десятичным разрядом (decimal place). Разряды отсчитываются справа налево:
Число ведь по сути растёт справа на лево. То есть сначала было 7, а потом стало 10. Поэтому и разряды считаются справа, начиная с нуля. А для чего всё это? Всё потому, что мы — не компьютеры. И если мы считаем в десятичной системе (то есть, по основанию 10), компьютеры считают в двоичной системе счисления (то есть, по основанию 2). Но правила, которые действуют в этих системах счисления, одинаковы.
Двоичная система
Двоичная система очень похожа на десятичную с тем лишь отличием, что ограничение здесь не 10, а 2. Давайте сравним на примере. Как нам представить 11 в двоичной системе? Очень просто: надо лишь разделить десятичное число на основание 2, то есть посчитать 11/2 в столбик. Пример:
Интересно, что мы можем число представить в двоичной системе так же, как и в десятичной: 111 в двоичной системе = 1*2^2 + 1*2^1 + 1*2^0 = 4 + 2 + 1
Пример перевода из двоичной системы в десятичную можно увидеть в онлайн калькуляторе. Говоря про то, что правила работы в системах счисления одинаковы, давайте посмотрим на сложение в двоичной системе:
И тут возникает вопрос: а как же быть с отрицательными числами? Чтобы понять это, поговорим о том, как представлены байты в Java
Java и Byte
Как же так получается, что в Java мы можем использовать отрицательные числа? Сделано это просто. В Java байты знаковые. Крайний левый разряд/бит (его ещё называют «старший бит») сделан своего рода «маркером», отвечающим на вопрос: «Это число отрицательное?». Если ответ да, значит маркер имеет значение 1. А иначе — 0. Давайте посмотрим на примере, как превратить число 5 в отрицательное число 5:
- если если прибавить единицу к 127, мы получим уже -128.
- если вычесть единицу из -128, мы получим 127.
Работа с байтами активно используется при работе с I/O Streams. Подробнее можно прочитать в tutorial от Oracle: «I/O Streams». Кроме того, в Java можно использовать особый литерал, чтобы значение указывать в виде битов:
Bit Manipulation
Затрагивая байты и биты, нельзя не упомянуть о различных манипуляциях с битами. Наверно, самая распространённая операция — это сдвиги (bitwise shift или bit-shift). А всё потому, что их результат имеет явную практическую пользу. Какая польза? Сдвиг влево на N позиций эквивалентен умножению числа на 2N. А сдвиг вправо аналогичен такому же делению.Таким образом, 5
Побитовое отрицание NOT (Unary bitwise), которое обозначается как тильда, инвертирует биты. Записывается как тильда, например ~5.
public static void main(String[] args) < System.out.println(~5); //-6 System.out.println(~-5);//4 >
Это лишний раз показывает, что когда Java меняет знак у числа, кроме инверсии значений битов в самом конце выполняем +1. А без этого, как мы видим, наше число 5 изменяется. И чтобы оно осталось тем же числом, что и до смены знака, надо делать +1. Побитовый И (Bitwise AND) позволяет из двух разных чисел оставить значение 1 для бита только тогда, во всех битах стоит единица. Интересно это может быть тем, что у этого есть польза для примененеия:
При помощи совместного использования побитового И (Bitwise AND) и побитового ИЛИ (Bitwise OR) можно использовать маски:
public static void main(String[] args) < byte optionA=0b0100; byte optionB=0b0010; byte optionC=0b0001; byte value = (byte)(optionB | optionC); // Check for optionB if ((optionC & value) != 0b0000) < System.out.println("Yes"); >else < System.out.println("No"); >>
Подробнее см. «Masking options with bitwise operators in Java». Манипуляции с битами — занятная тема, по которой пишутся отдельные обзоры, статьи и книги. И отсюда начинается длинный путь в криптографию. В рамках данного обзора стоит понимать, почему это работает и как. Подробнее про битовые операции рекомендую почитать обзор от tproger: «О битовых операциях».
Примитивные типы
Итак, байт — это октет, то есть 8 бит. Легко запомнить, что в Java существует тоже 8 примитивных типов, так уж совпало. Примитивным типом называется тип данных, который встроен в язык программирования, то есть доступен по умолчанию. byte — минимальный по объёму занимаемой памяти примитивный тип данных, с которым может работать Java. Как мы ранее говорили, байт занимает 8 бит. Следовательно, старший разряд имеет номер 7. Поэтому, byte содержит значения от -2 в 7 степени до 2 в 7 степени минус 1 из результата. Какие же ещё есть примитивные типы:
Как мы видим по таблице, типы данных по объёму занимаемых данных растут в два раза. То есть short = 2 * byte, а int = 2 * short. Запомнить, на самом деле, легко. То, что байт = 8 бит, запомнили. То, что меньше быть не может — тоже запомнили. В английском языке целое число называется integer. Примитивный тип от него назвали сокращением int. Есть обычное целое число — int. Есть его коротка версия short и длинная версия long. Соответственно int занимает 32 бита (4 байта). Короткая версия в 2 раза меньше -— 16 бит (2 байта), а длинная — в два раза больше, т.е. 64 бита (8 байт). Таким образом, int максимум может хранить число примерно в 2 миллиарда и сто миллионов. А long максимум может хранить примерно 9 квадриллионов (красивое слово). Вспоминая бородатый анекдот про то, что начинающий программист думает, что в килобайте 1000 байт, а законченный программист считает, что в килограмме 1024 грамм, можем понять:
1 mb = 1024 Kbyte = 1024 * 1024 = 1048576 bytes 1 int = 4 bytes 1 mb = 262144 int
Кстати, внимательный читатель мог заметить, что на картинке всего 7 типов. 8 примитивный тип — это boolean. boolean — это логический тип данных, который имеет всего два значения: true и false. Но возникает вопрос — какого он размера? Ответит нам Java Virtual Machine Specifiaction и раздел «2.3.4. The boolean Type»:
То есть просто boolean будет занимать столько же, сколько и int. Если же мы объявим массив из boolean, то каждый элемент массива будет занимать 1 байт. Вот такие вот чудеса 🙂