*Все примеры здесь рассматриваются для 64 битных чисел(все примеры аналогичны и для других значений), если не указано иное.

Более 90% всех программистов знают, что такое обычные числа с плавающей запятой: binary32/binary64/binary128, их часто называют float, double и т.д. соответсвенно, есть много информации о том почему 0,1 не может существовать в бинарном виде, что при большом количестве значащих цифр будут недостаток точности, даже, если ты не выходишь за рамки 16 цифр, зато они быстрые… Но почти нет информации о том, что прекрасное решение, которое сохраняет все достоинства и исправляет недостатки, есть, даже в самом, обновленном стандарте плавающих чисел IEEE 754-2008 уже больше 15 лет, это decimal floating point(DFP).
Для начала вспомним устройство обычного binary64: 1 бит знака, 11 битов экспоненты, 52 бита мантиссы. Давайте лучше картинку покажу:

Раньше попытка создать десятичную систему для плавающих чисел привела к прожорливому, которому, чем больше памяти дашь, тем больше он съест BCD, ты кодировал числа тетрадами, но главная проблема это, то что четвертый бит использовался лишь для трёх чисел, из-за этого даже двоичная мантисса для иррациональных чисел была точнее, так как позволяла кодировать больше десятичных цифр: 16,3 против 13.
Потеря битов была замечена в конце 1960-х, поэтому была создана кодировка Чен-Хо, которая кодировала 3 десятичные цифры в 10 битов, после 2002 году её доработали и превратили в плотно упакованную десятичную дробь или правильнее Densely packed decimal(DPD) были сохранены все преимущества и добавлено ещё одно первые числа в этой кодировке были такими же, как в BCD, это упрощало преобразования, поэтому её включили в обновленный стандарт IEEE 754, там же оказалась ещё одна Binary Integer Decimal(BID). Потери были хоть какими-то в этом случае только для 10 байтов: 1000 против 1024 десятичных цифр, но покажите мне реальное применение таких чисел с 80 битами в мантиссе(!).
Вот иллюстрация кодирования цифр в BCD, Чен-Хо, DPD:

