Содержание:
Изображение: http://pikabu.ru/Вступление
В первые же дни изучения Java я наткнулся на такой любопытный вид примитивов, как числа с плавающей точкой.
Меня сразу заинтересовали их особенности и, тем более, способ записи в двоичном коде (что взаимосвязано). В отличие от какого-либо диапазона целых чисел, даже в очень малом промежутке (например от 1 до 2) их бесконечное множество. И имея конечный размер памяти, невозможно выразить это множество. Так как же они выражены в двоичном коде и как работают?
Увы, объяснения в
вики и достаточно клёвой статьи на хабре
тут не дали мне полного понимания, хотя заложили базу. Осознание пришло лишь после этой
статьи-разбора наутро после прочтения.
Экскурс в историю
(
почерпнул из этой статьи на Хабре)
В 60-70 гг, когда компьютеры были большими, а программы — маленькими, ещё не было единого стандарта вычислений, как и стандарта выражения самого числа с плавающей точкой. Каждый компьютер делал это по-своему, и ошибки были у каждого свои. Но в середине 70-х компания Intel решила сделать новые процессоры с поддерживаемой "улучшенной" арифметикой и заодно стандартизировать её. Для разработки привлекли профессоров Уильяма Кэхэна и Джона Палмера (нет, не автора книг про пиво). Не обошлось без драм, но всё же новый стандарт был разработан. Сейчас этот стандарт называют IEEE754
Формат записи числа с плавающей точкой
Ещё в учебниках школьного курса все сталкивались с непривычным способом записи очень больших или очень малых чисел вида
1,2×103 или
1,2E3, что равно
1,2×1000 = 1200. Это называется способ записи через экспоненту. В данном случае мы имеем дело с выражением числа по формуле:
N=M×np, где
- N = 1200 — получаемое число
- M = 1,2 — мантисса — дробная часть, без учёта порядков
- n = 10 — основание порядка. В данном случае и когда речь не идёт о компьютерах, основанием выступает цифра 10
- p = 3 — степень основания
Довольно часто основание порядка подразумевается, как
10 и записывают лишь мантиссу и значение степени основания, разделяя их буквой
E. В нашем примере я привёл равнозначные записи
1,2×103 и
1,2E3
Если всё понятно, и ностальгический экскурс в школьную программу мы закончили, то сейчас рекомендую забыть это, т. к. при формировании числа с плавающей точкой мы имеем дело со степенями двойки, а не десятки, т.е.
n = 2, вся стройная формула
1,2E3 ломается и это здорово сломало мне мозг.
Знак и степень
И что мы имеем? В итоге мы также имеем двоичное число, которое состоит из
мантиссы — часть, которую будем возводить в степень и саму степень. Кроме этого, так же как принято и у целочисленных типов, в числах с плавающей точкой есть бит, который определяет знак — будет число положительным или отрицательным.
В качестве примера предлагаю рассмотреть тип
float
, который состоит из 32 бит. С числами двойной точности
double
логика такая же, только в два раза больше бит.
Из 32 бит, первый старший отводится на знак, следующие 8 бит отводятся на экспоненту — степень, на которую будем возводить мантиссу, а остальные 23 бита — на мантиссу.
Для демонстрации давайте посмотрим на пример пример:
![Что внутри числа с плавающей точкой и как оно работает - 1]()
С первым битом всё очень просто. Если значение первого бита
0, то число, которое мы получим будет
положительным. Если бит равен
1, то число будет
отрицательным.
Следующий блок из 8 бит — блок с экспонентой.
Экспонента записывается как обычное
восьмибитное число, а чтоб получить требуемую степень нам нужно из полученного числа вычесть
127
В нашем случае восемь бит экспоненты — это
10000001. Это соответствует числу
129. Если есть вопрос, как это посчитать, то на картинке быстрый ответ. Развёрнутый можно получить на любом курсе булевой алгебры.
1×27 + 0×26 + 0×25 + 0×24 + 0×23 + 0×22 + 0×21 + 1×20 = 1×128 + 1×1 = 128+1=129
Не сложно посчитать, что максимальное число, которое мы можем получить из этих 8 бит
111111112 = 25510 (подстрочные
2 и
10 означают двоичную и десятеричную системы исчисления)
Однако, если использовать только положительные значения степени (
от 0 и до 255), то полученные числа будут иметь много чисел перед запятой, но не после?
Чтоб получать отрицательные значения степени, из сформированной экспоненты нужно вычитать
127. Таким образом, диапазон степеней будет
от -127 до 128.
Если использовать наш пример, то нужная степень будет
129-127 = 2. Пока запоминаем это число.
Мантисса
Теперь о мантиссе. Она состоит из 23 бит, однако в начале всегда подразумевается ещё одна единица, на которую биты не выделяются.
Это сделано в целях целесообразности и экономии. Одно и то же число можно выражать разными степенями, добавляя к мантиссе нули перед или после запятой.
Проще всего это понять с десятичной экспонентой:
120 000 = 1,2×105 = 0,12×106 = 0,012×107 = 0,0012×108 и т.д.
Однако, введя фиксированное число в голове мантиссы мы каждый раз будем получать новые числа. Примем как данность, что перед нашими 23 битами будет ещё один с единицей. Обычно этот бит от остальных отделают точкой, которая, впрочем, ничего не значит. Просто так удобнее
1 . 11100000000000000000000
![Что внутри числа с плавающей точкой и как оно работает - 3]()
Теперь полученную мантиссу нужно возводить в степень слева направо, уменьшая с каждым шагом степень на одну. Стартуем со значения степени, который мы получили в результате вычисления, т. е.
2
(Я специально выбрал простой пример, чтоб не писать каждое значение степени двойки и в приведенной таблице не вычислял их, когда соответствующий бит равен нулю)
1×22 + 1×21 + 1×20 + 1×2-1 = 1×4 + 1×2 + 1×1 + 1×0,5 = 4+2+1+0,5 = 7,5
и получили результат
7,5, правильность можно проверить, например, по
этой ссылке
Итоги
Стандартное число с плавающей точкой типа
float
состоит из 32 бит, первый бит — знак (+ или -), следующие восемь — экспонента, следующие 23 — мантисса
По знаку — если бит 0 — число положительное. Если бит 1 — отрицательное.
По экспоненте — побитно переводим в десятичное число (первый слева бит —
128, второй —
64, третий —
32, четвёртый —
16, пятый —
8, шестой —
4, седьмой —
2, восьмой —
1), из полученного числа вычитаем
127, получаем степень с которой будем стартовать.
По мантиссе — к имеющимся 23 битам спереди дописываем ещё один бит со значением 1 и с него начинаем возводить в полученную нами степень, с каждым следующим битом декрементируя эту степень.
That's all folks, kids!
P. S.: В виде домашнего задания, используя эту статью, оставьте в комментариях свои версии, почему при большом количестве арифметических операций с числами с плавающей точкой возникают ошибки точности
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