Какой тип данных следует использовать для внутриигровой валюты?

В простой бизнес-симуляции (встроенной в Java + Slick2D), если текущая сумма денег игрока будет храниться как float или int, или что-то еще

В моем случае использования большинство транзакций будут использовать центы ($ 0.50, $ 1.20 и т. д.), и будут задействованы простые расчеты процентной ставки.

Я видел, как люди говорили, что вы не должны использовать float для валюты, а также людей, говорящих, что вы никогда не должны использовать int для валюты. Я чувствую, что должен использовать int и округлять любые необходимые процентные вычисления. Что я должен использовать?

96 голосов | спросил Lucas Tulio 21 FriEurope/Moscow2012-12-21T17:42:41+04:00Europe/Moscow12bEurope/MoscowFri, 21 Dec 2012 17:42:41 +0400 2012, 17:42:41

12 ответов


91

Вы можете использовать int и рассматривать все в центах. $ 1,20 составляет всего 120 центов. На дисплее вы помещаете десятичное число в том месте, где оно принадлежит.

Процентные расчеты будут либо усечены, либо округлены. Так

newAmt = round( 120 cents * 1.04 ) = round( 124.8 ) = 125 cents

Таким образом, у вас нет грязных десятичных знаков, всегда торчащих вокруг. Вы можете разбогатеть, добавив неучтенные деньги (из-за округления) на свой собственный банковский счет

ответил bobobobo 21 FriEurope/Moscow2012-12-21T19:40:50+04:00Europe/Moscow12bEurope/MoscowFri, 21 Dec 2012 19:40:50 +0400 2012, 19:40:50
65

Хорошо, я вскочу.

Мой совет: это игра. Успокойтесь и используйте double.

Вот мое обоснование:

  • float имеет проблему с высокой точностью, которая появляется при добавлении единиц в миллионы, поэтому, хотя это может быть доброкачественным, я бы избегал этого типа. double только начинает получать проблемы вокруг квинтильонов (миллиард миллиардов).
  • Поскольку у вас будут процентные ставки, в любом случае вам понадобится теоретическая бесконечная точность: с процентными ставками 4%, 100 долларов США станут $ 104, затем $ 108,16, затем $ 112,4864 и т. д. Это делает int и long бесполезно, потому что вы не знаете, где остановиться с десятичными знаками.
  • BigDecimal даст вам произвольную точность, но станет болезненно медленным, если вы не закроете точность в какое-то время. Каковы правила округления? Как вы выбираете, где остановиться? Стоит ли иметь более точные биты, чем double? Я верю, что нет.

Причина арифметики с фиксированной точкой используется в финансовых приложениях, потому что они детерминированы. Правила округления отлично определены, иногда по закону, и должны строго применяться, , но округление все еще происходит в какой-то момент. Любой аргумент в пользу данного типа, основанный на точности, скорее всего, фиктивный. Все типы имеют проблемы с точностью, с которыми вы собираетесь делать.

Практические примеры

Я вижу довольно много комментариев, претендующих на то, что касается округления или точности, с которыми я не согласен. Вот несколько дополнительных примеров, иллюстрирующих, что я имею в виду.

Хранение : если ваш базовый блок является центом, вы, вероятно, захотите округлить до ближайшего значения при сохранении значений:

void SetValue(double v) { m_value = round(v * 100.0) / 100.0; }

При принятии этого метода вы не получите абсолютно никаких проблем округления, которые у вас также не были бы с целым типом.

Извлечение : все вычисления могут выполняться непосредственно по двойным значениям без конверсий:

double value = data.GetValue();
value = value / 3.0 * 12.0;
[...]
data.SetValue(value);

Обратите внимание, что приведенный выше код не работает, если вы замените double на int64_t: произойдет неявное преобразование в double, затем усечение до int64_t с возможной потерей информации.data.GetValue ()

Сравнение : сравнение - это единственное, что можно получить с помощью типов с плавающей запятой. Я предлагаю использовать метод сравнения, такой как этот:

/* Are values equal to a tenth of a cent? */
bool AreCurrencyValuesEqual(double a, double b) { return abs(a - b) < 0.001; }

Плотность округления

Предположим, что у вас есть счет в размере 9,99 доллара США на счете с долей в 4%. Сколько должно зарабатывать игрок? При округлении округлых чисел вы получаете $ 0,03; с округлением с плавающей точкой вы получаете $ 0,04. Я считаю, что последнее более справедливо.

ответил sam hocevar 21 FriEurope/Moscow2012-12-21T19:01:12+04:00Europe/Moscow12bEurope/MoscowFri, 21 Dec 2012 19:01:12 +0400 2012, 19:01:12
21