Кодировка есть, плавающие числа есть, так что же не объединить? Это и было сделано: мы получили Decimal floating-point (DFP), в них может использоваться DPD, либо BID(разницы особо нет).
Давайте проведём сравнение binary64 vs decimal64(для остальных вариантов всё будет аналогично):
- Количество значащих цифр 16,3 и 16, в числе 1 + 1e-16 эти 16 цифр совпадают, но, например, в числе 8,1 binary64 начнёт писать 8,09999…, тому, что нет даже единицы я был удивлён, а если эти два числа начать складывать между собой, начинают появляться двойки в первых 16 цифрах… - Отдам голос за decimal64. 
- Диапазон экспоненты 10−308 до 10308 и 10−383 до 10384 - здесь победа Decimal64. 
- 
Скорость выполнения: Первый ответ, который приходит в голову - binary быстрее. Но тут очень интересно: binary считает не ALU(он просто не умеет) а отдельный подпроцессор(FPU), поэтому тоже самое можно сделать и для Decimal(сейчас есть реализация только в микрокоде). Это как сравнивать двоичные и троичные компьютеры при этом вторые эмулировать программно. Паритет. 
Итог: Decimal floating point очень интересная и выгодная из-за возможности представить точно десятичную систему счисления, а в некоторых случаях даже иррациональную и большего диапазона экспоненты одновременно замена binary, реализовать её не сложнее FPU, а плюсов будет больше(не только для финансов, а также, например, для графики, пример приводил в комментарии @kinh,.но это только один пример, их больше). Почему не заменили в ПК(моё мнение): хоть она и есть в IEEE 754, мало кому она известна, а производителям процессора для ПК лень менять, так как запроса нет, а запроса нет, потому что нет реализаций и даже в C++ нет Decimal, получается замкнутый круг.
Некоторые производители, как писали в комментариях и по разным источникам, даже наоборот убирают команды связанные с десятичными форматами прошлого, но на это можно посмотреть с другой стороны, что мы просто избавляемся от рудиментов, это даже вероятнее, так как условным прожорливым, неэффективным BCD никто пользоваться не собирается в наше время.
Но это моё мнение, я буду рад прочитать комментарии, которые дадут другую точку зрения, их плюсы и минусы Decimal, и почему нет аппаратной реализации в персональных компьютерах.
Цель этой статьи познакомить большее количество людей с этой удивительной технологией, поэтому без большого количества формул и т.п. :).
Комментарии (38)
 - SIISII29.08.2024 12:11+2- Насчёт быстродействия. Если делать отдельный аппаратный блок для десятичных операций, он будет либо работать медленнее, чем аналогичный двоичный блок, либо будет существенно сложней (и не факт, что такой же быстрый: усложнение железа нередко его замедляет, хотя есть нюансы :) ). Но это уже переход на уровень схемотехники. 
 - DimPal29.08.2024 12:11+2- Двоично-десятичная система не является "более точной". В десятичной системе тоже легко получить бесконечно длинные дробные части (в периоде и без). Сохранение введенного вручную числа (любимое 0.1) не является признаком "точности". Уж если такая задача реально возникает, то она легко исправляется другими методами. Например, округлением до нужного знака после запятой, а это уже больше связано с алгоритмом преобразования двочного представления в десятичное.  - SIISII29.08.2024 12:11+3- Проблема не в бесконечно длинных числах, а в том, что для подобных случаев для десятичной системы у финансистов есть строгие правила, как именно надо считать и формировать конечный результат. В двоичной системе эти правила автоматически работать не будут, поскольку не каждая конечная десятичная дробь может быть представлена точно в двоичном виде. Отсюда и возникают проблемы. Они решаются использованием только целых чисел (точней, с фиксированной запятой, но технически это те же целые), что достаточно геморройно для программиста. Вещественные же "самоокругляются" в определённых случаях, и уследить за этим куда сложней. 
  - LanskoyGames Автор29.08.2024 12:11- В числе 1 + 1e-16, 16 цифр совпадают, как и должно быть, но, например, в числе 8,1 binary64 начнёт писать 8,09999…, тому, что нет даже единицы я был удивлён, а если эти числа начать складывать… - Отдам голос за decimal64  - DimPal29.08.2024 12:11- Вот об этом и речь. Это не потеря "точности", это грубое округление. Можно просто научить функцию преобразования в строку (в десятичный формат?) красиво округлять для заданного числа разрядов после запятой.  - SIISII29.08.2024 12:11+2- Нет, это вполне может приводить к потере точности. Операции-то выполняются одна за другой, и каждая из них потенциально даёт результат, чуть-чуть отличающийся от точного -- причём отличающегося не так, как он отличался бы при расчётах в десятичной системе. Такие мелкие ошибки могут накапливаться, и итоговый результат может не совпасть с требуемым по строгим правилам. Никаким округлением это не исправить.  - N-Cube29.08.2024 12:11- Эээ, а ничего, что когда-то на счетах справлялись без потери точности? Кстати, тогда еще доли копеек в ходу были, справлялись и с ними.  - SIISII29.08.2024 12:11- На счётах считали в десятичном виде -- и тогда же вырабатывали правила, как надо считать и как получать результаты. Для десятичной системы вырабатывали, а не для двоичной, и при расчётах в двоичной с последующим преобразованием в десятичную может быть получен не тот результат, который был бы получен, если бы все расчёты вели в десятичном виде. Простым округлением эта проблема не решается.  - N-Cube29.08.2024 12:11- Вы не понимаете принцип - как на счетах работали с дробями? Как выше писали, десятичные дроби тоже бывают бесконечными, но на счетах это решили (как?). 
 
 
  - DimPal29.08.2024 12:11- При равных условиях (выполняемых операциях) потеря точности вычислений для обеих систем будет примерно одинаковая (у двоичной потери будут даже меньше). Друг от друга они действительно будут различаться.  - LanskoyGames Автор29.08.2024 12:11- Как? Минимальный шаг между числами в десятичной системе намного больше, чем в двоичной для одного знака в пять раз, для двух в 25 раз и т.д.. А здесь Decimal даже точнее будет  - DimPal29.08.2024 12:11- Вы неправильно понимаете принцип двоично-десятичного представления. на один десятичный разряд приходится 4 бита, а не один. Для упомянутой в статье кодировке Чен-Хо на 3 десятичных разряда тратися 10 бит (т.е. 1000 vs 1024). Двоично-десятичное представление рыхлое - содержит неиспользуемые значения на всём возможном диапазоне.  - LanskoyGames Автор29.08.2024 12:11- Я сейчас статью процитирую: «Потери были хоть какими-то в этом случае только для 10 байтов: 1000 против 1024 десятичных цифр, но покажите мне реальное применение таких чисел с 80 битами в мантиссе(!).». Даже для 128 бит разницы нет, но зато мы получаем более точную арифметику. 
 
 
 
  - Uint3229.08.2024 12:11- Можно и не накапливать результат. - 10.2 * 3 >= 30.6- возвращает false при любой точности binary float  - LanskoyGames Автор29.08.2024 12:11- Было бы лучше, если double и float, например, всегда выдавали результат чуть в большую сторону, но для 0,1 они выдают число больше 0,1, а, например, для 8,1 меньше 8,1, это приводит к тому, что после сложения этих двух чисел появляются двойки в первых 16(отсюда и эта ошибка). Это ещё один плюс decimal64 в плане точности и экспонента больше, но поддержки Decimal64 нет. 
 
 
  - LanskoyGames Автор29.08.2024 12:11- На выводе округлится, но в аппаратном плане округлить нельзя, а значит до свидания даже 16 десятичных цифр 
 
 
 
 - kinh29.08.2024 12:11+1- Десятичные числа нужны не только для финансистов. Ещё они нужны для графической разметки. Описание разметки задаётся примерно в виде: "умножить высоту этого окна на 80% и сложить с удвоенной толщиной этой рамки". Если мы преобразовали десятичные числа в двоичные, и прокручиваем длинный документ, то ошибка накапливается, пока графические элементы не начнут съезжать от правильных положений. Всё, что касается разметки, 3D-графики, чертежей - там нужны десятичные числа. 
 
           
 
