В простой бизнес-симуляции (встроенной в Java + Slick2D) текущая сумма денег игрока должна храниться как float
или int
, или как-то еще?
В моем случае использования большинство транзакций будет использовать центы ($ 0,50, $ 1,20 и т. Д.), И будут использоваться простые расчеты процентной ставки.
Я видел людей, которые говорили, что вы никогда не должны использовать float
для валюты, а также людей, которые говорили, что вы никогда не должны использовать int
для валюты. Я чувствую, что должен использовать int
и округлять любые необходимые процентные вычисления. Что я должен использовать?
Currency
типа, подобного Delphi, который использует масштабированную математику с фиксированной точкой, чтобы дать вам десятичную математику без проблем точности, свойственных плавающей точке?BigDecimal
проблемы такого рода.Ответы:
Вы можете использовать
int
и считать все в центах. 1,20 доллара - это всего 120 центов. При отображении вы помещаете десятичное число в том месте, где оно принадлежит.Расчет процентов будет либо усеченным, либо округленным. Так
Таким образом, у вас не будет грязных десятичных дробей, которые всегда торчат. Вы можете разбогатеть, добавив неучтенные деньги (из-за округления) на свой банковский счет
источник
round
то нет никакой реальной причины, чтобы броситьint
, не так ли?Хорошо, я прыгну.
Мой совет: это игра. Успокойся и используй
double
.Вот мое обоснование:
float
действительно есть проблема точности, которая появляется при добавлении единиц к миллионам, поэтому, хотя это может быть доброкачественным, я бы избегал этого типа.double
только начинаются проблемы вокруг квинтиллонов (миллиард миллиардов).int
иlong
бесполезно , потому что вы не знаете , где остановиться с десятичные дроби.BigDecimal
даст вам произвольную точность, но станет мучительно медленным, если вы не ограничите точность через некоторое время. Каковы правила округления? Как вы выбираете, где остановиться? Стоит ли иметь больше битов точности, чемdouble
? Я верю нет.Арифметика с фиксированной точкой используется в финансовых приложениях потому, что они являются детерминированными. Правила округления четко определены, иногда законом, и должны строго соблюдаться, но в определенный момент округление все же происходит . Любой аргумент в пользу заданного типа, основанный на точности, скорее всего является поддельным. Все типы имеют проблемы с точностью вычислений, которые вы собираетесь делать.
Практические примеры
Я вижу довольно много комментариев, утверждающих вещи о округлении или точности, с которыми я не согласен. Вот несколько дополнительных примеров, чтобы проиллюстрировать, что я имею в виду.
Хранение : если ваша базовая единица равна центу, вы, вероятно, захотите округлить до ближайшего цента при сохранении значений:
При использовании этого метода у вас не возникнет никаких проблем с округлением, которых у вас не будет с целочисленным типом.
Получение : все вычисления могут быть выполнены непосредственно на двойных значениях без преобразований:
Обратите внимание , что приведенный выше код не работает , если вы заменяете
double
сint64_t
: будет неявное преобразование кdouble
, то усечение кint64_t
, с возможной потерей information.data.GetValue ()Сравнение : сравнение - это единственное, что нужно сделать правильно с типами с плавающей точкой. Я предлагаю использовать метод сравнения, такой как этот:
Округление справедливости
Предположим, у вас есть $ 9,99 на счете с 4%. Сколько игрок должен заработать? С целочисленным округлением вы получаете $ 0,03; при округлении с плавающей точкой вы получаете $ 0,04. Я считаю, что последнее более справедливо.
источник
int_64t
с.10/3 = 3+3+4
или10/3 = 3+3+3 (+1)
. Для всех других операций целые числа работают отлично, в отличие от числа с плавающей запятой, которое может вызвать проблемы округления в любой операции.Типы с плавающей запятой в Java (
float
,double
) не являются хорошим представлением для валют по одной основной причине - существует ошибка машины при округлении. Даже если простой расчет возвращает целое число - как12.0/2
(6,0), с плавающей точкой может неправильно вокруг него (из - Тхо конкретного представления этих типов в памяти) , как6.0000000000001
и5.999999999999998
или подобное. Это результат конкретного округления компьютера, который происходит в процессоре, и он уникален для компьютера, который его вычислил. Как правило, с этими значениями редко возникает проблема, так как ошибка довольно небрежна, но отображать ее пользователю неудобно.Возможным решением этой проблемы будет использование пользовательских реализаций типа данных с плавающей точкой, например
BigDecimal
. Он поддерживает более совершенные механизмы расчета, которые, по крайней мере, изолируют ошибки округления, чтобы они не зависели от конкретной машины, но были медленнее с точки зрения производительности.Если вам нужна высокая производительность, вам лучше придерживаться простых типов. Если вы работаете с важными финансовыми данными , и каждый цент важен (например, приложение Forex или какая-то игра в казино), то я бы порекомендовал вам использовать
Long
илиlong
.Long
позволит вам обрабатывать большие объемы и хорошую точность. Просто предположим, что вам нужно, скажем, 4 цифры после десятичной точки, все, что вам нужно, это умножить сумму на 10000. Имея опыт в разработке игр для онлайн-казино, яLong
часто видел, как представлять деньги в центах , В приложениях Forex точность важнее, поэтому вам понадобится больший множитель - тем не менее, целые числа свободны от проблем с машинным округлением (конечно, ручное округление, как в 3/2, вы должны обрабатывать самостоятельно).Приемлемым вариантом будет использование стандартных типов с плавающей запятой -
Float
иDouble
, если производительность важнее точности с точностью до сотых долей цента. Затем в логике отображения все, что вам нужно, это использовать предопределенное форматирование , чтобы уродство потенциального машинного округления не доходило до пользователя.источник
Для мелкомасштабных игр и там, где важна скорость процесса, память (из-за точности или из-за того, что работа с математическим сопроцессором может быть мучительно медленной), двойного достаточно.
Но для крупномасштабных игр (например, социальных игр) и там, где скорость процесса не ограничена, память лучше BigDecimal . Потому что здесь
Ресурсы:
С https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency
От Блоха, Дж. Эффективная Ява, 2-е изд, пункт 48:
Также посмотрите на
источник
Вы хотите сохранить свою валюту в
long
и рассчитать валюту вdouble
, по крайней мере , в качестве резервного. Вы хотите, чтобы все транзакции происходили какlong
.Причина, по которой вы хотите хранить свою валюту,
long
заключается в том, что вы не хотите терять валюту.Предположим, вы используете
double
, и у вас нет денег. Кто-то дает вам три цента, а затем забирает их.Ну, это не так круто. Может быть, кто-то с 10 долларами хочет отдать свое состояние, сначала дав вам три цента, а затем 9,70 доллара кому-то еще.
И тогда вы отдаете им десять центов назад
Это просто сломано.
Теперь давайте используем long и будем отслеживать десятые доли центов (поэтому 1 = $ 0,001). Давайте дадим каждому на планете один миллиард сто двенадцать миллионов семьдесят пять тысяч сто сорок три доллара:
Подожди, мы можем дать каждому более миллиарда долларов и потратить чуть больше двух? Переполнение - это катастрофа.
Таким образом, всякий раз , когда вы рассчитываете сумму денег для передачи, использования
double
иMath.round
его получитьlong
. Затем исправьте остатки (сложите и вычтите обе учетные записи), используяlong
.Ваша экономика не протечет, и она увеличится до четырех миллиардов долларов.
Есть более сложные вопросы - например, что вы делаете, если делаете двадцать платежей? * - но это должно помочь вам начать.
* Вы рассчитываете, что один платеж, округлить до
long
; затем умножьте на20.0
и убедитесь, что он в пределах досягаемости; Если это так, вы умножаете платеж20L
на сумму, вычтенную из вашего баланса. В общем, все транзакции должны обрабатываться какlong
, так что вам действительно нужно суммировать все отдельные транзакции; Вы можете умножить как ярлык, но вам нужно убедиться, что вы не добавляете ошибки округления и не переполняетесь, что означает, что вам нужно проверитьdouble
перед выполнением реального вычисления сlong
.источник
Я бы сказал, что любое значение, которое может отображаться пользователю, почти всегда должно быть целым числом. Деньги - только самый яркий пример этого. Нанесение 225 урона четырежды монстру с 900 HP и обнаружение, что у него все еще осталось 1 HP, вычтет из опыта столько же, сколько и обнаружение, что вы - невидимая часть копейки, если вам чего-то не хватает.
С технической стороны, я думаю, стоит отметить, что не нужно возвращаться к плавающим предметам, чтобы делать сложные вещи, такие как интерес. Если у вас есть достаточный запас в выбранном вами целочисленном типе, умножение и деление заменят десятичное число, например, чтобы добавить 4%, округленное в меньшую сторону:
Чтобы добавить 4%, округленный по стандартным соглашениям:
Здесь нет погрешности с плавающей точкой, округление всегда делится точно на
.5
отметку.Изменить, реальный вопрос:
Видя , как дебаты ушел , я начинаю думать , что с изложением того, что речь идет все о может быть более полезным , чем просто
int
/float
ответ. Суть вопроса не в типах данных, а в том, чтобы взять под контроль детали программы.Использование целого числа для представления нецелого значения заставляет программиста иметь дело с деталями реализации. "Какую точность использовать?" и "Какой способ округлить?" вопросы, на которые необходимо дать четкий ответ
С другой стороны, float не заставляет программиста беспокоиться, он уже делает в значительной степени то, что и следовало ожидать. Но поскольку число с плавающей запятой не бесконечной точности, произойдет некоторое округление, и это округление довольно непредсказуемо.
Что происходит в одноразовых поплавках и хотите взять контроль над округлением? Оказывается, это почти невозможно. Единственный способ сделать плавающее число действительно предсказуемым - это использовать только те значения, которые могут быть представлены в целых
2^n
s. Но эта конструкция делает поплавки довольно сложными в использовании.Таким образом, ответ на простой вопрос таков: если вы хотите взять управление на себя, используйте целые числа, если нет - используйте числа с плавающей точкой.
Но обсуждаемый вопрос - это еще одна форма вопроса: хотите ли вы взять под контроль?
источник
Даже если бы это была «только игра», я бы использовал Money Pattern от Мартина Фаулера, подкрепленную долго.
Почему?
Локализация (L10n): Используя этот шаблон, вы можете легко локализовать свою игровую валюту. Подумайте о старых играх магнатов, таких как «Транспортный магнат». Они легко позволяют игроку менять внутриигровую валюту (например, с британского фунта на доллар США), чтобы она соответствовала реальной мировой валюте.
А также
Это означает, что вы можете хранить 9000-кратную текущую денежную массу США в M2 (~ 10000 миллиардов долларов). Предоставление вам достаточно места, чтобы использовать любую другую мировую валюту, возможно, даже те, кто имел / имеет гиперинфляцию (если любопытно, посмотрите инфляцию в Германии после Первой мировой войны , где 1 фунт хлеба составлял 3 000 000 000 марок)
Лонг легко сохранить, он очень быстрый и должен дать вам достаточно места для выполнения всех расчетов процентов с использованием только целочисленной арифметики, а ответ на электронный бизнес дает объяснение, как это сделать.
источник
Сколько работы вы хотите вложить в это? Насколько важна точность? Вас интересует отслеживание дробных ошибок, возникающих при округлении, и неточности представления десятичных чисел в двоичной системе?
В конечном счете, я склонен тратить немного больше времени на кодирование и реализацию модульных тестов для «угловых случаев» и известных проблемных случаев - поэтому я буду думать в терминах модифицированного BigInteger, который включает в себя произвольно большие суммы и поддерживает дробные биты с помощью BigDecimal или часть BigRational (два BigInteger, один для знаменателя, а другой для числителя) - и включает код для сохранения дробной части фактической дроби путем добавления (возможно, только периодически) любой нефракционной части к основному BigInteger. Тогда я внутренне следил бы за всем в центах, чтобы не допустить дробных частей в вычисления GUI.
Вероятно, это сложный способ для (простой) игры - но хорошо для библиотеки, опубликованной с открытым исходным кодом! Просто нужно было бы найти способы сохранить производительность при работе с дробными битами ...
источник
Конечно, не плавать. Только с 7 цифрами у вас есть только точность, как:
12 345,67 123 456,7x
Итак, вы видите, уже с 10 ^ 5 долларов, вы теряете счет пенни. double может использоваться в игровых целях, а не в реальной жизни из-за проблем с точностью.
Но long (и под этим я имею в виду 64-битный или long long в C ++) недостаточно, если вы собираетесь отслеживать транзакции и суммировать их. Этого достаточно для поддержания чистых активов любой компании, но все транзакции в течение года могут быть переполнены. Это зависит от того, насколько велик ваш финансовый «мир» в игре.
источник
Еще одно решение, которое я добавлю здесь, это сделать класс денег. Класс будет что-то вроде (может быть даже просто структура).
Это позволит вам представлять центы в этом случае как целое число, а все доллары - как целое число. Поскольку у вас есть отдельный флаг отрицания, вы можете использовать целые числа без знака для своих значений, что удваивает возможную сумму (вы также можете написать класс чисел, который применяет эту идею к числам, чтобы получить ДЕЙСТВИТЕЛЬНО большие числа). Все, что вам нужно сделать, это перегрузить математические операторы, и вы можете использовать это как любой старый тип данных. Это занимает больше памяти, но действительно расширяет пределы значений.
Это может быть дополнительно расширено, чтобы включить такие вещи, как количество частичных единиц на целые единицы, чтобы вы могли иметь валюты, которые подразделяются на что-то отличное от 100 дополнительных единиц, в данном случае центов на доллар. Например, вы можете иметь 125 флоппи, составляющих один флопер.
Редактировать:
Я расширю идею, что у вас также может быть своего рода справочная таблица, к которой может получить доступ денежный класс, который обеспечит обменный курс для его валюты по отношению ко всем другим валютам. Вы можете встроить это в функции перегрузки вашего оператора. Поэтому, если вы автоматически попытаетесь добавить 4 доллара США к 4 британским фунтам, это автоматически даст вам 6,6 фунтов (на момент написания).
источник
Как упоминалось в одном из комментариев к исходному вопросу, эта проблема была рассмотрена в StackExchange: https://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for- деньги-в-Java-приложение
По сути, типы чисел с плавающей точкой никогда не должны использоваться для представления валют, и, как и в большинстве языков, Java имеет более подходящий тип. Собственная документация Oracle по примитивам предлагает использовать BigDecimal .
источник
Используйте класс, это лучший способ. Вы можете скрыть реализацию своих денег, вы можете переключить двойную / длинную, что вы хотите в любое время.
пример
В вашем коде используйте просто getter, я думаю, это лучший способ, и вы можете хранить больше полезных методов.
источник