Типы с плавающей точкой в ​​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, если производительность важнее точности до сотых долей процента. Затем в вашей логике отображения все, что вам нужно, это использовать предопределенное форматирование , так что уродство потенциального округления машины не доходит до пользователя.

ответил Ivaylo Slavov 21 FriEurope/Moscow2012-12-21T17:56:11+04:00Europe/Moscow12bEurope/MoscowFri, 21 Dec 2012 17:56:11 +0400 2012, 17:56:11
17

Для мелкомасштабной игры и где скорость процесса важна проблема памяти (из-за точности или работы с математическим сопроцессором может сделать больно медленным), там double достаточно.

Но для крупномасштабных игр (например, для социальных игр) и где скорость процесса не ограничена памятью, там лучше BigDecimal . Потому что здесь

  • int или долго для денежных расчетов.
  • Поплавки и двойники не могут точно представлять большинство базовых 10 реальных числа.

Ресурсы

От https://stackoverflow.com/questions/3730019 /почему-не-использование двойное или обращение к представлять валюту

  

Поскольку поплавки и удвоения не могут точно представлять большинство базовых 10   действительные числа.

     

Так работает число с плавающей запятой IEEE-754: оно посвящает   бит для знака, несколько бит для хранения экспоненты, а остальные для   фактическая доля. Это приводит к тому, что числа представлены в форме   аналогично 1.45 * 10 ^ 4; за исключением того, что вместо базы 10, это   два.

     

Все действительные десятичные числа можно видеть, фактически, как точные доли   сила десяти. Например, 10.45 действительно 1045/10 ^ 2. И так же, как   некоторые фракции не могут быть представлены точно как доля мощности   из десяти (1/3 приходит в голову), некоторые из них не могут быть представлены   точно так же, как и доля мощности двух. В качестве простого примера,   вы просто не можете хранить 0,1 внутри переменной с плавающей запятой. Вы будете   получить ближайшее представимое значение, что-то вроде   0.0999999999999999996, и при его отображении программное обеспечение будет округлено до 0,1.

     

Однако, поскольку вы выполняете больше дополнений, вычитаний, умножений   и деления на неточные числа, вы потеряете все больше и больше точности   как крошечные ошибки складываются. Это делает поплавки и удваивает неадекватность   для работы с деньгами, где требуется совершенная точность.

От Блоха, Дж., Эффективная Java, 2-е изд., ст. 48:

The float and double types are particularly ill-suited for 
     

денежные расчеты, потому что невозможно представить 0,1 (или   любая другая отрицательная мощность в десять) как плавающий или двойной точно.

For example, suppose you have $1.03 and you spend 42c. How much money do you have left?

System.out.println(1.03 - .42);

prints out 0.6100000000000001.

The right way to solve this problem is to use BigDecimal, 
     

int или долго для денежных расчетов.

Также посмотрите

ответил Md Mahbubur Rahman 21 FriEurope/Moscow2012-12-21T18:13:16+04:00Europe/Moscow12bEurope/MoscowFri, 21 Dec 2012 18:13:16 +0400 2012, 18:13:16
13

Вы хотите сохранить свою валюту в long и рассчитать свою валюту в double, по крайней мере, как резервное копирование. Вы хотите, чтобы все транзакции выполнялись как long.

Причина, по которой вы хотите сохранить свою валюту в long, заключается в том, что вы не хотите терять валюту.

Предположим, вы используете double, и у вас нет денег. Кто-то дает вам три десятилетия, а затем возвращает их.

You:       0.1+0.1+0.1-0.1-0.1-0.1 = 2.7755575615628914E-17

Ну, это не так здорово. Может быть, кто-то с 10 долларов хочет отдать свое состояние, сначала давая вам три десятилетия, а затем давая $ 9,70 кому-то другому.

Them: 10.0-0.1-0.1-0.1-9.7 = 1.7763568394002505E-15

И затем вы дадите им десять центов назад:

Them: ...+0.1+0.1+0.1 = 0.3000000000000018

Это просто сломано.

Теперь давайте использовать длинный, и мы будем отслеживать десятки центов (так 1 = $ 0.001). Давайте дадим всем на планете один миллиард, сто двенадцать миллионов, семьдесят пять тысяч, сто сорок три доллара:

Us: 7000000000L*1112075143000L = 1 894 569 218 048

Эм, подождите, мы можем дать всем более миллиарда долларов, и потратьте чуть больше двух? Переполнение - это катастрофа здесь.

Итак, всякий раз, когда вы вычисляете сумму денег для перевода, используйте double и Math.round, чтобы получить long. Затем исправьте балансы (добавьте и вычтите обе учетные записи), используя long.

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

Есть более сложные проблемы - например, что вы делаете, если вы делаете двадцать платежей? * - но это должно заставить вас начать.

