[unity, C#] 思い通りにならない float の計算誤差

0.01 を 100 回足したら当然 1 だよなぁ?

いや、確かに「ほとんど1」ではあるけど……ちょっと……微妙に……。

ちなみに double にするとこんな感じ。ほんの少しマシになるだけで、1 にはならない。

小数点を扱える float や double ですが、このようにピッタリと期待される数値にならず、困った経験がある人、多いんじゃないでしょうか。

具体的な理由は他の解説サイトに譲るとして、float や double は、多少の誤差はしょうがないよね? という変数です。ゆえにこれは正着、なんの問題もありません。

でも、これでは困ることもあります。

例えばオブジェクトを 360 度回転させようとすると、359.9997 と微妙にズレた回転になったり、金額の計算をしようとすれば(微妙ではあるものの)ズレまくって話にならないといったケースです。

ここではその問題に対処する方法をいくつか紹介します。

decimal を使う

C# の場合はこれが1番簡単。少数点も使える上に、誤差が出ることは「ほぼ」なく、double よりも有効桁が大きいです。

実は誤差が出ることもありますが、float に比べるとまず問題のないレベル、金額の計算にもほぼこれが使われます。

短所は float、double よりも速度が遅いこと。Vector3 (float) にキャストする場合は更に速度ロスが発生しますので、使いすぎには注意しましょう。

固定少数点を使う

色々な固定少数点がありますが、例えば long の変数を用意し、1234567 という数字を入れます。
このままではただの整数ですが、「いいえ、これは 12345.67 です(100 = 1)」と決めつける方法です。

long LLLLL_MM = 1234567;
LLLLL_MM += 2;
LLLLL_MM += 30;

// 結果を取得 12345.99f
float val = (float)LLLLL_MM / 100.0f;

計算は全て整数で行い、最後の結果だけ 100 で割れば欲しい値が得られます。
計算中は整数なので、decimal に比べると処理が軽くなるという利点があります。
ただし、計算した後の「値の範囲」には注意してください。(わりと足りなくなることがあります)

短所は決めつけを徹底しないとどこが整数と少数の境かわからなくなるところですね。
決めつけなので、例えば 4096 = 1 とする事もできますがこうなるともう 333333 という数字の「整数」がいくつかなんて計算しないとわかりません。

なるべく計算する部分を一か所にまとめる、変数名でそれとすぐわかるようにするなど運用に気を遣いましょう。

番外編:ゴリ押す

decimal とか固定少数とか、なんか面倒! というアナタにオススメなのがゴリ押す方法です。

rot += 0.01f;
if (rot > 359.99f || rot < 360.01f)
{
  rot = 360.0f;
}

単に特定の範囲に入ったら値を強制するだけです。

さすがに雑だろ、と思うかもしれませんがこんなので済ました方が楽なこともあります。

float a = 0.0f;

for (int i = 0; i < 99; i++)
{
    a += 0.01f;
    Debug.Log(a);
}
a = 1.0f;
Debug.Log(a);

100 回まわして最後 1 ピッタリにするなら、最後は足し算じゃなくて代入でいいじゃない。
こんなゴリラコーディングも、局所的に固定少数使うより見たまんまでわかりやすいし、状況によってはアリかもしれません。

短所は 応用力 0 ということ。光のプログラマーならこんなの絶対すすめません。
コードレビューされたら間違いなく「直せ」と突き返されるレベル。

その場凌ぎだから当然ですが、時間がない、最小限の修正でなんとかしたいなんて時にふと思い出すと(その瞬間だけは)幸せになれることがあります。

すすめてないよ!!



返信を残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA