ゲーム内通貨に使用するデータ型は何ですか?


96

単純なビジネスシミュレーションゲーム(Java + Slick2Dで構築)で、プレーヤーの現在の金額を、floatまたはint、または他の何かとして保存する必要がありますか?

私のユースケースでは、ほとんどのトランザクションはセント(0.50ドル、1.20ドルなど)を使用し、単純な金利計算が含まれます。

私はあなたがfloat通貨のために決して使うべきでないと言う人々を見て、同様にあなたが通貨のために決して使うべきでないと言う人々を見ましたintint必要なパーセンテージ計算を使用して丸める必要があるように感じます。何を使うべきですか?


2
この質問はStackOverflowで議論されました。BigDecimalがおそらく進むべき道のようです。stackoverflow.com/questions/285680/...
アデミラー

2
JavaにはCurrencyDelphiのような型がありません。Delphiはスケーリングされた固定小数点演算を使用して、浮動小数点に固有の精度の問題なしに小数演算を行いますか?
メイソンウィーラー

1
それはゲームです。会計は端数を切り捨てるわけではないので、通貨に浮動小数点を使用する通常の理由は重要ではありません。
ローレンペクテル

@MasonWheeler:JavaにBigDecimalはこの種の問題があります。
マーティンシュレーダー

回答:


92

を使用してint、すべてをセント単位で検討できます。1.20ドルはわずか120セントです。表示では、小数点に小数点を入れます。

金利計算は切り捨てられるか切り上げられるだけです。そう

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

この方法では、散らかった小数を常に持ち続けることはありません。あなたは自分の銀行口座に未払いのお金を(切り捨てによる)追加することで金持ちになることができます


3
を使用する場合、roundにキャストする本当の理由intはありませんか?
サムホセバー

19
+1。浮動小数点数は等値比較を台無しにし、適切にフォーマットするのが難しくなり、時間の経過とともに面白い丸め誤差が発生します。後でそれについて苦情を受け取らないように、それらすべてを自分で行う方が良いです。それは本当に多くの仕事ではありません。
ドーベスヴァンダーメール

+1。私のゲームに行く良い方法のように思えます。私は自分で整数を丸めることに対処し、すべてのフロートの問題を避けます。
ルーカストゥリオ

ただの免責事項:私はこれを正しい答えに変更しました。それが最終的に私が使用したものになったからです。それははるかに単純であり、そのような単純なゲームのコンテキストでは、会計処理されていない小数は実際には重要ではありません。
ルーカスチュリオ

3
ただし、100ではなく10,000の係数で、セント(100ベーシスポイント= 1セント)ではなくベーシスポイントを使用します。これは、すべての財務、銀行、または会計アプリケーションに対してGAAPで必要です。
ピーターギアケンズ

66

さて、飛び込みます。

私のアドバイス:それはゲームです。簡単に使用してくださいdouble

私の理論的根拠は次のとおりです。

  • float単位を数百万に追加すると精度の問題が発生するため、良性かもしれませんが、このタイプは避けます。doubleキンティリオン(10億億)の周辺で問題が発生し始めただけです。
  • あなたは金利を持ってしようとしているので、とにかく理論的な無限の精度が必要になります:4%の金利で、$ 100は、その後$ 104 $ 108.16、そして$ 112.4864、これは作るなどになりますintと、longあなたはどこで停止するかわからないので、無用の小数。
  • BigDecimal任意の精度が得られますが、いつか精度を固定しない限り、非常に遅くなります。丸め規則とは何ですか?停止する場所をどのように選択しますか?それよりも正確なビットを持つ価値はありdoubleますか?私は信じない。

金融アプリケーションで固定小数点演算が使用される理由は、それらが決定論的であるためです。丸め規則は、場合によっては法律によって完全に定義され、厳密に適用する必要がありますが、丸めはまだある時点で行われます。精度に基づいて特定の型を支持する引数は、おそらく偽物です。すべてのタイプには、実行する計算の種類に精度の問題があります。

実用例

丸めや精度について私が同意しないと主張するコメントがかなりあります。ここに私が意味することを説明するためのいくつかの追加例を示します。

格納:ベースユニットがセントの場合、値を格納するときに最も近いセントに丸める必要がある場合があります。

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

この方法を採用する場合、整数型では得られない丸めの問題はまったくありません。

取得:すべての計算は、変換なしでdouble値で直接実行できます。

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

あなたが交換した場合、上記のコードは動作しないことに注意doubleしてint64_t:への暗黙的な変換があるだろうdoubleし、切り捨てint64_tinformation.data.GetValueの損失の可能性と、()

比較する:比較は、浮動小数点型を正しく理解するための1つのことです。このような比較方法を使用することをお勧めします。

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

丸めの公平性

アカウントに4%の利子がある9.99ドルがあるとします。プレーヤーはいくら稼ぐ必要がありますか?浮動小数点の丸めでは、0.04ドルになります。後者の方が公平だと思います。


4
+1。私が追加したい唯一の明確化は、金融アプリケーションが特定の事前定義された要件に準拠するように、丸めアルゴリズムも同様です。ゲームがカジノのゲームである場合、同様の基準に準拠しているため、ダブルは選択できません。
イヴァイロスラヴォフ

2
UIをフォーマットするには、まだ丸め規則について考える必要があります。バックエンドがUIと同じ精度を使用していないことに気付いたときに叫び声を上げたり下げたりする人々に対処したくない場合、ゲームをスプレッドシートとして扱うことを試みるプレイヤーの一部が常に存在するためです「間違った」結果が表示されるため、それらを静かに保つための最良のオプションは、同じ内部および外部表現を使用することです。つまり、固定小数点形式を使用することです。パフォーマンスが問題にならない限り、これを行うにはBigDecimalが最適です。
ダンニーリー

10
私はこの答えに同意しません。丸めの問題が発生し、それらが低下した場合、追加の丸めを使用していない場合、1.00ドルが突然0.99ドルになることがあります。しかし、最も重要なことは、任意精度の小数が遅いことは(ほとんど)完全に無関係です。私たちはほぼ2013年にいます。数千桁の数百万を超える大規模な因数分解、トリガ処理、または対数を行っていない限り、パフォーマンスの低下に気付くことさえありません。とにかく、シンプルが常に最善であるため、私の推奨事項はすべての数字をセントとして保存することです。そうすれば、彼らはすべてですint_64t
パンダパジャマ

8
@Samが最後に銀行口座を確認したとき、残高は.0000000000000001で終わりませんでした。実際の通貨システムは、割り切れない単位で動作する必要があり、整数で正しく表されます。通貨分割を正しく行う必要がある場合(実際の金融業界では実際にはそれほど一般的ではありません)、お金が失われないようにそれらを行う必要があります。たとえば10/3 = 3+3+4または10/3 = 3+3+3 (+1)。他のすべての操作では、整数はすべての操作で丸めの問題を引き起こす可能性がある浮動小数点とは異なり、完全に機能します。
パンダパジャマ

2
@SamHocevar 999 * 4/100。規制で規定されていない場合、すべての丸めは銀行に有利に行われることを想定しています。
ダンニーリー

21

Javaの浮動小数点型(floatdouble)は、主な理由の1つである通貨の適切な表現ではありません- 丸めにはマシンエラーがあります。単純な計算は整数を返した場合でも-等12.0/2(6.0)、浮動小数点かもしれない誤っ(によるメモリにおけるこれらのタイプの特定の表現THO)それとして円形6.0000000000001または5.999999999999998または同様。これは、プロセッサで発生する特定のマシンの丸めの結果であり、それを計算したコンピューターに固有のものです。通常、これらの値を操作することはめったに問題ではありません。エラーは非常に軽微なためですが、ユーザーに表示するのは苦痛です。

これに対する可能な解決策は、のような浮動小数点データ型のカスタム実装を使用することBigDecimalです。これは、マシン固有ではない丸め誤差を少なくとも分離するより優れた計算メカニズムをサポートしますが、パフォーマンスの点では低速です。

高い生産性が必要な場合は、単純なタイプに固執することをお勧めします。場合にはあなたが作動する重要な財務データ、および各セントが重要である(外国為替アプリケーション、またはいくつかのカジノのゲームのような)その後、私が使用することをお勧めいたしますでしょうLonglongLong大量かつ正確に処理できるようになります。たとえば、小数点以下4桁が必要だと仮定します。必要なのは、金額に10000を掛けるだけです。オンラインカジノゲームの開発の経験がありLong、お金をセントで表すのによく使用されることがあります。外国為替アプリケーションでは、精度がより重要であるため、より大きな乗数が必要になります-それでも、整数にはマシンの丸めの問題がありません(もちろん、3/2のような手動での丸めは自分で処理する必要があります)。

-許容可能なオプションは、標準の浮動小数点型を使用することであろうFloatと、Doubleパフォーマンスはセントの百分のに正確さよりも重要であるならば、。次に、表示ロジックで必要なのは、事前に定義された書式設定を使用することだけです。これにより、潜在的なマシン丸めのugさがユーザーに伝わらないようにできます。


4
+1詳細:「たとえば、小数点以下4桁が必要だと仮定します。必要なのは、金額に1000を掛けるだけです。」->これは固定小数点と呼ばれます。
ローランCouvidou

1
@Sam、数字は例のステークに提供されました。それでも、私は個人的にそのような単純な数値で奇妙な結果を見てきましたが、それは頻繁なシナリオではありません。浮動小数点が再生されるようになると、これはおそらく発生する可能性が高くなりますが、見つける可能性は低くなります
イヴァイロスラヴォフ

2
@Ivaylo Slavov、私はあなたに同意します。私の答えでは、私はちょうど私の意見を述べた。私は議論するのが大好きで、正しいことをする意欲があります。また、ご意見ありがとうございます。:)
Md Mahbubur Ra​​hman

1
@SamHocevar:その推論はすべての場合に機能するとは限りません。参照:ideone.com/nI2ZOK
サマールサ

1
@SamHocevar:それも保証されていません。:まだ全体の数である第1分割上のエラー蓄積を開始範囲内の数字ideone.com/AKbR7i
Samaursa

17

以下のために小規模なゲーム、どこで処理速度、メモリは重要な問題(数学コプロセッサを搭載した精度や仕事のために痛々しいほど遅い作ることができる)で、そこに倍増十分です。

しかし、大規模なゲーム(ソーシャルゲームなど)で、プロセス速度とメモリが制限されていない場合、BigDecimalの方が優れています。ここだから

  • 通貨計算の場合はintまたはlong。
  • floatとdoubleは、ほとんどの基数10の実数を正確に表すことができません。

リソース:

https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currencyから

浮動小数点数と倍精度数は、ほとんどの基数10の実数を正確に表すことができないためです。

これがIEEE-754浮動小数点数の仕組みです。符号専用のビット、指数を格納するための数ビット、および実際の小数部の残りを専用にします。これにより、数値は1.45 * 10 ^ 4のような形式で表されます。ただし、ベースが10である代わりに2である点が異なります。

実際、すべての実際の10進数は、10の累乗の正確な小数として見ることができます。たとえば、10.45は1045/10 ^ 2です。また、一部の小数を10の累乗の小数として正確に表すことができないように(1/3が思い浮かぶ)、それらの一部も2の累乗の小数として正確に表すことができません。簡単な例として、浮動小数点変数内に0.1を保存することはできません。0.0999999999999999996のような最も近い表現可能な値を取得し、ソフトウェアは表示するときに0.1に丸めます。

ただし、不正確な数値に対して加算、減算、乗算、除算をさらに実行すると、わずかな誤差が加算されるため、精度が低下します。これにより、完全な精度が要求されるお金を扱うには、浮動小数点数と倍精度数が不十分になります。

Bloch、J.、Effective Java、第2版、アイテム48から:

The float and double types are particularly ill-suited for 

0.1(またはその他の負の10のべき乗)をfloatまたはdoubleとして正確に表すことは不可能であるため、通貨計算。

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またはlong。

また見てください


私はlongが小さな計算に適している部分にのみ同意します。私の実際の経験では、十分な精度と大量のお金を処理するのに十分であることが証明されています。はい、使用するのは少し面倒かもしれませんが、2つの重要な点でBigDecimalに勝ちます-1)高速です(BigDecimalはソフトウェアの丸めを使用します。これは、組み込みのCPU丸めがfloat / doubleで使用されるよりもずっと遅いです)および2) BigDecimalは、精度を損なうことなくデータベースに保持するのが困難です-すべてのデータベースがそのような「カスタム」タイプをサポートしているわけではありません。Longは、すべての主要なデータベースで広く知られ、広くサポートされています。
イヴァイロスラヴォフ

@Ivaylo Slavov、私のミスでごめんなさい。回答を更新しました。
Mdマフブールラーマン

同じ問題に対して別のアプローチを提供するために謝罪する必要はありません:)
Ivaylo Slavov