* Вы подсчитываете, какой платеж, округлый до long; затем умножьте на 20.0 и проверьте, что он находится в диапазоне; если это так, вы умножаете платеж на 20L, чтобы получить сумму, вычитаемую из вашего баланса. В общем, все транзакции должны обрабатываться как long, поэтому вам действительно нужно суммировать все отдельные транзакции; вы можете размножаться как ярлык, но вам нужно убедиться, что вы не добавляете ошибки округления и , которые вы не переполняете, что означает, что вам нужно проверить с помощью double перед выполнением реального вычисления с помощью long.

ответил Rex Kerr 22 SatEurope/Moscow2012-12-22T04:44:58+04:00Europe/Moscow12bEurope/MoscowSat, 22 Dec 2012 04:44:58 +0400 2012, 04:44:58
8

Я бы сказал, что любое значение, которое может отображаться пользователю, должно всегда быть целым числом. Деньги - только самый яркий пример этого. Нанеся 225 урона четыре раза монстру с 900 HP и обнаружив, что он все еще имеет 1 HP, вычитает из опыта столько же, сколько и обнаруживает, что вы являетесь невидимой частью копейки, которая не дает что-то.

На более технической стороне я думаю, что стоит отметить, что не нужно возвращаться к поплавкам, чтобы делать продвинутые вещи, такие как интерес. До тех пор, пока у вас есть достаточный запас высоты в выбранном вами целочисленном типе, умножение и деление будут стоять за умножение на десятичное число, например, чтобы добавить 4%, округлить вниз:

number=(number*104)/100

Чтобы добавить 4%, округленные стандартными соглашениями:

number=(number*104+50)/100

Здесь не указана погрешность точки с плавающей запятой, округление всегда равномерно разбивается на метку .5.

Изменить, реальный вопрос:

Увидев, как прошло обсуждение, я начинаю думать, что изложение того, что касается всего вопроса, может быть более полезным, чем простой ответ int /float. В основе вопроса - не о типах данных, а о контроле над деталями программы.

Использование целого числа для представления нецелого значения заставляет программиста иметь дело с деталями реализации. «Какую точность использовать?» и «Каким образом раунд?». это вопросы, на которые нужно ответить явно.

Поплавок с другой стороны не заставляет программиста волноваться, он уже делает многое, чего можно было бы ожидать. Но поскольку float не является бесконечной точностью, произойдет некоторое округление, и округление довольно непредсказуемо.

Что происходит в одном использовании float и хотите взять под контроль округление? Это оказывается практически невозможным. Единственный способ сделать поплавок по-настоящему предсказуемым - использовать только значения, которые могут быть представлены в целом 2^n s. Но эта конструкция делает поплавки довольно сложными в использовании.

Таким образом, ответ на простой вопрос: если вы хотите взять управление, используйте целые числа, если нет, используйте float.

Но вопрос, который обсуждается, - это еще одна форма вопроса: хотите ли вы взять под свой контроль?

ответил aaaaaaaaaaaa 22 SatEurope/Moscow2012-12-22T17:49:18+04:00Europe/Moscow12bEurope/MoscowSat, 22 Dec 2012 17:49:18 +0400 2012, 17:49:18
5

Даже если это «только игра», я бы использовал Money Pattern от Мартина Фаулера , подкрепленный длинным.

Почему?

Локализация (L10n): Используя этот шаблон, вы можете легко локализовать валюту своей игры. Подумайте о старых играх-магнатах, таких как «Transport Tycoon». Они легко позволяют игроку менять валюту в игре (т. Е. От британского фунта стерлингов к доллару США) для удовлетворения реальной мировой валюты.

и

  

Длинный тип данных - это целое число дополнений, состоящее из 64-разрядных подписей. В нем есть   минимальное значение -9,223,372,036,854,775,808 и максимальное значение   9,223,372,036,854,775,807 (включительно) ( учебные пособия по Java )

Это означает, что вы можете хранить 9000 раз текущую денежную поставку M2 США (~ 10 000 Миллиард долларов). Предоставляя вам достаточно места для использования любой другой мировой валюты, возможно, даже тех, у кого была /была гиперинфляция (если интересно, см. послевоенная инфляция в Германии , где 1 фунт хлеба составлял 3 000 000 000 марок)

Длинный легко переносится, очень быстро и должен предоставить вам достаточно места для выполнения всех вычислений процентов, используя только целую арифметику, eBusiness дает объяснение, как это сделать.

ответил grprado 28 FriEurope/Moscow2012-12-28T00:09:52+04:00Europe/Moscow12bEurope/MoscowFri, 28 Dec 2012 00:09:52 +0400 2012, 00:09:52
2

Сколько работы вы хотите внести в это? Насколько важна точность? Вы заботитесь о отслеживании дробных ошибок, возникающих при округлении, и о неточности представления десятичных чисел в двоичной системе?

