Я могу найти много вопросов о библиотеках для представления сумм в какой-то валюте. И о давней проблеме, почему вы не должны хранить валюту как число с плавающей точкой IEEE 754. Но я не могу найти больше ничего. Конечно, есть еще много информации о валюте в реальном мире. Меня особенно интересует, что вам нужно знать, чтобы представить это в физическом выражении (например, с долларом вы никогда не будете иметь точность менее 0,01 доллара, что позволяет представлять его как целое число центов).
Но трудно предположить, насколько универсальной является ваша программа, когда единственными валютами, которые вы знаете, являются популярные западные (например, доллар, евро и фунт). Что еще относится к знаниям в чисто программной перспективе? Меня не волнует тема конверсии.
В частности, что нам нужно знать, чтобы иметь возможность хранить значения в некоторой валюте и распечатывать их?
Ответы:
Да неужели?
Пожалуйста, не стесняйтесь хранить дюймы в числах с плавающей точкой IEEE 754 . Они хранят именно то, что вы ожидаете.
Пожалуйста, не стесняйтесь хранить любую сумму денег в числах с плавающей точкой IEEE 754, которые вы можете хранить, используя галочки, которые делят линейку на доли дюйма.
Почему? Потому что, когда вы используете IEEE 754 , вы храните его.
Что касается дюймов, они разделены пополам. Что касается большинства видов валюты, то они разделены на десятые доли (некоторые - нет, но давайте сосредоточимся).
Эта разница не будет слишком запутанной, за исключением того, что для большинства языков программирования ввод и вывод из чисел с плавающей запятой IEEE 754 выражается в десятичных числах! Что очень странно, потому что они не хранятся в десятичных числах.
Из-за этого вы никогда не увидите, как эти биты делают странные вещи, когда просите компьютер сохранить
0.1
. Вы видите странность только тогда, когда вы делаете математику против нее, и в ней есть странные ошибки.От эффективного Java Джош Блоха :
Производит
0.6100000000000001
Что больше всего говорит об этом, так это не
1
сидение справа там. Это странные цифры, которые должны были быть использованы, чтобы получить его. Вместо того, чтобы использовать самый популярный пример,0.1
мы должны использовать пример, который показывает проблему и избегает округления, которое скрыло бы ее.Например, почему это работает?
Производит
-0.01
Потому что нам повезло.
Я ненавижу проблемы, которые трудно диагностировать, потому что иногда мне везет.
IEEE 754 просто не может хранить 0.1 точно. Но если вы попросите его сохранить 0,1, а затем попросите его напечатать, он покажет 0,1, и вы будете думать, что все в порядке. Это не хорошо, но вы не можете видеть это, потому что округляется, чтобы вернуться к 0,1.
Некоторые люди путают чертовски с другими, называя эти несоответствия округляя ошибки. Нет, это не ошибки округления. Округление делает то, что должно, и превращает то, что не является десятичным, в десятичное, чтобы оно могло печататься на экране.
Но это скрывает несоответствие между тем, как отображается число и как оно сохраняется. Ошибка не произошла, когда произошло округление. Это произошло, когда вы решили поместить число в систему, которое не может хранить его точно, и предположили, что оно хранится именно тогда, когда его нет.
Никто не ожидает, что π будет хранить именно в калькуляторе, и им удается нормально с ним работать. Так что проблема даже не в точности. Это об ожидаемой точности. Компьютеры отображают одну десятую, как
0.1
и наши калькуляторы, поэтому мы ожидаем, что они будут хранить одну десятую точно так же, как наши калькуляторы. Они не Что удивительно, так как компьютеры стоят дороже.Позвольте мне показать вам несоответствие:
Обратите внимание, что 1/2 и 0.5 идеально выровнены. Но 0.1 просто не совпадает. Конечно, вы можете приблизиться, если продолжите делить на 2, но вы никогда не попадете точно. И нам нужно все больше и больше битов каждый раз, когда мы делим на 2. Поэтому для представления 0.1 в любой системе, которая делит на 2, нужно бесконечное количество битов. Мой жесткий диск не такой большой.
Поэтому IEEE 754 перестает пытаться, когда заканчивается бит. Что хорошо, потому что мне нужно место на моем жестком диске для ... семейных фотографий. Нет, правда. Семейные фотографии. :П
В любом случае, то, что вы вводите, и то, что вы видите, - это десятичные дроби (справа), а то, что вы сохраняете, - это двоичные числа (слева). Иногда они совершенно одинаковы. Иногда это не так. Иногда это выглядит так, будто они одинаковы, а просто нет. Это округление.
Пожалуйста, если вы используете мои десятичные деньги, не используйте числа с плавающей запятой или двойные числа.
Если вы уверены, что такие вещи, как десятые доли копейки, не будут участвовать, просто храните копейки. Если нет, то выясните, какой будет самая маленькая единица этой валюты, и используйте ее. Если вы не можете, используйте что-то вроде BigDecimal .
Мой собственный капитал, вероятно, всегда будет вписываться в 64-битное целое число, просто отлично, но такие вещи, как BigInteger, хорошо работают для проектов большего размера. Они просто медленнее, чем нативные типы.
Выяснить, как хранить это только половина проблемы. Помните, что вы также должны иметь возможность отображать его. Хороший дизайн разделит эти две вещи. Настоящая проблема с использованием поплавков заключается в том, что эти две вещи объединены.
источник
Как я объясню, «библиотеки» не нужны, если в некоторых типах данных отсутствует стандартная библиотека вашего языка.
Проще говоря, вам нужно десятичное число с фиксированной запятой, а не десятичное с плавающей запятой. Например, класс Java BigDecimal можно использовать для хранения суммы в валюте. Другие современные языки имеют подобные встроенные типы, включая C # и Python . Реализации различаются, но обычно они хранят число в виде целого числа с десятичным местоположением в качестве отдельного элемента данных. Это дает точную точность даже при выполнении арифметических операций, которые дают странные остатки (например, 0,0000001) с числами с плавающей запятой IEEE.
Есть несколько важных моментов.
Используйте фактический десятичный тип, а не с плавающей точкой.
Следует понимать, что сумма в валюте имеет два компонента: значение (5,63) и код или тип валюты (USD, CAD, GBP, EUR и др.). Иногда вы можете игнорировать код валюты, в других случаях это жизненно важно. Что делать, если вы работаете в финансовой или розничной системе электронной коммерции, которая допускает несколько валют? Что произойдет, если вы пытаетесь взять деньги у клиента в CAD, но он хочет заплатить с помощью MXN? Вам нужен тип «деньги» с кодом валюты и суммой валюты, чтобы иметь возможность смешивать эти значения (также обменный курс, но я не хочу слишком углубляться в касательную). В то же время моему программному обеспечению для личных финансов никогда не нужно беспокоиться об этом, потому что все в долларах (оно может смешивать валюты, но мне это никогда не нужно).
В то время как валюта может иметь наименьшую физическую единицу на практике (CAD и USD имеют центы, JPY - это просто ... Йена), возможно, станет меньше. Ответ CandiedOrange указывает цены на топливо в десятых долях цента. Мои налоги на недвижимость оцениваются в мельницах за доллар или в десятых долях цента (1/1000 доллара США). Не ограничивайте себя $ 0,01. В то время как вы можете отображать эти значения большую часть времени, ваши типы должны разрешать меньшие (десятичные типы, упомянутые выше, делают).
Промежуточные вычисления, безусловно, должны обеспечивать большую точность, чем один цент. Я работал над системами розничной торговли / электронной коммерции, где внутренние значения были округлены до 0,00000001 долл. США внутри страны. Бесконечная точность обычно не поддерживается десятичными типами (или SQL), поэтому должен быть некоторый предел. Например, деление 1/3 с использованием Java BigDecimal вызовет исключение без указания RoundingMode или MathContext, поскольку значение не может быть точно представлено.
Во всяком случае, это критично в определенных случаях. Предположим, у вас есть пользователь с шестью товарами в его корзине, и он отправляется на проверку. Ваша система должна рассчитывать налог и делает это для каждого товара, потому что товары могут облагаться налогом по-разному. Если вы округляете налоги по каждому товару, вы можете получить ошибки округления пенни на уровне транзакции / корзины. Один из подходов к решению этой проблемы может заключаться в том, чтобы хранить налоги в большем количестве десятичных разрядов на единицу, получать общую сумму за всю транзакцию и возвращаться и округлять каждый элемент, чтобы общий налог был правильным (возможно, один элемент округляется вверх, другой - вниз).
Из всего этого важно понять, что такая важная вещь, как округление копеек, может быть очень важна для нужных людей (например, для некоторых из моих прошлых клиентов, которые должны были платить правительственные налоги с продаж от имени своих клиентов). Однако это все решенные проблемы. Помните вышеупомянутые пункты и сделайте некоторые эксперименты самостоятельно, и вы будете учиться на практике.
источник
Единственное место, где многие разработчики сталкиваются с единым представлением данных для любой валюты, - это покупки в приложениях для приложений iOS. Ваш покупатель может подключиться к магазину практически в любой стране мира. И в этой ситуации вам будет предоставлена цена покупки, состоящая из числа с двойной точностью и кода валюты.
Вы должны знать, что цифры могут быть большими. Есть валюты, где эквивалент, скажем, десять долларов составляет более 100 000 единиц. И нам повезло, что в данный момент нет таких валют, как зимбабвийский доллар, где вы могли бы иметь банкноту в сто триллионов!
Для отображения валют вам понадобится некоторая библиотека - у вас нет шансов все сделать правильно. Отображение зависит от двух вещей: кода валюты и языкового стандарта пользователя. Подумайте, как будут отображаться доллары США и канадские доллары в локали США и Канаде: в США у вас есть $ против CAN $, а в Канаде у вас есть $ против $. Надеюсь, это встроено в ОС, или у вас есть хорошая библиотека.
Для расчетов любой расчет заканчивается шагом округления. Вам нужно будет выяснить, как вы должны выполнить это округление на законных основаниях . Это не проблема программирования, это юридическая проблема. Например, если вы рассчитываете НДС в Великобритании, вам нужно рассчитать налог на единицу или на строку позиции и округлить до пенни. То, к чему вы обращаетесь, зависит от валюты. Но правила явно зависят от страны. Вы не можете ожидать, что вычисления, которые являются юридически правильными в Великобритании, будут юридически правильными в Японии и наоборот.
источник
Некоторые примеры проблем, связанных с локалью:
Другая потенциальная проблема - даже если вы правильно храните валюту в формате с фиксированной запятой, возможно, ее придется преобразовать в число с плавающей запятой, поскольку библиотека, используемая для печати, поддерживает только числа с плавающей запятой.
источник