2
@Ivaylo Slavov、私はあなたに同意します。私の答えでは、私はちょうど私の意見を述べた。私は議論するのが大好きで、正しいことをする意欲があります。また、ご意見ありがとうございます。:)
Mdマフブールラーマン

このサイトの目的は、特定のシナリオに応じて、問題を明確にし、OPが正しい決定を下せるようにすることです。私たちにできることすべては:)プロセスをスピードアップである
Ivaylo Slavov

13

少なくともバックアップとして、通貨をに格納し、通貨long計算しますdouble。すべてのトランザクションをとして実行する必要がありlongます。

通貨を保管する理由longは、通貨を失いたくないからです。

を使用しdouble、お金がないとしましょう。誰かがあなたに3ダイムを与え、そしてそれらを取り戻します。

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

まあ、それはそれほどクールではありません。たぶん、10ドルの人は、最初に3ダイムを渡してから、9.70ドルを他の人に渡して、自分の財産を渡したいと思うかもしれません。

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

そして、あなたは彼らに10セントを返します:

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

これは壊れています。

ここで、長い値を使用して、10分の1セント(1 = $ 0.001)を追跡します。地球上のすべての人に10億、1億1,200万、75,000、143ドルを与えましょう。

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

ええと、待ってください、私たちは10億ドル以上を誰にでも与えることができます、そして、わずか2つ以上を使うでしょうか?ここでオーバーフローは災害です。

したがって、送金する金額を計算するときはいつでもdoubleMath.roundそれを使用してを取得しlongます。次に、を使用して残高を修正します(両方のアカウントを加算および減算します)long

あなたの経済は漏れません、そして、それは1兆ドルまで拡大します。

さらに注意が必要な問題があります。たとえば、20回の支払いを行う場合はどうしますか。

* 1回の支払いが何であるかを計算しlongます。次に乗算し20.0て範囲内にあることを確認します。もしそうなら、あなた20Lはあなたの残高から差し引かれる量を得るためにあなたに支払いを掛けます。通常、すべてのトランザクションはとして処理する必要があるlongため、個々のトランザクションをすべてまとめる必要があります。ショートカットとして乗算できますが、丸めエラー追加しないようにし、オーバーフローしないようにする必要があります。doubleつまり、で実際の計算を行う前に確認する必要がありますlong


8

ユーザーに表示される可能性のある値は、ほとんどの場合整数である必要があります。お金はこれの最も顕著な例です。900のHPを持つモンスターに225のダメージを4回与えて、HPがまだ1つ残っていることを発見すると、経験値からは、あなたが何かを手に入れるのに十分な額の目に見えない割合であることに気付くのと同じくらい減ります。

より技術的な面では、関心のような高度なことをするためにフロートに戻る必要がないことに注意する価値があると思います。選択した整数型に十分なヘッドルームがある限り、乗算と除算は10進数による乗算の代わりになります。たとえば、4%を切り捨てて切り捨てます。

number=(number*104)/100

標準の規則で四捨五入して4%を追加するには:

number=(number*104+50)/100

ここでは浮動小数点の不正確さはありません.5。丸めは常にマーク上で正確に分割されます。

編集、本当の質問:

議論は、私が質問はすべてが何であるかを概説することについての簡単なよりも有用であり得ることを考え始める行っている方法を見てint/ float答え。問題の核心は、データ型ではなく、プログラムの詳細を制御することです。

整数を使用して非整数値を表すと、プログラマは実装の詳細を処理する必要があります。「使用する精度は?」と「どのように丸めるの?」明示的に回答する必要がある質問です。

一方、フロートはプログラマに心配を強いるものではありません。すでに期待されていることはほとんど行われています。しかし、floatは無限の精度ではないため、一部の丸めが行われ、その丸めはかなり予測不能です。

フロートを使用して、丸めを制御したい場合はどうなりますか?それはほとんど不可能であることが判明しました。floatを本当に予測可能にする唯一の方法は、2^ns で表現できる値のみを使用することです。しかし、その構造はフロートの使用を非常に困難にします。

したがって、単純な質問に対する答えは次のとおりです。制御する場合は整数を使用し、そうでない場合は浮動小数点数を使用します。

しかし、議論されている質問は、質問の別の形にすぎません。あなたはコントロールを取りたいですか?


5

「ゲームだけ」であっても、長い間バックアップされたMartin FowlerのMoney Patternを使用します。

どうして?

ローカライズ(L10n):そのパターンを使用すると、ゲーム通貨を簡単にローカライズできます。「Transport Tycoon」のような古いタイクーンゲームについて考えてください。これにより、プレイヤーはゲーム内の通貨(英国ポンドから米ドルへ)を変更して、実際の通貨に合わせることができます。

そして

longデータ型は、64ビットの符号付き2の補数整数です。最小値は-9,223,372,036,854,775,808で、最大値は9,223,372,036,854,775,807です(Javaチュートリアル

つまり、現在のM2米国マネーサプライの 9,000倍(〜10,000億ドル)を保存できます。(好奇心ならば、見るあなたに他の世界の通貨を使用するのに十分な余地を与えて、おそらく、でもそれらの/はハイパーインフレを持っていた人のポスト第一次世界大戦のドイツのインフレパンの1ポンドは30億マークでした、)

Longは持続が容易で、非常に高速で、整数演算のみを使用してすべての利息計算を実行するのに十分なスペースを確保する必要があります。eBusinessの回答ではその方法について説明します。


2

これにどれだけの労力をかけたいですか?精度はどれほど重要ですか?丸めで発生する小数エラーと、バイナリシステムで10進数を表現する際の不正確さを追跡することに関心がありますか?

最終的には、「コーナーケース」および既知の問題のあるケースのユニットテストのコーディングと実装にもう少し時間を費やす傾向があります。したがって、任意の大量を包含し、BigDecimalまたはBigRationalパーツ(2つのBigInteger、1つは分母、もう1つは分子)-メインのBigIntegerに非分数部分を追加することで(おそらく定期的にのみ)分数部分を実際の分数に保つコードを含めます。その後、GUI計算の小数部分を排除するために、内部的にすべてをセント単位で追跡します。

おそらく(単純な)ゲームを複雑にする方法ですが、オープンソースとして公開されているライブラリには適しています!小数ビットを扱うときにパフォーマンスを良好に保つ方法を見つける必要があります...


1

確かに浮かない。わずか7桁で、次のような精度しかありません。

12,345.67 123,456.7x

すでに10 ^ 5ドルで、あなたはペニーのトラックを失いつつあるのを見ます。doubleは、ゲームの目的で使用できますが、精度の問題から実際の生活では使用できません。

ただし、トランザクションを追跡して合計する場合、長い(つまり、64ビット、つまりC ++では長いという意味)では十分ではありません。任意の会社の純保有量を維持するのに十分ですが、1年間のすべてのトランザクションがオーバーフローする可能性があります。それは、あなたの金融の「世界」がゲーム内でどれだけ大きいかに依存します。


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;
}

これにより、この場合のセントを整数で表し、ドル全体を整数で表すことができます。負であることを示す別個のフラグがあるため、値に符号なし整数を使用して、可能な量を2倍にすることができます(この考え方を数値に適用して、本当に大きな数値を取得する数値クラスを作成することもできます)。必要なのは、数学演算子をオーバーロードするだけで、これを古いデータ型のように使用できます。より多くのメモリを使用しますが、実際には値の制限が拡大します。

これをさらに拡張して、ユニット全体の部分ユニット数などを含めることができるため、100サブユニット以外の通貨(この場合はセント)をドル単位で細分化することができます。たとえば、125個のフループで1つのフラッパーを構成できます。

編集:

マネークラスがアクセスして、その通貨と他のすべての通貨の為替レートを提供できるように、ルックアップテーブルを使用できるという点で、アイデアを拡張します。これをオペレーターのオーバーロード関数に組み込むことができます。したがって、4ポンドに4米ドルを自動的に追加しようとすると、6.6ポンドが自動的に得られます(執筆時点)。


0

元の質問のコメントの1つで述べたように、この問題はStackExchangeでカバーされています:https ://stackoverflow.com/questions/8148684/what-is-the-best-data-type-to-use-for- java-in-javaアプリ

基本的に、浮動小数点型は決して通貨を表すために使用されるべきではなく、ほとんどの言語と同様にJavaにはより適切な型があります。 プリミティブに関するオラクル独自のドキュメントでは、BigDecimalの使用が推奨されています。


-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を使用していますが、それは私が考えるより良い方法であり、より便利なメソッドを保存できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.