В конечном счете я стремлюсь потратить немного больше времени на кодирование и реализацию модульных тестов для «краевых дел» и известных проблемных случаев, поэтому я бы подумал о модифицированном BigInteger, который включает в себя сколь угодно большие суммы и поддерживает дробные биты с помощью BigDecimal или BigRational (два BigIntegers, один для знаменателя и другой для числителя), - и включают код, чтобы дробная часть содержала фактическую долю (возможно, только периодически), добавляя любую недробную часть к основному BigInteger. Затем я буду внутренне отслеживать все в центах только для того, чтобы исключить частичные части из графических графиков.

Вероятно, путь к сложной для (простой) игре - но полезный для библиотеки, опубликованной как с открытым исходным кодом! Просто нужно будет выяснить, как поддерживать производительность при работе с дробными битами ...

ответил Richard Arnold Mead 22 SatEurope/Moscow2012-12-22T03:38:37+04:00Europe/Moscow12bEurope/MoscowSat, 22 Dec 2012 03:38:37 +0400 2012, 03:38:37
1

Конечно, не плавать. Имея всего 7 цифр, у вас есть только такая точность:

12,345.67 123,456.7x

Итак, вы видите, уже с 10 ^ 5 долларов, вы теряете след копейки. double применим для игровых целей, а не в реальной жизни из-за проблем с точностью.

Но длинный (и под этим я имею в виду 64-битный или длинный длинный на C ++) недостаточно, если вы собираетесь отслеживать транзакции и суммировать их. Достаточно поддерживать чистые активы любой компании, но все транзакции за год могут переполняться. Это зависит от того, насколько велика ваша финансовая «мир» в игре.

ответил Dov 23 SunEurope/Moscow2012-12-23T09:39:15+04:00Europe/Moscow12bEurope/MoscowSun, 23 Dec 2012 09:39:15 +0400 2012, 09:39:15
0

Еще одно решение, которое я добавлю, это сделать класс денег. Класс будет чем-то вроде (может даже быть просто структурой).

class Money
{
     string currencyType; //the type of currency example USD could be an enum as well
     bool isNegativeValue;
     unsigned long int wholeUnits;
     unsigned short int partialUnits;
}

Это позволит вам представить центы в этом случае как целое число, а целые доллары - как целое число. Поскольку у вас есть отдельный флаг для того, чтобы быть отрицательным, вы можете использовать целые числа без знака для своих значений, которые удваивают возможную сумму (вы также можете написать класс чисел, который применяет эту идею к числам, чтобы получить ДЕЙСТВИТЕЛЬНО большие числа). Все, что вам нужно сделать, это перегружать математические операторы, и вы можете использовать его, как любой старый тип данных. Он занимает больше памяти, но он действительно расширяет пределы значений.

Это может быть дополнительно расширено, чтобы включить такие вещи, как количество частичных единиц на единицу, чтобы вы могли иметь валюты, которые подразделяются на что-то, кроме 100 под единиц, в этом случае - за доллар. Например, вы могли бы иметь 125 floopies, например, один flooper.

Edit:

Я разберу идею в том, что у вас также может быть таблица со просмотром, которую может получить доступ к денежному классу, который обеспечит обменный курс для своей валюты по отношению ко всем другим валютам. Вы можете создать это в своих функциях перегрузки оператора. Поэтому, если вы автоматически попытаетесь добавить 4 доллара США к 4 британским фунтам, это автоматически даст вам 6,6 фунтов (с момента написания).

ответил Azaral 8 PMpMon, 08 Apr 2013 16:37:24 +040037Monday 2013, 16:37:24
0

Как упоминалось в одном из комментариев по вашему первоначальному вопросу, этот вопрос был рассмотрен на StackExchange: https://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for-money-in -java-приложение

В принципе, типы с плавающей точкой никогда не должны использоваться для представления валют, и, как и большинство языков, язык Java имеет более подходящий тип. Собственная документация Oracle по примитивам предлагает использовать BigDecimal .

ответил FlintZA 2 Mayam14 2014, 11:27:51
-1

Использовать класс , это лучший способ. Вы можете скрыть реализацию своих денег, вы можете переключать двойные /длинные, что захотите, в любое время.

Пример

class Money
{
    private long count;//Store here what ever you want in what type you want i suggest long or int
    //methods constructors etc.
    public String print()
    {
        return count/100.F; //convert this to string
    }
}

В вашем коде используйте просто getter, это лучше, я думаю, и вы можете хранить более полезные методы.

ответил Dawid Drozd 26 WedEurope/Moscow2012-12-26T00:46:19+04:00Europe/Moscow12bEurope/MoscowWed, 26 Dec 2012 00:46:19 +0400 2012, 00:46:19

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132