Очень интересная тема. Никогда не думал, что за выработку стандарта представления чисел с плавающей запятой будут конкурировать такие компании, как DEC, National Superconductor, Zilog, Motorola, Intel. Для конструктивного описания данной темы я использовал "сливки" источников https://habr.com/ru/post/112953/ и Википедия. Надеюсь, будет полезно прочитать. Поехали!

Инициатива создать единый стандарт для представления чисел с плавающей запятой подозрительно совпала с попытками в 1976 году компанией Intel разработать «лучшую» арифметику для новых сопроцессоров к 8086 и i432. За разработку взялись ученые киты в этой области, проф. Джон Палмер и Уильям Кэхэн. Последний в своем интервью высказал мнение, что серьезность, с которой Intel разрабатывала свою арифметику, заставила другие компании объединиться и начать процесс стандартизации.

Все были настроены серьезно, ведь очень выгодно продвинуть свою архитектуру и сделать ее стандартной. Свои предложения представили компании DEC, National Superconductor, Zilog, Motorola. Производители мейнфреймов Cray и IBM наблюдали со стороны. Компания Intel, разумеется, тоже представила свою новую арифметику. Авторами предложенной спецификации стали Уильям Кэхэн, Джероми Кунен и Гарольд Стоун и их предложение сразу прозвали «K-C-S».

Практически сразу же были отброшены все предложения, кроме двух: VAX от DEC и «K-C-S» от Intel. Спецификация VAX была значительно проще, уже была реализована в компьютерах PDP-11, и было понятно, как на ней получить максимальную производительность. С другой стороны в «K-C-S» содержалось много полезной функциональности, такой как «специальные» и «денормализованные» числа (подробности ниже).

В «K-C-S» все арифметические алгоритмы заданы строго и требуется, чтобы в реализации результат с ними совпадал. Это позволяет выводить строгие выкладки в рамках этой спецификации. Если раньше математик решал задачу численными методами и доказывал свойства решения, не было никакой гарантии, что эти свойства сохранятся в программе. Строгость арифметики «K-C-S» сделала возможным доказательство теорем, опираясь на арифметику с плавающей запятой.

Компания DEC сделала все, чтобы ее спецификацию сделали стандартом. Она даже заручилась поддержкой некоторых авторитетных ученых в том, что арифметика «K-C-S» в принципе не может достигнуть такой же производительности, как у DEC. Ирония в том, что Intel знала, как сделать свою спецификацию такой же производительной, но эти хитрости были коммерческой тайной. Если бы Intel не уступила и не открыла часть секретов, она бы не смогла сдержать натиск DEC.