SIISII
Реально десятичные вещественные реализованы в z/Architecture. Основная польза от них, как представляется, не для научных расчётов, а для финансов -- раньше там использовались тоже десятичные числа, но целые переменной длины (1-31 цифра плюс знак), что было реализовано ещё в Системе 360 и благополучно дожило до наших дней.
В финансах десятичная система удобней, в частности, из-за того, что все правила округления и т.п. при расчётах вырабатывались веками и именно для десятичной системы, и, чтобы им следовать, считая в двоичной, приходится принимать специальные меры. Для научных расчётов этого не требуется: там результаты практически всегда не точные, а приблизительные, и важно лишь то, какая точность достигается, а не конкретные правила (в отличие от финансов, где считать надо только так, как это установлено регулирующими документами).
В общем, в той же z/Architecture двоичные и десятичные будут благополучно сосуществовать и дальше, а вот внедрение десятичных (хоть целых, хоть вещественных) в другие архитектуры представляется довольно сомнительным, не смотря на определённые выгоды от них в некоторых задачах.
unreal_undead2
А в финансах именно десятичные вещественные с мантиссой/порядком (когда мы, соответственно, теряем целые рубли, оперируя суммами 10^много рублей) действительно нужны? На взгляд дилетанта - там либо операции с суммами на счетах, налогами и т.п., когда нужна точность до копеек независимо от суммы, либо аналитика (просчитать какие-нибудь тренды), когда можно округлить и "по двоичному".
SIISII
Исторически для финансов используются целые BCD (точней, с фиксированной запятой; целые -- их частный случай). Конкретно на z/Architecture преимущество от вещественных десятичных может быть в том, что их можно хранить в регистрах процессора, а обычные целые десятичные всегда хранятся только в памяти и имеют переменную длину; соответственно, обрабатывать вещественные целые можно быстрей. Но, чтоб не было недопустимых округлений, придётся постоянно следить за порядком и не допускать его выхода за определённые пределы. С целыми в памяти в этом смысле проще: если результат не лезет в отведённое поле, автоматом фиксируется переполнение. Как обстоит с этим дело на практике, не скажу: к финансовому сектору отношения не имею и, надеюсь, иметь не буду :)
N-Cube
При современных объемах хранения данных, копейки или центы в формате int64 решают все практические задачи в финансах. Объем нормативных и прочих документов, хранимых в центробанке, явно на много порядков превышает объем всех финансовых транзакций, так что мало проку экономить на битах в числах.
SIISII
Ну, с появлением 64-разрядных целых появились и шутки про способность компьютера считать госдолг США :) Если же серьёзно, считать числа необходимой разрядности можно было на Системе 360 (собственно, и считали), но это делалось без лишнего геморроя лишь в десятичных операциях: двоичных операций над 64-разрядными числами не было (сама архитектура 32-разрядная). Для реализации 64-разрядных операций сложения-вычитания требовалась последовательность команд типа "сложение - условный переход - сложение" (перенос и переполнение выявлялись, но не могли прямо использоваться арифметическими командами). С умножением и делением, понятное дело, ещё хуже.
unreal_undead2
Случаи разные бывают. Скажем, ВВП Югославии в 1993ем был в районе 100 миллиардов долларов США, доллар 31 декабря 1993го стоил 1,775,998,646,615 динаров - так что на работу с суммами порядка ВВП в динарах, если ничего не путаю, фиксированных 64 бит не хватило бы. Не факт, что подобное не повторится.
LanskoyGames Автор
Зато хватает Decimal64 или double
SIISII
Если не ошибаюсь, для выражения ВВП в динарах потребовалось бы 22 десятичных разряда -- если б и влезло в 64-разр целое, то на пределе. А вот десятичные переменной длины Системы 360 справились бы -- там до 31 цифры включительно :)
LanskoyGames Автор
Нет, сейчас посмотрел даже в 8,1 нет единицы в Ieee754, так что я ошибся, с binary всё ещё хуже
unreal_undead2
2^64=1.8e19 , так что не влезет
N-Cube
Вот только какое отношение пиковый курс доллара имеет к годовому ввп в местной валюте? А по среднему курсу за год ВВП без проблем считается. При желании, можно придумать и ситуацию, когда экспоненты не хватит, и даже не факт, что где-то когда-то это не произойдет - так вы предлагаете всем заранее отказаться от экономики и математики? :)
unreal_undead2
У топовых предприятий обороты порядка ВВП страны, и налоги и прочее им надо считать в местной валюте - так что при составлении отчётности за 1993 год надо было оперировать подобными суммами.
Предлагаю использовать целые произвольной длины (с оптимизациями на случай, когда значение влезает в машинное слово).
N-Cube
Пиковый курс на несколько минут или часов никак не может повлиять на годовую отчетность, о чем вы? Там все скачки курса заняли пару дней, что никак не влияет.
unreal_undead2
Да, соглашусь, возможно в отдельной компании проблем не было. Но всё таки двенадцатизначный курс там держался неделю и всё это время в ЦБ надо было с точностью до динара рассчитывать имеющиеся ресурсы включая золотовалютные запасы.
LanskoyGames Автор
В числе 1 + 1e-16, 16 цифр совпадают, как и должно быть, но, например, в числе 8,1 binary64 начнёт писать 8,09999…, тому, что нет даже единицы я был удивлён, а если эти числа начать складывать… - Отдам голос за decimal64
Sap_ru
А x86 тоже до сих пор есть десятичная арифметика.
SIISII
Изначально в 8086/8088 (16-разрядных ещё, не 32) были команды коррекции для обычных арифметических операций. Это -- не полноценная десятичная арифметика, какая имеется на ИБМовских мэйнфреймах, это -- "жалкое подобие левой руки" (с), родом из калькуляторных применений. Но с переходом на 64 бита и её полностью выпилили вместе с рядом других команд. Так что современные процы могут выполнять эти команды только в 16- и 32-разрядных режимах, где они сохраняются для совместимости.
LanskoyGames Автор
Только непонятно для чего выпиливать?
SIISII
От них толку было примерно 0: они крайне неэффективны, почему и не использовались. Если хотите реально разобраться -- учите ассемблер и пробуйте написать полноценную десятичную арифметику а-ля мэйнфреймы на нём :)
LanskoyGames Автор
А для графики?