Найти в Дзене
CodeLab

Проблема чисел с плавающей точкой: почему 0.1 + 0.1 не будет равно 0.2?

👨‍💻 Сегодня буду рассказывать вам про хранение памяти в компьютере, как раз это свяжем с проблемой чисел с плавающей точкой, поэтому постарайтесь все внимательно прочитать‼️ ⭕️ Для хранения одной ячейки информации в памяти используется байт (8 битов), а не один бит. В одном байте, как мы знаем 8 бит, но процессор обращается к памяти на уровне байтов. Он может иметь значение 1 или 0. следовательно 1 будет означать передачу тока в транзистор, а 0 – нет. ⭕️ Каждая такая ячейка имеет уникальный адрес, который используется для доступа к данным, хранящимся в этой ячейке. А нужно это для быстрого поиска и использования информации, процессор будет напрямую обращаться к любой ячейке памяти. Существует формат IEEE 754, который описывает числа с плавающей точкой. Число с плавающей запятой состоит из набора отдельных разрядов, условно разделенных на знак, порядок и мантиссу. ⭕️ Порядок (экспонента) — отвечает за масштаб числа, сдвигая десятичную точку. ⭕️ Мантисса — содержит значимые цифры числ

👨‍💻 Сегодня буду рассказывать вам про хранение памяти в компьютере, как раз это свяжем с проблемой чисел с плавающей точкой, поэтому постарайтесь все внимательно прочитать‼️

⭕️ Для хранения одной ячейки информации в памяти используется байт (8 битов), а не один бит.

В одном байте, как мы знаем 8 бит, но процессор обращается к памяти на уровне байтов. Он может иметь значение 1 или 0. следовательно 1 будет означать передачу тока в транзистор, а 0 – нет. ⭕️ Каждая такая ячейка имеет уникальный адрес, который используется для доступа к данным, хранящимся в этой ячейке. А нужно это для быстрого поиска и использования информации, процессор будет напрямую обращаться к любой ячейке памяти.

Существует формат IEEE 754, который описывает числа с плавающей точкой.

Число с плавающей запятой состоит из набора отдельных разрядов, условно разделенных на знак, порядок и мантиссу.

⭕️ Порядок (экспонента) — отвечает за масштаб числа, сдвигая десятичную точку.

⭕️ Мантисса — содержит значимые цифры числа. Теперь об этом поподробнее.

Итак, возьмем вещественное число
6.25 и переведем его в двоичный код. (Хочу показать как найти мантиссу)

⭕️ Целую часть (6) мы делим на 2, при этом записывая остаток от деления:

6 : 2 = 3[0], 3 : 2 = 1[1], 1 : 2 = 1[1]❗️ (В квадратных скобках остаток от деления). (И получили мы 110, т.к с конца записали)