Подробнее о баталиях при стандартизации смотрите в интервью профессора Кэхэна [https://people.eecs.berkeley.edu/~wkahan/ieee754status/754story.html].

Разработчики «K-C-S» победили и теперь их детище воплотилось в стандарт IEEE754. Числа с плавающей запятой в нем представлены в виде знака (s), мантиссы (M) и порядка (E) следующим образом:

(-1)^s × 1.M × 2^E

Замечание. В новом стандарте IEE754-2008 кроме чисел с основанием 2 присутствуют числа с основанием 10, так называемые десятичные (decimal) числа с плавающей запятой.

Чтобы не загромождать читателя чрезмерной информацией, которую можно найти в Википедии, рассмотрим только один тип данных, с одинарной точностью (float). Числа с половинной, двойной и расширенной точностью обладают теми же особенностями, но имеют другой диапазон порядка и мантиссы. В числах одинарной точности (float/single) порядок состоит из 8 бит, а мантисса – из 23. Эффективный порядок вычисляется как Е-(2^(n-1)-1) (данный вычет делается, чтобы не хранить отдельно знак порядка, в нашем случае он определяется как E-127). Например, число 0,15625 будет записано в памяти как

https://bitbucket.org/landwatersun/forum/downloads/201909061545.gif

В этом примере:
Знак s=0 (положительное число)
Порядок E=01111100(2)-127(10) = -3
Мантисса M = 1.01(2) (первая единица не явная)
В результате наше число F = 1.01(2)E-3 = 2^(-3)+2^(-5) = 0,125 + 0,03125 = 0,15625

Чуть более подробное объяснение:
Здесь мы имеем дело с двоичным представлением числа «101» со сдвигом запятой на несколько разрядов влево. 1,01 — это двоичное представление, означающее 1×2^0 + 0×2^(-1) + 1×2^(-2). Сдвинув запятую на три позиции влево получим 1,01e-3 = 1×2^(-3) + 0×2^(-4) + 1×2^(-5) = 1×0,125 + 0×0,0625 + 1×0,03125 = 0,125 + 0,03125 = 0,15625.

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

Ноль (со знаком):
https://bitbucket.org/landwatersun/forum/downloads/201909071647.png

Неопределенность (NaN):
https://bitbucket.org/landwatersun/forum/downloads/201909071648.png

Бесконечности:
https://bitbucket.org/landwatersun/forum/downloads/201909071649.png

Любознательный читатель вероятно заметил, что в описанном представлении чисел с плавающей запятой существует два нуля, которые отличаются только знаком. Арифметика отрицательного нуля аналогична таковой для любого отрицательного числа и понятна интуитивно. Вот несколько примеров:
https://bitbucket.org/landwatersun/forum/downloads/201909091354.png

Проблему ошибок округления хорошо описывает следующий показательный пример. Пусть имеем нормализованное представление с длиной мантиссы |M|=2 бита (+ один бит нормализации) и диапазоном значений порядка -1≤E≤2 (количество бит порядка – 2). В этом случае получим 16 чисел:
https://bitbucket.org/landwatersun/forum/downloads/201909091446.gif

Крупными штрихами показаны числа с мантиссой, равной 1,00. Видно, что расстояние от нуля до ближайшего числа (0 — 0,5) больше, чем от этого числа к следующему (0,5 — 0,625). Это значит, что разница двух любых чисел от 0,5 до 1 даст 0, даже если эти числа не равны. Что еще хуже, в пропасть между 0,5 и 0 попадает разница чисел, больших 1. Например, «1,5-1,25=0» (см. картинку).

В «околонулевую яму» подпадает не каждая программа. Согласно статистике 70-х годов в среднем каждый компьютер сталкивался с такой проблемой один раз в месяц. Учитывая, что компьютеры приобретали массовость, разработчики «K-C-S» посчитали эту проблему достаточно серьезной, чтобы решать ее на аппаратном уровне. Предложенное ими решение состояло в следующем. Мы знаем, что при значениях порядка и мантиссе равных нулю число тоже считается равным нулю. Однако, если же мантисса не нулевая, то число считается не нулевым, а его эффективный порядок считается как Е-(2^(n-1)), где n это число бит порядка, причем неявный старший бит мантиссы полагается равным нулю. Такие числа называются денормализованными.

Введем новое значение порядка, E=-2, при котором числа являются денормализованными. В результате получаем новое представление чисел:
https://bitbucket.org/landwatersun/forum/downloads/201909091447.gif

Интервал от 0 до 0,5 заполняют денормализованные числа, что дает возможность не проваливаться в 0 рассмотренных выше примерах (0,5-0,25 и 1,5-1,25). Это сделало представление более устойчиво к ошибкам округления для чисел, близких к нулю.

Но роскошь использования денормализованного представления чисел в процессоре не дается бесплатно. Из-за того, что такие числа нужно обрабатывать по-другому во всех арифметических операциях, трудно сделать работу в такой арифметике эффективной. Это накладывает дополнительные сложности при реализации АЛУ в процессоре. И хоть денормализованные числа очень полезны, они не являются панацеей и за округлением до нуля все равно нужно следить. Поэтому эта функциональность стала камнем преткновения при разработке стандарта и встретила самое сильное сопротивление.

Вышесказанное указывает на то, что не все десятичные числа имеют двоичное представление с плавающей запятой. Например, число «0,2» будет представлено как «0,200000003» в одинарной точности. Соответственно, «0,2 + 0,2 ≈ 0,4». Абсолютная погрешность в отдельном случае может и не высока, но если использовать такую константу в цикле, можем получить накопленную погрешность. А при вычитании близких чисел значимые разряды могут потеряться, что может в разы увеличить относительную погрешность. Для многих широко распространенных математических формул математики даже разработали специальную форму, которая позволяет значительно уменьшить погрешность при округлении. Например, расчет формулы «x^2-y^2» лучше вычислять используя формулу «(x-y)(x+y)».

Вследствие этого можно отметить, что в финансовой сфере вычисления не следует проводить с использованием типов float или double, а рекомендуется использовать тип decimal. К примеру, в .NET Framework двоичное представление значения Decimal состоит из 1-разрядного знака, 96-разрядного целого числа и коэффициента масштабирования, используемого для деления 96-разрядного целого числа и указания того, какая часть является десятичной дробью. Коэффициент масштабирования неявно является числом 10, возведенным в степень в диапазоне от 0 до 28. Таким образом, двоичное представление значения Decimal определяется формой (от-2^96 до 2^96)/(10^(от 0 до 28)), где -(2^96-1) это MinValue, а (2^96-1) это MaxValue.