Введение Давайте напишем простейшую программу на питоне: pri | Holy Python
Введение
Давайте напишем простейшую программу на питоне:
print(0.1 + 0.2)
Неопытные программисты с уверенностью ответят что выводом будет 0.3. Однако как вам известно это не так, выводом данной программы будет непонятное число с большим количеством нулей и четвёркой в конце:
0.30000000000000004
И это проблема не питона, вы можете запустить такую же программу на js или c++. Вывод будет таким же.
Что же происходит на самом деле?
Давайте разбираться.
IEEE
Чем отличается целое число от дробного с точки зрения хранения?
Правильно в дробном числе есть точка. Точку не возможно представить с помощью нулей и единиц, поэтому нужна другая, специальная форма хранения такого числа. Данную задачу решил "Институт инженеров электроники и электротехники"(IEEE), он создал стандарт хранения дробных чисел - IEEE 754.
Как перевести дробное число из десятичной системы счисления в двоичную?
Перед тем как перейти к изучению стандарта IEEE 754 давайте рассмотрим как перевести дробное число из десятичной системы счисления в двоичную.
В качестве примера возьмём число 3.25. Чтобы перевести его в двоичную систему счисления, нужно перевести целую и дробную часть в двоичную систему поотдельности.
Начнём с целой части. Здесь все просто, мы делим число на 2 пока последнее частное не станет меньше двух. Последнее частное и все остатки записываем в обратном порядке.
Пример:
3 ÷ 2 = 1 (1)
3 в двоичной системе счисления - это 11
Перейдём к дробной части. Здесь вместо деления нам нужно умножение. Мы умножаем дробную часть на 2 пока не получим в ответе единицу. А затем просто склеиваем целые части ответов.
Пример:
0.25 * 2 = 0.5
0.5 * 2 = 1.0
0.25 в двоичной системе - это 01
IEEE 754
Итак, мы успешно перевели дробное число в двоичный вид: 11.01
Теперь нам нужно как-то хранить наше число. Для этого стандарт предлагает использовать экспоненцальную запись, которая соответствует, вот этой формуле:
(-1)^s*1.M*10^E
s - знак числа
m - мантиса(дробная часть числа)
e - это количество знаков на которое нужно сдвинуть точку, чтобы перед ней осталась только одна единица.
Чтобы сохранить число в такой форме стандарт предлагает 3 основных формата хранения:
1. Одинарный - 32 бит
2. Двойной - 64 бит
3. Четырехкратный - 128 бит
Чем больше размер тем выше точность хранимого числа.
В качестве простого примера рассмотрим 32 битный формат.
Первый бит - бит знака. Если он равен нулю то число положительное, если он равен единице то число отрицательное.
Следующие 8 бит - выделены для хранения степени. Чтобы определить знак степени, нам нужно прибавить 127 к значению степени. Если число будет больше 127, то степень положительная, иначе - отрицательная.
Оставшиеся 23 бита - выделены для хранения мантиссы. Если мантисса меньше 23 бит, оставшиеся пространство заполняют нулями.
Вы можете спросить, а где хранится целая часть и основание степени?
Ответ прост - целая часть всегда равна единице, а основание степени всегда равно десяти => для экономии памяти их можно не хранить!
Давайте сохраним наше число в памяти.
1. Сдвинем точку на 1 знак влево 1.101. e = 1
2. Первый бит знака равен нулю, так как число положительное
3. e + 127 = 128 => число положительное.
4. Переведём 128 в двоичную систему счисления.
128 ÷ 2 = 64 (0)
64 ÷ 2 = 32 (0)
32 ÷ 2 = 16 (0)
16 ÷ 2 = 8 (0)
8 ÷ 2 = 4 (0)
4 ÷ 2 = 2 (0)
2 ÷ 2 = 1 (0)
128 в двоичной системе счисления -
10000000
5. Запишем 10000000 в 8 бит предназначеные для степени.
6. Запишем 101 в 23 бита предназначеные для хранения дробной части. Оставшиеся биты заполним нулями.
7. Мы сохранили наше число в памяти!
Вот результат выполнения всех шагов:
0 10000000 10100000000000000000000
Отлично, теперь попробуем перевести сохранённое число обратно:
s = 0 (знак положительный)
m = 101
e = 1
-1^s * 1.m * 10^e = 11.01
Осталось перевести 11.01 в десятичную систему счисления и мы получим наше число - 3.25.
Проблема
Не всегда всё происходит настолько гладко. Некоторые дробные числа просто нельзя перевести в двоичную систему счисления.
Пример: