Данная часть посвящена изучению работы с числами на более глубоком уровне. В ней рассматривается машинное представление целых и вещественных чисел, эффективное для аппаратной реализации, а также объясняются особенности и проблемы, к которым приводит такое представление.
Позиционная
Например, число 2006 в десятичной
то есть слагаемых с различными степенями числа 10. По основанию степени - числу десять - система называется десятичной. Другие
При написании программ чаще всего используют десятичную, шестнадцатеричную (основание шестнадцать), восьмеричную (основание восемь) и двоичную (основание два) системы. Число различных знаков - цифр, используемых для записи чисел - в каждой системе равно основанию данной
| 0,1 | - цифры двоичной системы |
| 0,1,2,3,4,5,6,7 | - цифры восьмеричной системы |
| 0,1,2,3,4,5,6,7,8,9 | - цифры десятичной системы |
| 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F | - цифры шестнадцатеричной системы |
В шестнадцатеричной системе "обычных" десятичных цифр недостаточно, и для обозначения цифр, больших девяти, используются заглавные латинские буквы A,B,C,D,E,F.
В дальнейшем везде, где это необходимо, мы будем указывать основание
Существует множество непозиционных систем счисления, в которых числа изображаются и называются по своим правилам. Для римской системы чисел характерны сопоставление отдельного знака каждому большому числу ( V - пять, X - десять, L - пятьдесят, C - сто, M - тысяча ), повторение знака столько раз, сколько таких чисел во всем числе ( III - три, XX - двадцать), отдельные правила для предшествующих и последующих чисел (IV - четыре, VI - шесть, IX - девять). Множество непозиционных систем счисления связано с традиционными способами измерения конкретных величин - времени ( секунда, минута, час, сутки, неделя, месяц, год), длины ( дюйм, фут, ярд, миля, аршин, сажень), массы ( унция, фунт), денежных единиц. Выполнение арифметических действий в таких системах представляет собой крайне сложную задачу.
Приведем пример самой простой из возможных систем счисления – унарную. В ней имеется всего одна цифра 1. В унарной
Машинным словом называют порцию данных, которую процессор компьютера может обработать за одну операцию (
Представление чисел в двоичной и шестнадцатеричной
Целые числа в компьютере обычно кодируются в двоичном коде, то есть в
Показатель
Аналогично, $$6=1\cdot2^2+1\cdot2^1+0\cdot2^0=110_2,\; 2=10_2,\; 4=100_2,\; 8=1000_2,\; 9=1001_2$$, и так далее. Все очень похоже на обозначение чисел в десятичной
$$153=1\cdot10^2+5\cdot10^1+3\cdot10^0$$. Но только в качестве основания
В двоичной арифметике всего две цифры, 0 и 1. Двоичный код положительного целого числа – это коэффициенты разложения числа по степеням двойки.
Умножение числа на двоичное десять, то есть на $$10_2=2$$, приводит к дописыванию справа нуля в
Целочисленное деление на $$10_2$$ с отбрасыванием остатка производится путем отбрасывания последнего (младшего) бита, деление на $$100_2$$ - отбрасывания двух последних бит, и так далее. Обычно такие операции называют побитовыми сдвигами на n бит влево (умножение на $$2^n$$ ) или вправо (целочисленное деление на $$2^n$$ ).
Сложение двоичных чисел можно производить "в столбик" по полной аналогии со сложением десятичных чисел. Единственное отличие – то, что в двоичной арифметике только две цифры, 0 и 1, а не десять цифр (от 0 до 9) как в десятичной. Поэтому если в десятичной арифметике единицу более старшего разряда дает, к примеру, сложение 1 и 9, то в двоичной арифметике ее даст сложение 1 и 1. То есть
$$1_2+1_2=10_2$$(в десятичной системе это равенство выглядит как 1+1=2). Аналогично, $$10_2+10_2=100_2$$, и так далее.
Примеры сложения "в столбик":
$$\frac{+\substack{0110_2\\ 1011_2}}{10001_2}\quad \frac{+\substack{1100_2\\ 0010_2}}{1110_2}\quad \frac{+\substack{111_2\\ 001_2}}{1000_2}$$Совершенно аналогично выполняется умножение:
$$\cfrac{\;\;\;\;\;\times\substack{101_2\\ \;11_2}}{\cfrac{+\substack{\;\;\;101\\101}}{\;\;\;\;\;\;1111_2}}$$В машинной реализации целочисленного умножения используют побитовые сдвиги влево и сложения. Поскольку эти алгоритмы очень просты, они реализуются аппаратно.
Старший бит в целых без знака имеет обычный смысл, в целых со знаком – для положительных чисел всегда равен 0. В отрицательных числах старший бит всегда равен 1. В примерах для простоты мы будем рассматривать четырехбитную арифметику. Тогда в качестве примера целого положительного числа можно привести $$0110_2$$.
Для хранения отрицательных чисел используется
Надо отметить, что
Например, код числа (–1) в четырехразрядной арифметике будет $$1111_2$$, а в 8-разрядной арифметике будет $$11111111_2$$. Коды числа (–2) будут $$1110_2$$ и $$11111110_2$$, и так далее.
Для того, чтобы понять причину использования дополнительного кода, рассмотрим сложение чисел, представленных в
Сложение положительного и отрицательного чисел
Рассмотрим, чему равна сумма числа 1 и числа –1, представленного в
Таким образом, в четырехбитном представлении $$–1_{10}=1111_2$$.
Проверка:
$$n2+n=10000_2$$. Получившийся пятый разряд, выходящий за пределы четырехбитной ячейки, отбрасывается, поэтому в рамках четырехбитной арифметики получается $$n2+n=0000_2=0$$.
Аналогично
Очевидно, во всех этих случаях $$n2+n=0$$.
Что будет, если мы сложим $$3_{10}$$ и $$–2_{10}$$ (равное 1110_2, как мы уже знаем)?
$$\frac{+\substack{0011_2\\ 1110_2}}{\;\;10001_2}$$После отбрасывания старшего бита, выходящего за пределы нашей четырехбитовой ячейки, получаем $$0011_2 + 1110_2=0001_2$$, то есть $$3_{10} + (–2_{10})=1_{10}$$, как и должно быть.
Сложение отрицательных чисел
$$(-1)+(-1)=1111_2+1111_2=11110_2\to 1110_2$$ из-за отбрасывания лишнего старшего бита, выходящего за пределы ячейки. Поэтому $$(-1)+(-1)=1110_2=-2$$.
Вычитание положительных чисел осуществляется путем сложения положительного числа с отрицательным, получившимся из вычитаемого в результате его перевода в
Приведенные примеры иллюстрируют тот факт, что сложение положительного числа с отрицательным, хранящимся в
Несмотря на достоинства в двоичной машинной (аппаратной) арифметике имеются очень неприятные особенности, возникающие из-за конечной разрядности машинной ячейки.
Проблемы сложения положительных чисел
Пусть $$a=3_{10}=0011_2; b=2_{10}=0010_2; a+b=0101_2=5_{10}$$, то есть все в порядке.
Пусть теперь $$a=6_{10}=0110_2, b=5_{10}=0101_2. Тогда a+b =1011_2= -3_2$$.
То есть сложение двух положительных чисел может дать отрицательное, если результат сложения превышает максимальное положительное число, выделяемое под целое со знаком для данной разрядности ячеек! В любом случае при выходе за разрешенный диапазон значений результат оказывается неверным.
Если у нас беззнаковые целые, проблема остается в несколько измененном виде. Сложим $$8_{10}+8_{10}$$ в
При целочисленном умножении выход за пределы разрядности ячейки происходит гораздо чаще, чем при сложении или вычитании. Например, $$110_2\times 101_2=110_2\times 100_2+110_2\times 1_2=11000_2+110_2=11100_2$$. Если наша ячейка четырехразрядная, произойдет выход за ее пределы, и мы получим после отбрасывания лишнего бита $$1110_2=-2_{10}<0$$. Таким образом, умножение целых чисел легко может дать неправильный результат. В том числе – даже отрицательное число. Поэтому при работе с
byte m=10,n=10,k=10;
Тогда значения m*n, m*k и n*k будут лежать в разрешенном диапазоне -128..127. А вот m*n + m*k из него выйдет. Не говоря уж об m*n*k.
Если мы зададим
short m=10,n=10,k=10;
переполнения не возникнет даже для m*n*k. Однако, при m=n=k=100 значение m*n*k будет равно $$10^6$$, что заметно выходит за пределы разрешенного диапазона –32768..32767. Хотя m*n, m*k и n*k не будут за него выходить (но уже 4*m*n за него выйдет). Использование типа long поможет и в этом случае. Однако уже значения m=n=k=2000 (не такие уж большие!) опять приведут к выходу m*n*k за пределы диапазона. Хотя для m*n выход произойдет только при значениях около 50000.
Вычисление факториала с помощью целочисленной арифметики даст удивительные результаты! В таких случаях лучше использовать числа с плавающей точкой. Пример:
byte i=127, j=1, k; k=(byte)(i+j); System.out.println(k);
В результате получим число (-128). Если бы мы попробовали написать
byte i=127,j=1,k; System.out.println(i+j);
то получили бы +128. Напомним, что значения величин типа byte перед проведением сложения преобразуются в значения типа int.
Во время программирования различного рода внешних устройств, регистров процессора, битовыми масками, кодировке цвета, и так далее, приходится работать с кодами беззнаковых целых чисел. При этом использование десятичных чисел крайне неудобно из-за невозможности легкого сопоставления числа в десятичном виде и его двоичных бит. А использование чисел в двоичной кодировке крайне громоздко – получаются слишком длинные последовательности нулей и единиц. Программисты используют компромиссное решение – шестнадцатеричную кодировку чисел, где в качестве основания
В Java для того, чтобы отличать 16-ричные числа, как мы уже знаем, перед ними ставят префикс 0x: 0xFF обозначает $$FF_{16}$$, а 0x10 – это $$10_{16}$$, то есть 16.
Число N может быть записано с помощью разных систем счисления. Например, в десятичной:
или в двоичной:
$$N = B_n 2^n + ... + B_2 2^2 + B_1 2^1 + B_0 2^0\qquad (B_n = 0\text{ или }1)$$или в шестнадцатеричной:
$$N = C_n 16^n + ... + C_2 16^2 + C_1 16^1 + C_0 16^0\qquad (C_n = 0 .. F)$$Преобразование в другую систему счисления сводится к нахождению соответствующих коэффициентов. Например, $$B_n$$ по известным коэффициентам $$A_n$$ – при переводе из десятичной системы в двоичную, или коэффициентов $$A_n$$ по коэффициентам $$B_n$$ - из двоичной системы в десятичную.
Преобразование чисел из системы с меньшим основанием в систему с большим основанием
Рассмотрим преобразование из двоичной системы в десятичную. Запишем число N в виде
и будем рассматривать как алгебраическое выражение в десятичной системе. Выполним арифметические действия по правилам десятичной системы. Полученный результат даст десятичное представление числа N.
Пример:
Преобразуем $$01011110_2$$ к десятичному виду. Имеем:
$$01011110_2 = 0\cdot 2^7+1\cdot 2^6+0\cdot 2^5+1\cdot 2^4+1\cdot 2^3+1\cdot 2^2+1\cdot 2^1+0\cdot 2^0= 0 + 64 + 0 + 16 + 8 + 4 + 2 + 0 = 94_{10}$$Преобразование чисел из системы с большим основанием в систему с меньшим основанием
Рассмотрим его на примере преобразования из десятичной системы в двоичную. Нужно для известного числа $$N_{10}$$ найти коэффициенты в выражении
$$N = B_n 2^n + ... + B_2 2^2 + B_1 2^1 + B_0 2^0\qquad (B_n = 0\text{ или }1)$$Воспользуемся следующим алгоритмом: в десятичной системе разделим число N на 2 с остатком. Остаток деления (он не превосходит делителя) даст коэффициент $$B_0$$ при младшей степени $$2^0$$. Далее делим на 2 частное, полученное от предыдущего деления. Остаток деления будет следующим коэффициентом $$B_1$$ двоичной записи N. Повторяя эту процедуру до тех пор, пока частное не станет равным нулю, получим последовательность коэффициентов $$B_n$$.
Например, преобразуем $$345_{10}$$ к двоичному виду. Имеем:
| частное | остаток | $$B_i$$ | |
|---|---|---|---|
| 345 / 2 | 172 | 1 | $$B_0$$ |
| 172 / 2 | 86 | 0 | $$B_1$$ |
| 86 / 2 | 43 | 0 | $$B_2$$ |
| 43 / 2 | 21 | 1 | $$B_3$$ |
| 21 / 2 | 10 | 1 | $$B_4$$ |
| 10 / 2 | 5 | 0 | $$B_5$$ |
| 5 / 2 | 2 | 1 | $$B_6$$ |
| 2 / 2 | 1 | 0 | $$B_7$$ |
| 1 / 2 | 0 | 1 | $$B_8$$ |
Преобразование чисел в системах счисления с кратными основаниями
Рассмотрим число N в двоичном и шестнадцатеричном представлениях.
Заметим, что $$16 = 2^4$$. Объединим цифры в двоичной записи числа группами по четыре. Каждая группа из четырех двоичных цифр представляет число от 0 до $$F_{16},$$ то есть от 0 до $$15_{10}$$. От группы к группе вес цифры изменяется в $$2^4=16$$ раз (основание 16-ричной системы). Таким образом, перевод чисел из
| $$0000_2 = 0_{16}$$ |
| $$0001_2 = 1_{16}$$ |
| $$0010_2 = 2_{16}$$ |
| $$0011_2 = 3_{16}$$ |
| $$0100_2 = 4_{16}$$ |
| $$0101_2 = 5_{16}$$ |
| $$0110_2 = 6_{16}$$ |
| $$0111_2 = 7_{16}$$ |
| $$1000_2 = 8_{16}$$ |
| $$1001_2 = 9_{16}$$ |
| $$1010_2 = A_{16}$$ |
| $$1011_2 = B_{16}$$ |
| $$1100_2 = C_{16}$$ |
| $$1101_2 = D_{16}$$ |
| $$1110_2 = E_{16}$$ |
| $$1111_2 = F_{16}$$ |
Например, преобразуем $$1011010111_2$$ к шестнадцатеричному виду:
$$1011010111_2 = 0010\;1101\;0111_2 = 2D7_{16}$$| Оператор | Название | Пример | Примечание |
|---|---|---|---|
~ |
Оператор побитового дополнения (побитовое "не", побитовое отрицание) | ~i |
|
^ |
Оператор " побитовое исключающее или" (XOR) | i^j |
|
|
Оператор "побитовое и" (AND) | ij |
|
| |
Оператор "побитовое или" (OR) | i|j |
<< |
Оператор левого побитового сдвига |
>>> |
Оператор беззнакового правого побитового сдвига |
>> |
Оператор правого побитового сдвига с сохранением знака отрицательного числа |
= |
y=x эквивалентно y=yx |
|= |
y|=x эквивалентно y=y|x |
^= |
y^=x эквивалентно y=y^x |
>>= |
y>>=x эквивалентно y= y>>x |
>>>= |
y>>>=x эквивалентно y= y>>>x |
<<= |
y<<=x эквивалентно y= y<<x |
Побитовые операции – когда целые числа рассматриваются как наборы бит, где 0 и 1 играют роли логического нуля и логической единицы. При этом все логические операции для двух чисел осуществляются поразрядно – k-тый разряд первого числа с k-тым разрядом второго. Для простоты мы будем рассматривать четырехбитовые ячейки, хотя реально самая малая по размеру ячейка восьмибитовая и соответствует типу byte.
OR ).Пусть число $$a = a_3*2^3 + a_2*2^2 + a_1*2^1 + a_0*2^0$$, где значения $$a_i$$ – содержание соответствующих бит числа (то есть либо нули , либо единицы).
| a | $$a_3$$ | $$a_2$$ | $$a_1$$ | $$a_0$$ |
| m | 0 | 1 | 0 | 1 |
| a|m | $$a_3$$ | 1 | $$a_1$$ | 1 |
Видно, что независимо от начального значения в числе a в результате нулевой и второй бит установились в единицу. Таким образом, операцию OR с маской можно использовать для установки нужных бит переменной в единицу, если нужные биты маски установлены в единицу, а остальные – нули.
AND ):| a | $$a_3$$ | $$a_2$$ | $$a_1$$ | $$a_0$$ |
| m | 0 | 1 | 0 | 1 |
| am | 0 | $$a_2$$ | 0 | $$a_0$$ |
Видно, что независимо от начального значения в числе a в результате первый и третий бит установились в нуль. Таким образом, операцию AND с маской можно использовать для установки нужных бит переменной в ноль, если нужные биты маски установлены в ноль, а остальные – единицы.
в) инверсия (замена единиц на нули, а нулей на единицы) в битах числа a, стоящих на задаваемых маской m местах, операцией a^m (арифметический, или, что то же, побитовый оператор XOR ):
| a | 1 | 1 | 0 | 0 |
| m | 0 | 1 | 0 | 1 |
| a^m | 1 | 0 | 0 | 1 |
Видно, что если в бите, где маска m имеет единицу, у числа a происходит инверсия: если стоит 1, в результате будет 0, а если 0 – в результате будет 1. В остальных битах значение не меняется.
Восстановление первоначального значения после операции XOR – повторное XOR с той же битовой маской:
| a^m | 1 | 0 | 0 | 1 |
| m | 0 | 1 | 0 | 1 |
| (a^m)^m | 1 | 1 | 0 | 0 |
Видно, что содержание ячейки приняло то же значение, что было первоначально в ячейке a. Очевидно, что всегда (a ^ m) ^ m = a, так как повторная инверсия возвращает первоначальные значения в битах числа. Операция XOR часто используется в программировании для инверсии цветов частей экрана с сохранением в памяти только информации о маске. Повторное XOR с той же маской восстанавливает первоначальное изображение. - Имеется команда перевода вывода графики в режим XOR при рисовании, для этого используется команда graphics.setXORMode(цвет).
Еще одна область, где часто используется эта операция - криптография.
Инверсия всех битов числа осуществляется с помощью побитового отрицания ~a.
Побитовые сдвиги "<<", ">>" и ">>>" приводят к перемещению всех бит ячейки, к которой применяется оператор, на указанное число бит влево или вправо. Сначала рассмотрим действие операторов на положительные целые числа.
Побитовый сдвиг на n бит влево m<<n эквивалентен быстрому целочисленному умножению числа m на $$2^n$$. Младшие биты (находящиеся справа), освобождающиеся после сдвигов, заполняются нулями. Следует учитывать, что старшие биты (находящиеся слева), выходящие за пределы ячейки, теряются, как и при обычном целочисленном переполнении.
Побитовые сдвиги на n бит вправо m>>n или m>>>n эквивалентны быстрому целочисленному делению числа m на $$2^n$$. При этом для положительных m разницы между операторами ">>" и ">>>" нет.
Рассмотрим теперь операции побитовых сдвигов для отрицательных чисел m. Поскольку они хранятся в int или long, то есть для 32-битных или 64-битных чисел.
Пусть m равно -1. В этом случае $$m=1111_2$$. Оператор $$m<<1$$ даст $$m=11110_2$$, но из-за четырехбитности ячейки старший бит теряется, и мы получаем $$m=1110_2=-2$$. То есть также получается полная эквивалентность умножению m на $$2^n$$.
Иная ситуация возникает при побитовых сдвигах вправо. Оператор правого сдвига ">>" для положительных чисел заполняет освободившиеся биты нулями, а для отрицательных - единицами. Легко заметить, что этот оператор эквивалентен быстрому целочисленному делению числа m на $$2^n$$ как для положительных, так и для отрицательных чисел. Оператор m>>>n, заполняющий нулями освободившиеся после сдвигов биты, переводит отрицательные числа в положительные. Поэтому он не может быть эквивалентен быстрому делению числа на $$2^n$$. Но иногда такой оператор бывает нужен для манипуляции с наборами бит, хранящихся в числовой ячейке. Само значение числа в этом случае значения не имеет, а ячейка используется как буфер соответствующего размера.
Например, можно преобразовать последовательность бит, образующее некое целое значение, в число типа float методом Float.intBitsToFloat(целое значение) или типа double методом Double.intBitsToDouble (целое значение). Так, Float.intBitsToFloat(0x7F7FFFFF) даст максимальное значение типа float.
Целое число $$0101_2$$ можно представить в виде $$0101_2 =0*2^3 + 1*2^2 + 0*2^1 + 1*2^0$$
Аналогично можно записать двоичную дробь:
$$11.0101_2 =1*2^1+ 1*2^0 + 0*2^{-1} + 1*2^{-2} + 0*2^{-3} + 1*2^{-4}$$Заметим, что сдвиг двоичной точки на n разрядов вправо (чаще говорят о сдвиге самого числа влево) эквивалентен умножению числа на $$(10_2)^n = 2^n$$. Сдвиг точки влево (то есть сдвиг самого числа вправо) – делению на $$2^n$$.
Рассмотрим сначала упрощенную схему хранения чисел в формате с плавающей точкой (
Число $$x$$ с плавающей точкой может быть представлено в виде $$x=s*m*2^p$$. Множитель s – знак числа. Второй множитель m называется мантиссой, а число $$p$$ – порядком числа.
Для простоты рассмотрим 10-битовую ячейку, состоящую из трех независимых частей:
(рис 4.1) Первым идет
Число сдвигов двоичной точки (с учетом знака) хранится в части ячейки, выделяемой под порядок числа. В нашем примере числа $$1.0101_2$$, $$10.101_2$$ и $$0.10101_2$$ будут иметь порядки 0, 1 и -1, соответственно. При перемножении чисел их
Замечание: системы символьных вычислений (или, что то же, – аналитических вычислений, или, что то же, системы компьютерной алгебры) позволяют проводить точные численные расчеты с получением результатов в виде формул. Однако они выполняют вычисления на много порядков медленнее, требуют намного больше ресурсов и не могут работать без громоздкой среды разработки. Поэтому для решения большинства практически важных задач они либо неприменимы, либо их использование нецелесообразно.
При сложении или вычитании сначала происходит приведение чисел к одному порядку:
Пример:
сложим числа $$11.011_2$$ и $$0.11011_2.$$ Для первого числа
Но при таком сдвиге теряется два последних значащих бита
вместо точного значения $$1.0000111_2*(10_2)^2$$.
Таким образом, числа в описанном формате являются на деле рациональными, а не вещественными. При этом операции сложения, вычитания, умножения и деления выполняются с погрешностями, тем меньшими, чем больше разрядность
Научная нотация записи вещественных чисел
При записи программы в текстовом файле или выдачи результатов в виде "плоского текста" (
Литерные константы для вещественных типов по умолчанию имеют тип double. Например, 1.5 , -17E2 , 0.0 . Если требуется ввести литерную константу типа float, после записи числа добавляют постфикс f (сокращение от "float"): 1.5f , -17E2f , 0.0f .
Минимальное по модулю не равное нулю и максимальное значение типа float можно получить с помощью констант
Float.MIN_VALUE - равна $$2^{-149}$$Float.MAX_VALUE - равна $$(2-2^{-23})\cdot 2^{127}$$Аналогичные значения для типа double - с помощью констант
Double.MIN_VALUE - равна $$2^{-1074}$$Double.MAX_VALUE - равна $$(2-2^{-52})\cdot 2^{1023}$$.*Этот параграф является необязательным и приводится в справочных целях
В каком виде на самом деле хранятся числа в формате с плавающей точкой? Ответ дает стандарт real*4, real*8 и real*10. Эти числа занимают 4, 8 и 10 байт, соответственно. В Java типу real*4 соответствует float, а типу real*8 соответствует double. Тип real*10 из распространенных языков программирования используется только в диалектах языка PASCAL, в Java он не применяется.
Число $$r$$ представляется в виде произведения знака $$s$$,
Число $$p$$ называется порядком. Оно может меняться для разных чисел. Значение $$d$$, называемое сдвигом порядка, постоянное для всех чисел заданного типа. Оно примерно равно половине максимального числа $$p_{max}$$, которое можно закодировать битами порядка. Точнее, $$d= (p_{max}+1)/2-1$$.
real*4: $$p_{max} = 255$$, d= 127.real*8: $$p_{max} = 2047$$, d= 1023.real*10: $$p_{max} = 32767$$, d=16383.Число называется нормализованным в случае, когда
Согласно стандарту
Размещение чисел в ячейках памяти такое:
| Тип | Байт1 | Байт2 | Байт3 | Байт4 | … | Байт8 | Байт9 | Байт10 |
|---|---|---|---|---|---|---|---|---|
real*4 |
sppp pppp | pmmm mmmm | mmmm mmmm | mmmm mmmm | ||||
real*8 |
sppp pppp | pppp mmmm | mmmm mmmm | mmmm mmmm | mmmm mmmm | |||
real*10 |
sppp pppp | pppp pppp | 1mmm mmmm | mmmm mmmm | mmmm mmmm | mmmm mmmm | mmmm mmmm |
Буква s обозначает p – биты m – биты float ) и real*8 ( double ) при базовом представлении ведущая единица
| Тип |
Тип Java | Число бит |
Число бит порядка | Сдвиг порядка |
|---|---|---|---|---|
real*4 |
float |
23+ подразумевается 1 ведущий бит | 8 | 127 |
real*8 |
double |
52+ подразумевается 1 ведущий бит | 11 | 1023 |
real*10 |
- | 64 | 15 | 16383 |
Чему равны минимальное и максимальное по модулю числа при их базовом представлении?
Минимальное значение достигается при минимальном порядке и всех нулевых битах m=1 и p=1. Значит, минимальное значение равно $$2^{1-d}$$.
Максимальное значение достигается при максимальном порядке и всех единичных битах
При значениях порядка в диапазоне от 1 до $$p_{max}-1$$ базовое представление позволяет закодировать
real*4 примерно от 2.350989E-38 до 3.402824E38,real*8 примерно от 2.225074E-308 до 1.797693E308,real*10 примерно от 3.362103E-4932 до 1.189731E4932.В случае, когда порядок равен 0 или $$p_{max}$$, используется особое представление чисел, несколько отличающееся от базового.
Если все биты порядка равны 0, но
Минимальное ненулевое значение
Минимальное отличное от нуля положительное число для некоторого типа чисел с плавающей точкой равно $$2^{1-d-n}$$.
Таким образом, особое представление позволяет закодировать
real*4 примерно от 1.401298E-45 до 2.350989E-38,real*8 примерно от 4.940656E-324 до 2.225074E-308,real*10 примерно от 3.6451995E-4951 до 3.362103E-4932.Специальный случай особого представления – когда и порядок и
Нечисловое представление соответствует случаю, когда $$p=p_{max}$$, то есть все биты порядка равны 1. Такое "число" в зависимости от значения Inf ( (Not a Number - "не число"), (Inf, а при делении 0 на 0 получается . Значение может получаться при преобразовании строки в число, взятии логарифма от отрицательного числа, тригонометрической функции от бесконечности и т.п.
Значение Inf соответствует нулевым битам + Inf, если –Inf.
Значение кодируется единицей в . Значения бывают двух типов – вызывающие возбуждение сигнала о переполнении ( ) и не вызывающие ( ). Значения обоих этих типов могут быть "положительными" (
В современных языках программирования поддерживается только часть возможностей, реализованных в процессорах в соответствии со стандартом
Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY.
Но значение вообще не употребляется и отождествляется с , хотя Float. и Double. различаются.
Числа в формате с плавающей точкой занимают следующие диапазоны значений:
| Название значения | s (знак) | p (порядок) | m ( |
|---|---|---|---|
| - |
1 | 11..11 | 11..11 : 10..01 |
| 1 | 11..11 | 10..00 | |
| 1 | 11..11 | 01..11 : 00..01 |
|
| - |
1 | 11..11 | 00..00 |
| Отрицательное нормализованное | 1 | 11..10 : 00..01 |
11..11 : 00..00 |
| Отрицательное ненормализованное | 1 | 00..00 | 11..11 : 00..01 |
| -0 | 1 | 00..00 | 00..00 |
| +0 | 0 | 00..00 | 00..00 |
| Положительное ненормализованное | 0 | 00..00 | 00..01 : 11..11 |
| Положительное нормализованное | 0 | 00..01 : 11..10 |
00..00 : 11..11 |
| + |
0 | 11..11 | 00..00 |
| 0 | 11..11 | 00..01 : 01..11 |
|
| 0 | 11..11 | 10..00 : 11..11 |
Имеются методы оболочечных классов, позволяющие преобразовывать наборы бит, хранящихся в ячейках типа int, в значения float, и наоборот – значения типа float в их битовое представление типа int. При этом содержание ячеек не меняется – просто содержащиеся в ячейках наборы бит начинают по-другому трактоваться.
Аналогичные операции существуют и для значений типа long и double:
Float.intBitsToFloat ( значение типа int )Double.longBitsToDouble ( значение типа long )Float.floatToIntBits ( значение типа float )Double.doubleToLongBits ( значение типа double )Например,
Float.intBitsToFloat(0x7F7FFFFF) даст значение, равное Float.MAX_VALUE,Float.intBitsToFloat(0x7F800000) – значение Float.POSITIVE_INFINITY,Float.intBitsToFloat(0xFF800000) – значение Float.NEGATIVE_INFINITY.Если аргумент метода Float.intBitsToFloat лежит в пределах от 0xF800001 до 0xF800001, результатом будет Float..
Следует подчеркнуть, что данные операции принципиально отличаются от "обычных" преобразований типов, например, из int в float или из double в long. При "обычных" преобразованиях значение числа не меняется, просто меняется форма хранения этого значения и, соответственно, наборы битов, которыми кодируется это значение. Причем может измениться размер ячейки (скажем, при преобразовании значений int в значения double ). А при рассматриваемых в данном разделе операциях сохраняется набор бит и размер ячейки, но меняется тип, который приписывается этому набору.
AND (оператор "" ) служит для сбрасывания в 0 тех битов числа, где в маске стоит 0, остальные биты числа не меняются. Побитовая маска OR (оператор "|" ) служит для установки в 1 тех битов числа, где в маске стоит 1, остальные биты числа не меняются.XOR (оператор "^" ) служит для инверсии тех битов числа, где в маске стоит 1 (единицы переходят в нули, а нули – в единицы), остальные биты числа не меняются. Имеется команда перевода вывода графики в режим XOR при рисовании, для этого используется команда graphics.setXORMode(цвет)."<<", ">>" и ">>>" приводят к перемещению всех бит ячейки, к которой применяется оператор, на указанное число бит влево или вправо. Причем m<<n является очень быстрым вариантом операции $$m\cdot 2^n$$, а m>>n – целочисленному делению m на $$2^n$$.double. Например, 1.5 , -17E2 , 0.0 . Если требуется ввести литерную константу типа float, после записи числа добавляют постфикс f (сокращение от "float"): 1.5f , -17E2f , 0.0f .1+x и присваиваются переменной float f, а также вычисляется выражение 1+y и присваиваются переменной double d. Величины x типа float и y типа double вводится пользователем с помощью пунктов ввода. Вывести в метку jLabel1 разность f-1, и в метку jLabel2 разность d-1. Провести вычисления для x и y, меняющихся в пределах от 1E-3 до 1E-18 . Объяснить результаты.f2(x) состоит из первых членов разложения f1(x) в ряд, то f1(x) и f2(x) должны быть примерно равны . Требуется сравнить значения выражения f1(x) и f2(x) при различных x. Все вычисления сначала проводить для переменных и функций типа float, а затем - для переменных и функций типа double. Величина x вводится пользователем.
Вывести в метки значения f1_double (x), a_double(x), b_double(x), f1_float(x), a_float(x), b_float(x), а также разности f1_float(x)-f2_float(x) и f1_double (x) - f2_double (x). Провести такое сравнение для аргументов x, меняющихся в пределах от 1E-8 до 0.1. Объяснить результаты.Данная часть посвящена изучению работы с числами на более глубоком уровне. В ней рассматривается машинное представление целых и вещественных чисел, эффективное для аппаратной реализации, а также объясняются особенности и проблемы, к которым приводит такое представление.
Позиционная
Например, число 2006 в десятичной
то есть слагаемых с различными степенями числа 10. По основанию степени - числу десять - система называется десятичной. Другие
При написании программ чаще всего используют десятичную, шестнадцатеричную (основание шестнадцать), восьмеричную (основание восемь) и двоичную (основание два) системы. Число различных знаков - цифр, используемых для записи чисел - в каждой системе равно основанию данной
| 0,1 | - цифры двоичной системы |
| 0,1,2,3,4,5,6,7 | - цифры восьмеричной системы |
| 0,1,2,3,4,5,6,7,8,9 | - цифры десятичной системы |
| 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F | - цифры шестнадцатеричной системы |
В шестнадцатеричной системе "обычных" десятичных цифр недостаточно, и для обозначения цифр, больших девяти, используются заглавные латинские буквы A,B,C,D,E,F.
В дальнейшем везде, где это необходимо, мы будем указывать основание
Существует множество непозиционных систем счисления, в которых числа изображаются и называются по своим правилам. Для римской системы чисел характерны сопоставление отдельного знака каждому большому числу ( V - пять, X - десять, L - пятьдесят, C - сто, M - тысяча ), повторение знака столько раз, сколько таких чисел во всем числе ( III - три, XX - двадцать), отдельные правила для предшествующих и последующих чисел (IV - четыре, VI - шесть, IX - девять). Множество непозиционных систем счисления связано с традиционными способами измерения конкретных величин - времени ( секунда, минута, час, сутки, неделя, месяц, год), длины ( дюйм, фут, ярд, миля, аршин, сажень), массы ( унция, фунт), денежных единиц. Выполнение арифметических действий в таких системах представляет собой крайне сложную задачу.
Приведем пример самой простой из возможных систем счисления – унарную. В ней имеется всего одна цифра 1. В унарной
Машинным словом называют порцию данных, которую процессор компьютера может обработать за одну операцию (
Представление чисел в двоичной и шестнадцатеричной
Целые числа в компьютере обычно кодируются в двоичном коде, то есть в
Показатель
Аналогично, $$6=1\cdot2^2+1\cdot2^1+0\cdot2^0=110_2,\; 2=10_2,\; 4=100_2,\; 8=1000_2,\; 9=1001_2$$, и так далее. Все очень похоже на обозначение чисел в десятичной
$$153=1\cdot10^2+5\cdot10^1+3\cdot10^0$$. Но только в качестве основания
В двоичной арифметике всего две цифры, 0 и 1. Двоичный код положительного целого числа – это коэффициенты разложения числа по степеням двойки.
Умножение числа на двоичное десять, то есть на $$10_2=2$$, приводит к дописыванию справа нуля в
Целочисленное деление на $$10_2$$ с отбрасыванием остатка производится путем отбрасывания последнего (младшего) бита, деление на $$100_2$$ - отбрасывания двух последних бит, и так далее. Обычно такие операции называют побитовыми сдвигами на n бит влево (умножение на $$2^n$$ ) или вправо (целочисленное деление на $$2^n$$ ).
Сложение двоичных чисел можно производить "в столбик" по полной аналогии со сложением десятичных чисел. Единственное отличие – то, что в двоичной арифметике только две цифры, 0 и 1, а не десять цифр (от 0 до 9) как в десятичной. Поэтому если в десятичной арифметике единицу более старшего разряда дает, к примеру, сложение 1 и 9, то в двоичной арифметике ее даст сложение 1 и 1. То есть
$$1_2+1_2=10_2$$(в десятичной системе это равенство выглядит как 1+1=2). Аналогично, $$10_2+10_2=100_2$$, и так далее.
Примеры сложения "в столбик":
$$\frac{+\substack{0110_2\\ 1011_2}}{10001_2}\quad \frac{+\substack{1100_2\\ 0010_2}}{1110_2}\quad \frac{+\substack{111_2\\ 001_2}}{1000_2}$$Совершенно аналогично выполняется умножение:
$$\cfrac{\;\;\;\;\;\times\substack{101_2\\ \;11_2}}{\cfrac{+\substack{\;\;\;101\\101}}{\;\;\;\;\;\;1111_2}}$$В машинной реализации целочисленного умножения используют побитовые сдвиги влево и сложения. Поскольку эти алгоритмы очень просты, они реализуются аппаратно.
Старший бит в целых без знака имеет обычный смысл, в целых со знаком – для положительных чисел всегда равен 0. В отрицательных числах старший бит всегда равен 1. В примерах для простоты мы будем рассматривать четырехбитную арифметику. Тогда в качестве примера целого положительного числа можно привести $$0110_2$$.
Для хранения отрицательных чисел используется
Надо отметить, что
Например, код числа (–1) в четырехразрядной арифметике будет $$1111_2$$, а в 8-разрядной арифметике будет $$11111111_2$$. Коды числа (–2) будут $$1110_2$$ и $$11111110_2$$, и так далее.
Для того, чтобы понять причину использования дополнительного кода, рассмотрим сложение чисел, представленных в
Сложение положительного и отрицательного чисел
Рассмотрим, чему равна сумма числа 1 и числа –1, представленного в
Таким образом, в четырехбитном представлении $$–1_{10}=1111_2$$.
Проверка:
$$n2+n=10000_2$$. Получившийся пятый разряд, выходящий за пределы четырехбитной ячейки, отбрасывается, поэтому в рамках четырехбитной арифметики получается $$n2+n=0000_2=0$$.
Аналогично
Очевидно, во всех этих случаях $$n2+n=0$$.
Что будет, если мы сложим $$3_{10}$$ и $$–2_{10}$$ (равное 1110_2, как мы уже знаем)?
$$\frac{+\substack{0011_2\\ 1110_2}}{\;\;10001_2}$$После отбрасывания старшего бита, выходящего за пределы нашей четырехбитовой ячейки, получаем $$0011_2 + 1110_2=0001_2$$, то есть $$3_{10} + (–2_{10})=1_{10}$$, как и должно быть.
Сложение отрицательных чисел
$$(-1)+(-1)=1111_2+1111_2=11110_2\to 1110_2$$ из-за отбрасывания лишнего старшего бита, выходящего за пределы ячейки. Поэтому $$(-1)+(-1)=1110_2=-2$$.
Вычитание положительных чисел осуществляется путем сложения положительного числа с отрицательным, получившимся из вычитаемого в результате его перевода в
Приведенные примеры иллюстрируют тот факт, что сложение положительного числа с отрицательным, хранящимся в
Несмотря на достоинства в двоичной машинной (аппаратной) арифметике имеются очень неприятные особенности, возникающие из-за конечной разрядности машинной ячейки.
Проблемы сложения положительных чисел
Пусть $$a=3_{10}=0011_2; b=2_{10}=0010_2; a+b=0101_2=5_{10}$$, то есть все в порядке.
Пусть теперь $$a=6_{10}=0110_2, b=5_{10}=0101_2. Тогда a+b =1011_2= -3_2$$.
То есть сложение двух положительных чисел может дать отрицательное, если результат сложения превышает максимальное положительное число, выделяемое под целое со знаком для данной разрядности ячеек! В любом случае при выходе за разрешенный диапазон значений результат оказывается неверным.
Если у нас беззнаковые целые, проблема остается в несколько измененном виде. Сложим $$8_{10}+8_{10}$$ в
При целочисленном умножении выход за пределы разрядности ячейки происходит гораздо чаще, чем при сложении или вычитании. Например, $$110_2\times 101_2=110_2\times 100_2+110_2\times 1_2=11000_2+110_2=11100_2$$. Если наша ячейка четырехразрядная, произойдет выход за ее пределы, и мы получим после отбрасывания лишнего бита $$1110_2=-2_{10}<0$$. Таким образом, умножение целых чисел легко может дать неправильный результат. В том числе – даже отрицательное число. Поэтому при работе с
byte m=10,n=10,k=10;
Тогда значения m*n, m*k и n*k будут лежать в разрешенном диапазоне -128..127. А вот m*n + m*k из него выйдет. Не говоря уж об m*n*k.
Если мы зададим
short m=10,n=10,k=10;
переполнения не возникнет даже для m*n*k. Однако, при m=n=k=100 значение m*n*k будет равно $$10^6$$, что заметно выходит за пределы разрешенного диапазона –32768..32767. Хотя m*n, m*k и n*k не будут за него выходить (но уже 4*m*n за него выйдет). Использование типа long поможет и в этом случае. Однако уже значения m=n=k=2000 (не такие уж большие!) опять приведут к выходу m*n*k за пределы диапазона. Хотя для m*n выход произойдет только при значениях около 50000.
Вычисление факториала с помощью целочисленной арифметики даст удивительные результаты! В таких случаях лучше использовать числа с плавающей точкой. Пример:
byte i=127, j=1, k; k=(byte)(i+j); System.out.println(k);
В результате получим число (-128). Если бы мы попробовали написать
byte i=127,j=1,k; System.out.println(i+j);
то получили бы +128. Напомним, что значения величин типа byte перед проведением сложения преобразуются в значения типа int.
Во время программирования различного рода внешних устройств, регистров процессора, битовыми масками, кодировке цвета, и так далее, приходится работать с кодами беззнаковых целых чисел. При этом использование десятичных чисел крайне неудобно из-за невозможности легкого сопоставления числа в десятичном виде и его двоичных бит. А использование чисел в двоичной кодировке крайне громоздко – получаются слишком длинные последовательности нулей и единиц. Программисты используют компромиссное решение – шестнадцатеричную кодировку чисел, где в качестве основания
В Java для того, чтобы отличать 16-ричные числа, как мы уже знаем, перед ними ставят префикс 0x: 0xFF обозначает $$FF_{16}$$, а 0x10 – это $$10_{16}$$, то есть 16.
Число N может быть записано с помощью разных систем счисления. Например, в десятичной:
или в двоичной:
$$N = B_n 2^n + ... + B_2 2^2 + B_1 2^1 + B_0 2^0\qquad (B_n = 0\text{ или }1)$$или в шестнадцатеричной:
$$N = C_n 16^n + ... + C_2 16^2 + C_1 16^1 + C_0 16^0\qquad (C_n = 0 .. F)$$Преобразование в другую систему счисления сводится к нахождению соответствующих коэффициентов. Например, $$B_n$$ по известным коэффициентам $$A_n$$ – при переводе из десятичной системы в двоичную, или коэффициентов $$A_n$$ по коэффициентам $$B_n$$ - из двоичной системы в десятичную.
Преобразование чисел из системы с меньшим основанием в систему с большим основанием
Рассмотрим преобразование из двоичной системы в десятичную. Запишем число N в виде
и будем рассматривать как алгебраическое выражение в десятичной системе. Выполним арифметические действия по правилам десятичной системы. Полученный результат даст десятичное представление числа N.
Пример:
Преобразуем $$01011110_2$$ к десятичному виду. Имеем:
$$01011110_2 = 0\cdot 2^7+1\cdot 2^6+0\cdot 2^5+1\cdot 2^4+1\cdot 2^3+1\cdot 2^2+1\cdot 2^1+0\cdot 2^0= 0 + 64 + 0 + 16 + 8 + 4 + 2 + 0 = 94_{10}$$Преобразование чисел из системы с большим основанием в систему с меньшим основанием
Рассмотрим его на примере преобразования из десятичной системы в двоичную. Нужно для известного числа $$N_{10}$$ найти коэффициенты в выражении
$$N = B_n 2^n + ... + B_2 2^2 + B_1 2^1 + B_0 2^0\qquad (B_n = 0\text{ или }1)$$Воспользуемся следующим алгоритмом: в десятичной системе разделим число N на 2 с остатком. Остаток деления (он не превосходит делителя) даст коэффициент $$B_0$$ при младшей степени $$2^0$$. Далее делим на 2 частное, полученное от предыдущего деления. Остаток деления будет следующим коэффициентом $$B_1$$ двоичной записи N. Повторяя эту процедуру до тех пор, пока частное не станет равным нулю, получим последовательность коэффициентов $$B_n$$.
Например, преобразуем $$345_{10}$$ к двоичному виду. Имеем:
| частное | остаток | $$B_i$$ | |
|---|---|---|---|
| 345 / 2 | 172 | 1 | $$B_0$$ |
| 172 / 2 | 86 | 0 | $$B_1$$ |
| 86 / 2 | 43 | 0 | $$B_2$$ |
| 43 / 2 | 21 | 1 | $$B_3$$ |
| 21 / 2 | 10 | 1 | $$B_4$$ |
| 10 / 2 | 5 | 0 | $$B_5$$ |
| 5 / 2 | 2 | 1 | $$B_6$$ |
| 2 / 2 | 1 | 0 | $$B_7$$ |
| 1 / 2 | 0 | 1 | $$B_8$$ |
Преобразование чисел в системах счисления с кратными основаниями
Рассмотрим число N в двоичном и шестнадцатеричном представлениях.
Заметим, что $$16 = 2^4$$. Объединим цифры в двоичной записи числа группами по четыре. Каждая группа из четырех двоичных цифр представляет число от 0 до $$F_{16},$$ то есть от 0 до $$15_{10}$$. От группы к группе вес цифры изменяется в $$2^4=16$$ раз (основание 16-ричной системы). Таким образом, перевод чисел из
| $$0000_2 = 0_{16}$$ |
| $$0001_2 = 1_{16}$$ |
| $$0010_2 = 2_{16}$$ |
| $$0011_2 = 3_{16}$$ |
| $$0100_2 = 4_{16}$$ |
| $$0101_2 = 5_{16}$$ |
| $$0110_2 = 6_{16}$$ |
| $$0111_2 = 7_{16}$$ |
| $$1000_2 = 8_{16}$$ |
| $$1001_2 = 9_{16}$$ |
| $$1010_2 = A_{16}$$ |
| $$1011_2 = B_{16}$$ |
| $$1100_2 = C_{16}$$ |
| $$1101_2 = D_{16}$$ |
| $$1110_2 = E_{16}$$ |
| $$1111_2 = F_{16}$$ |
Например, преобразуем $$1011010111_2$$ к шестнадцатеричному виду:
$$1011010111_2 = 0010\;1101\;0111_2 = 2D7_{16}$$| Оператор | Название | Пример | Примечание |
|---|---|---|---|
~ |
Оператор побитового дополнения (побитовое "не", побитовое отрицание) | ~i |
|
^ |
Оператор " побитовое исключающее или" (XOR) | i^j |
|
|
Оператор "побитовое и" (AND) | ij |
|
| |
Оператор "побитовое или" (OR) | i|j |
<< |
Оператор левого побитового сдвига |
>>> |
Оператор беззнакового правого побитового сдвига |
>> |
Оператор правого побитового сдвига с сохранением знака отрицательного числа |
= |
y=x эквивалентно y=yx |
|= |
y|=x эквивалентно y=y|x |
^= |
y^=x эквивалентно y=y^x |
>>= |
y>>=x эквивалентно y= y>>x |
>>>= |
y>>>=x эквивалентно y= y>>>x |
<<= |
y<<=x эквивалентно y= y<<x |
Побитовые операции – когда целые числа рассматриваются как наборы бит, где 0 и 1 играют роли логического нуля и логической единицы. При этом все логические операции для двух чисел осуществляются поразрядно – k-тый разряд первого числа с k-тым разрядом второго. Для простоты мы будем рассматривать четырехбитовые ячейки, хотя реально самая малая по размеру ячейка восьмибитовая и соответствует типу byte.
OR ).Пусть число $$a = a_3*2^3 + a_2*2^2 + a_1*2^1 + a_0*2^0$$, где значения $$a_i$$ – содержание соответствующих бит числа (то есть либо нули , либо единицы).
| a | $$a_3$$ | $$a_2$$ | $$a_1$$ | $$a_0$$ |
| m | 0 | 1 | 0 | 1 |
| a|m | $$a_3$$ | 1 | $$a_1$$ | 1 |
Видно, что независимо от начального значения в числе a в результате нулевой и второй бит установились в единицу. Таким образом, операцию OR с маской можно использовать для установки нужных бит переменной в единицу, если нужные биты маски установлены в единицу, а остальные – нули.
AND ):| a | $$a_3$$ | $$a_2$$ | $$a_1$$ | $$a_0$$ |
| m | 0 | 1 | 0 | 1 |
| am | 0 | $$a_2$$ | 0 | $$a_0$$ |
Видно, что независимо от начального значения в числе a в результате первый и третий бит установились в нуль. Таким образом, операцию AND с маской можно использовать для установки нужных бит переменной в ноль, если нужные биты маски установлены в ноль, а остальные – единицы.
в) инверсия (замена единиц на нули, а нулей на единицы) в битах числа a, стоящих на задаваемых маской m местах, операцией a^m (арифметический, или, что то же, побитовый оператор XOR ):
| a | 1 | 1 | 0 | 0 |
| m | 0 | 1 | 0 | 1 |
| a^m | 1 | 0 | 0 | 1 |
Видно, что если в бите, где маска m имеет единицу, у числа a происходит инверсия: если стоит 1, в результате будет 0, а если 0 – в результате будет 1. В остальных битах значение не меняется.
Восстановление первоначального значения после операции XOR – повторное XOR с той же битовой маской:
| a^m | 1 | 0 | 0 | 1 |
| m | 0 | 1 | 0 | 1 |
| (a^m)^m | 1 | 1 | 0 | 0 |
Видно, что содержание ячейки приняло то же значение, что было первоначально в ячейке a. Очевидно, что всегда (a ^ m) ^ m = a, так как повторная инверсия возвращает первоначальные значения в битах числа. Операция XOR часто используется в программировании для инверсии цветов частей экрана с сохранением в памяти только информации о маске. Повторное XOR с той же маской восстанавливает первоначальное изображение. - Имеется команда перевода вывода графики в режим XOR при рисовании, для этого используется команда graphics.setXORMode(цвет).
Еще одна область, где часто используется эта операция - криптография.
Инверсия всех битов числа осуществляется с помощью побитового отрицания ~a.
Побитовые сдвиги "<<", ">>" и ">>>" приводят к перемещению всех бит ячейки, к которой применяется оператор, на указанное число бит влево или вправо. Сначала рассмотрим действие операторов на положительные целые числа.
Побитовый сдвиг на n бит влево m<<n эквивалентен быстрому целочисленному умножению числа m на $$2^n$$. Младшие биты (находящиеся справа), освобождающиеся после сдвигов, заполняются нулями. Следует учитывать, что старшие биты (находящиеся слева), выходящие за пределы ячейки, теряются, как и при обычном целочисленном переполнении.
Побитовые сдвиги на n бит вправо m>>n или m>>>n эквивалентны быстрому целочисленному делению числа m на $$2^n$$. При этом для положительных m разницы между операторами ">>" и ">>>" нет.
Рассмотрим теперь операции побитовых сдвигов для отрицательных чисел m. Поскольку они хранятся в int или long, то есть для 32-битных или 64-битных чисел.
Пусть m равно -1. В этом случае $$m=1111_2$$. Оператор $$m<<1$$ даст $$m=11110_2$$, но из-за четырехбитности ячейки старший бит теряется, и мы получаем $$m=1110_2=-2$$. То есть также получается полная эквивалентность умножению m на $$2^n$$.
Иная ситуация возникает при побитовых сдвигах вправо. Оператор правого сдвига ">>" для положительных чисел заполняет освободившиеся биты нулями, а для отрицательных - единицами. Легко заметить, что этот оператор эквивалентен быстрому целочисленному делению числа m на $$2^n$$ как для положительных, так и для отрицательных чисел. Оператор m>>>n, заполняющий нулями освободившиеся после сдвигов биты, переводит отрицательные числа в положительные. Поэтому он не может быть эквивалентен быстрому делению числа на $$2^n$$. Но иногда такой оператор бывает нужен для манипуляции с наборами бит, хранящихся в числовой ячейке. Само значение числа в этом случае значения не имеет, а ячейка используется как буфер соответствующего размера.
Например, можно преобразовать последовательность бит, образующее некое целое значение, в число типа float методом Float.intBitsToFloat(целое значение) или типа double методом Double.intBitsToDouble (целое значение). Так, Float.intBitsToFloat(0x7F7FFFFF) даст максимальное значение типа float.
Целое число $$0101_2$$ можно представить в виде $$0101_2 =0*2^3 + 1*2^2 + 0*2^1 + 1*2^0$$
Аналогично можно записать двоичную дробь:
$$11.0101_2 =1*2^1+ 1*2^0 + 0*2^{-1} + 1*2^{-2} + 0*2^{-3} + 1*2^{-4}$$Заметим, что сдвиг двоичной точки на n разрядов вправо (чаще говорят о сдвиге самого числа влево) эквивалентен умножению числа на $$(10_2)^n = 2^n$$. Сдвиг точки влево (то есть сдвиг самого числа вправо) – делению на $$2^n$$.
Рассмотрим сначала упрощенную схему хранения чисел в формате с плавающей точкой (
Число $$x$$ с плавающей точкой может быть представлено в виде $$x=s*m*2^p$$. Множитель s – знак числа. Второй множитель m называется мантиссой, а число $$p$$ – порядком числа.
Для простоты рассмотрим 10-битовую ячейку, состоящую из трех независимых частей:
(рис 4.1) Первым идет
Число сдвигов двоичной точки (с учетом знака) хранится в части ячейки, выделяемой под порядок числа. В нашем примере числа $$1.0101_2$$, $$10.101_2$$ и $$0.10101_2$$ будут иметь порядки 0, 1 и -1, соответственно. При перемножении чисел их
Замечание: системы символьных вычислений (или, что то же, – аналитических вычислений, или, что то же, системы компьютерной алгебры) позволяют проводить точные численные расчеты с получением результатов в виде формул. Однако они выполняют вычисления на много порядков медленнее, требуют намного больше ресурсов и не могут работать без громоздкой среды разработки. Поэтому для решения большинства практически важных задач они либо неприменимы, либо их использование нецелесообразно.
При сложении или вычитании сначала происходит приведение чисел к одному порядку:
Пример:
сложим числа $$11.011_2$$ и $$0.11011_2.$$ Для первого числа
Но при таком сдвиге теряется два последних значащих бита
вместо точного значения $$1.0000111_2*(10_2)^2$$.
Таким образом, числа в описанном формате являются на деле рациональными, а не вещественными. При этом операции сложения, вычитания, умножения и деления выполняются с погрешностями, тем меньшими, чем больше разрядность
Научная нотация записи вещественных чисел
При записи программы в текстовом файле или выдачи результатов в виде "плоского текста" (
Литерные константы для вещественных типов по умолчанию имеют тип double. Например, 1.5 , -17E2 , 0.0 . Если требуется ввести литерную константу типа float, после записи числа добавляют постфикс f (сокращение от "float"): 1.5f , -17E2f , 0.0f .
Минимальное по модулю не равное нулю и максимальное значение типа float можно получить с помощью констант
Float.MIN_VALUE - равна $$2^{-149}$$Float.MAX_VALUE - равна $$(2-2^{-23})\cdot 2^{127}$$Аналогичные значения для типа double - с помощью констант
Double.MIN_VALUE - равна $$2^{-1074}$$Double.MAX_VALUE - равна $$(2-2^{-52})\cdot 2^{1023}$$.*Этот параграф является необязательным и приводится в справочных целях
В каком виде на самом деле хранятся числа в формате с плавающей точкой? Ответ дает стандарт real*4, real*8 и real*10. Эти числа занимают 4, 8 и 10 байт, соответственно. В Java типу real*4 соответствует float, а типу real*8 соответствует double. Тип real*10 из распространенных языков программирования используется только в диалектах языка PASCAL, в Java он не применяется.
Число $$r$$ представляется в виде произведения знака $$s$$,
Число $$p$$ называется порядком. Оно может меняться для разных чисел. Значение $$d$$, называемое сдвигом порядка, постоянное для всех чисел заданного типа. Оно примерно равно половине максимального числа $$p_{max}$$, которое можно закодировать битами порядка. Точнее, $$d= (p_{max}+1)/2-1$$.
real*4: $$p_{max} = 255$$, d= 127.real*8: $$p_{max} = 2047$$, d= 1023.real*10: $$p_{max} = 32767$$, d=16383.Число называется нормализованным в случае, когда
Согласно стандарту
Размещение чисел в ячейках памяти такое:
| Тип | Байт1 | Байт2 | Байт3 | Байт4 | … | Байт8 | Байт9 | Байт10 |
|---|---|---|---|---|---|---|---|---|
real*4 |
sppp pppp | pmmm mmmm | mmmm mmmm | mmmm mmmm | ||||
real*8 |
sppp pppp | pppp mmmm | mmmm mmmm | mmmm mmmm | mmmm mmmm | |||
real*10 |
sppp pppp | pppp pppp | 1mmm mmmm | mmmm mmmm | mmmm mmmm | mmmm mmmm | mmmm mmmm |
Буква s обозначает p – биты m – биты float ) и real*8 ( double ) при базовом представлении ведущая единица
| Тип |
Тип Java | Число бит |
Число бит порядка | Сдвиг порядка |
|---|---|---|---|---|
real*4 |
float |
23+ подразумевается 1 ведущий бит | 8 | 127 |
real*8 |
double |
52+ подразумевается 1 ведущий бит | 11 | 1023 |
real*10 |
- | 64 | 15 | 16383 |
Чему равны минимальное и максимальное по модулю числа при их базовом представлении?
Минимальное значение достигается при минимальном порядке и всех нулевых битах m=1 и p=1. Значит, минимальное значение равно $$2^{1-d}$$.
Максимальное значение достигается при максимальном порядке и всех единичных битах
При значениях порядка в диапазоне от 1 до $$p_{max}-1$$ базовое представление позволяет закодировать
real*4 примерно от 2.350989E-38 до 3.402824E38,real*8 примерно от 2.225074E-308 до 1.797693E308,real*10 примерно от 3.362103E-4932 до 1.189731E4932.В случае, когда порядок равен 0 или $$p_{max}$$, используется особое представление чисел, несколько отличающееся от базового.
Если все биты порядка равны 0, но
Минимальное ненулевое значение
Минимальное отличное от нуля положительное число для некоторого типа чисел с плавающей точкой равно $$2^{1-d-n}$$.
Таким образом, особое представление позволяет закодировать
real*4 примерно от 1.401298E-45 до 2.350989E-38,real*8 примерно от 4.940656E-324 до 2.225074E-308,real*10 примерно от 3.6451995E-4951 до 3.362103E-4932.Специальный случай особого представления – когда и порядок и
Нечисловое представление соответствует случаю, когда $$p=p_{max}$$, то есть все биты порядка равны 1. Такое "число" в зависимости от значения Inf ( (Not a Number - "не число"), (Inf, а при делении 0 на 0 получается . Значение может получаться при преобразовании строки в число, взятии логарифма от отрицательного числа, тригонометрической функции от бесконечности и т.п.
Значение Inf соответствует нулевым битам + Inf, если –Inf.
Значение кодируется единицей в . Значения бывают двух типов – вызывающие возбуждение сигнала о переполнении ( ) и не вызывающие ( ). Значения обоих этих типов могут быть "положительными" (
В современных языках программирования поддерживается только часть возможностей, реализованных в процессорах в соответствии со стандартом
Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY.
Но значение вообще не употребляется и отождествляется с , хотя Float. и Double. различаются.
Числа в формате с плавающей точкой занимают следующие диапазоны значений:
| Название значения | s (знак) | p (порядок) | m ( |
|---|---|---|---|
| - |
1 | 11..11 | 11..11 : 10..01 |
| 1 | 11..11 | 10..00 | |
| 1 | 11..11 | 01..11 : 00..01 |
|
| - |
1 | 11..11 | 00..00 |
| Отрицательное нормализованное | 1 | 11..10 : 00..01 |
11..11 : 00..00 |
| Отрицательное ненормализованное | 1 | 00..00 | 11..11 : 00..01 |
| -0 | 1 | 00..00 | 00..00 |
| +0 | 0 | 00..00 | 00..00 |
| Положительное ненормализованное | 0 | 00..00 | 00..01 : 11..11 |
| Положительное нормализованное | 0 | 00..01 : 11..10 |
00..00 : 11..11 |
| + |
0 | 11..11 | 00..00 |
| 0 | 11..11 | 00..01 : 01..11 |
|
| 0 | 11..11 | 10..00 : 11..11 |
Имеются методы оболочечных классов, позволяющие преобразовывать наборы бит, хранящихся в ячейках типа int, в значения float, и наоборот – значения типа float в их битовое представление типа int. При этом содержание ячеек не меняется – просто содержащиеся в ячейках наборы бит начинают по-другому трактоваться.
Аналогичные операции существуют и для значений типа long и double:
Float.intBitsToFloat ( значение типа int )Double.longBitsToDouble ( значение типа long )Float.floatToIntBits ( значение типа float )Double.doubleToLongBits ( значение типа double )Например,
Float.intBitsToFloat(0x7F7FFFFF) даст значение, равное Float.MAX_VALUE,Float.intBitsToFloat(0x7F800000) – значение Float.POSITIVE_INFINITY,Float.intBitsToFloat(0xFF800000) – значение Float.NEGATIVE_INFINITY.Если аргумент метода Float.intBitsToFloat лежит в пределах от 0xF800001 до 0xF800001, результатом будет Float..
Следует подчеркнуть, что данные операции принципиально отличаются от "обычных" преобразований типов, например, из int в float или из double в long. При "обычных" преобразованиях значение числа не меняется, просто меняется форма хранения этого значения и, соответственно, наборы битов, которыми кодируется это значение. Причем может измениться размер ячейки (скажем, при преобразовании значений int в значения double ). А при рассматриваемых в данном разделе операциях сохраняется набор бит и размер ячейки, но меняется тип, который приписывается этому набору.
AND (оператор "" ) служит для сбрасывания в 0 тех битов числа, где в маске стоит 0, остальные биты числа не меняются. Побитовая маска OR (оператор "|" ) служит для установки в 1 тех битов числа, где в маске стоит 1, остальные биты числа не меняются.XOR (оператор "^" ) служит для инверсии тех битов числа, где в маске стоит 1 (единицы переходят в нули, а нули – в единицы), остальные биты числа не меняются. Имеется команда перевода вывода графики в режим XOR при рисовании, для этого используется команда graphics.setXORMode(цвет)."<<", ">>" и ">>>" приводят к перемещению всех бит ячейки, к которой применяется оператор, на указанное число бит влево или вправо. Причем m<<n является очень быстрым вариантом операции $$m\cdot 2^n$$, а m>>n – целочисленному делению m на $$2^n$$.double. Например, 1.5 , -17E2 , 0.0 . Если требуется ввести литерную константу типа float, после записи числа добавляют постфикс f (сокращение от "float"): 1.5f , -17E2f , 0.0f .1+x и присваиваются переменной float f, а также вычисляется выражение 1+y и присваиваются переменной double d. Величины x типа float и y типа double вводится пользователем с помощью пунктов ввода. Вывести в метку jLabel1 разность f-1, и в метку jLabel2 разность d-1. Провести вычисления для x и y, меняющихся в пределах от 1E-3 до 1E-18 . Объяснить результаты.f2(x) состоит из первых членов разложения f1(x) в ряд, то f1(x) и f2(x) должны быть примерно равны . Требуется сравнить значения выражения f1(x) и f2(x) при различных x. Все вычисления сначала проводить для переменных и функций типа float, а затем - для переменных и функций типа double. Величина x вводится пользователем.
Вывести в метки значения f1_double (x), a_double(x), b_double(x), f1_float(x), a_float(x), b_float(x), а также разности f1_float(x)-f2_float(x) и f1_double (x) - f2_double (x). Провести такое сравнение для аргументов x, меняющихся в пределах от 1E-8 до 0.1. Объяснить результаты.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.