Дробную часть (0.25) в свою очередь умножаем на 2, при этом, целую часть записываем, а дробную продолжаем умножать: 0.25 * 2 = 0.5 (0 записали, а 0.5 продолжаем умножать на 2, получаем 1 (0.5 x 2 = 1.0), единицу тоже записываем (Записываем не с конца, просто получаем на выходе 01).

🔗Теперь, все как в школе🧑‍💻: остатки от деления целой частей частей записываем с конца - 110, и дробную, также с конца - получаем 01. Соединяем и получаем 110.01, это наше число 6.25 только в двоичном виде. Если вам не понятен перевод числа, я прикреплю фотки после сообщения, это будет [1] изображение. Так что это за мантисса вообще? ‼️ Ниже будет разбор по фотке, чтобы было понятнее ‼️

-2

⭕️ Мантисса (или дробная часть) — это часть числа, которая определяет его точность. Я уже упомянул стандарт IEEE-754, согласно этому стандарту, двоичное число представляется в виде формулы, где s — знак числа, M — мантисса, B — основание, E — экспонента (порядок, степень двойки).
⭕️ Поскольку мы работаем в двоичном коде, основание равно двум, и формула принимает следующий вид: [2].

-3

Вещественное число мы сохраняем как три отдельных числа: знак, экспоненту и мантиссу, да и все бы хорошо, но количество бит для хранения ограниченно форматом.

👨‍💻Формат если че это такая структура, которая описывает, как именно должны быть организованны данные. ❗️ Именно формат определяет, сколько битов выделяется для различных частей данных: для мантиссы, знака и порядка(экспоненты).

⭕️ Рассматриваем формат, в котором доступно 32 бита: 1 бит знака, 8 битов для экспоненты и 23 бита для мантиссы. Этот формат называется "одинарной точностью". Также есть формат 'Двойная точность (64 бита)': 1 бит для знака, 11 битов для экспоненты, 52 бита для мантиссы.

⭕️ Дак вот, нам нужно сдвинуть запятую нашего числа с прошлого поста 110.01 влево настолько, пока в целой части не останется единица. При этом, каждый сдвиг увеличивает степень основания (сейчас поясню).

⭕️ На первом этапе у нас число 110.01 * 10 ** 0, все написано по формуле! Десятка в основании, так как это 2 в двоичной степени ⭕️. Следующим этапом будет сдвиг запятой влево, т.е получится 11.001 * 10 ** 1.

⭕️ Ну и последним этапом, 1.1001 * 10 ** 2.
В итоге мы получаем
1.1001 x 2 ** 2. (По сути просто сдвигаем влево и увеличиваем степень нашей '2')

Здесь 1.1001 - это наша мантисса, а поскольку наше число положительное, бит знака будет равен 0.

На этом этапе хочу поблагодарить автора статьи https://struchkov.dev/blog/ru/floating-point-math/, потому что лично я узнал немало нового и делюсь этим с вами.

⭕️ Чтобы дальше получить экспоненту, нам нужно прибавить число 127. То есть было у нас число 1.1001 * 2 ** 2, к этой двойке прибавляем 127

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

⭕️ Решением проблемы будет состоять в хранении экспоненты относительно середины доступного диапазона значений, то есть диапазон чисел от 0 до 255, а для 8-битного диапазона этой "серединой" будет число 127 [3] изображение.

⭕️ Поскольку преобразования происходят в двоичном коде, основание экспоненты равно 2
⭕️ Поскольку преобразования происходят в двоичном коде, основание экспоненты равно 2

⭕️ По итогу, к нашей изначальной степени 2 прибавляем 127 и получаем 129. При преобразовании 129 в двоичную получаем 10000001 - это и будет экспонента.

Формат "одинарной точности", в котором доступно 32 бита: 1 бит знака, 8 битов для экспоненты и 23 бита для мантиссы.
Формат "одинарной точности", в котором доступно 32 бита: 1 бит знака, 8 битов для экспоненты и 23 бита для мантиссы.
🔗Укажем про прием, который использовали еще в первых машинах, который позволял не сохранять целую часть числа, так как она всегда равна единице. Это выглядит вот так, поэтому целую часть мы не записываем, она всегда равна единице.
🔗Укажем про прием, который использовали еще в первых машинах, который позволял не сохранять целую часть числа, так как она всегда равна единице. Это выглядит вот так, поэтому целую часть мы не записываем, она всегда равна единице.
✅ Нормализованное число (нормисное) имеет следующий вид: (Просто добавляем единицу)
✅ Нормализованное число (нормисное) имеет следующий вид: (Просто добавляем единицу)
⭕️ 8 битный диапазон, для нахождения экспоненты. Все отрицательные степени будут располагаться левее числа 127, а все положительные - правее.
⭕️ 8 битный диапазон, для нахождения экспоненты. Все отрицательные степени будут располагаться левее числа 127, а все положительные - правее.
🧑‍💻 Конечный результат
🧑‍💻 Конечный результат

Мне лично стало интересно почитать и изучить, как это работает в действительности, а не просто 'а нуу, 0.1 + 0.1 не равно 0.2 да и ладно, написано, значит написано'. Спасибо большое автору статьи! Сейчас скину пару изображений ... а продолжение будет совсем скоро.