前回の
[Unity5 C#] float型でハマった罠 その2 "途中で予期せぬ値に変わる時がある"
の続きです
今回がこの問題についての完結編、最終章になります
 
 
この問題をさらに難解にしている原因が実はもう1つあります
それは下の赤で囲まれている「Consoleウインドウ」の表示が
騙してくる時があるからです
 
 
以下が再現コードです
-------------------------------

float s1 = 1.3f;
float s2 = s1 % 1.0f;

Debug.Log("s2 = " + s2); //←この時点で「s2 = 0.3」と表示される

if (s2 == 0.3f) {
      Debug.Log("true");
} else {
      Debug.Log("false");  //←しかしこっちが呼び出される
}
-------------------------------

Consoleウインドウにはしっかりと「s2 = 0.3」と表示されるのにIFが不成立になります
実に納得のいかない実行結果です
 
前回のforループ実験結果から察するに
s2の中身は本当は 0.3 ではなく 0.3000001 のような誤差も入っているのだと思われます
ですがConsoleウインドウに表示する時に内部で勝手に端数処理みたいな事が行われ
0.3と表示されているのでは?と妄想できます
 
forループ実験の時は 0.3000001 のような値もそのまま表示されていた事から
この、Consoleウインドウに表示される時だけ端数処理がされてる現象は
される時とされない時がある感じで
余計に困った状態です
必ずどちらかに固定されてる方がまだマシです
 
ちなみに先程のコードの値を変えて実行すると
IFが成立する値もあります
分かりやすく変更箇所を赤色にしてあります
-------------------------------

float s1 = 1.6f;
float s2 = s1 % 1.0f;

Debug.Log("s2 = " + s2); //←この時点で「s2 = 0.6」と表示される

if (s2 ==
0.6f) {
      Debug.Log("true");  //←こっちが呼び出される!これは正常な結果
} else {
      Debug.Log("false");
}
-------------------------------

0.3、0.4だとIFが不成立になり
0.5、0.6だとIFが成立し
0.7だとまたIFが不成立になります
 
とても厄介です!
 
尚、0.3の場合も小数点第二位以下を四捨五入するとIFが成立します
この結果から0.3の場合の流れは
-------------------------------

float s1 = 0.3f;
float s2 = s1 % 1.0f;  //←ここで0.299999みたいになってるっぽい

Debug.Log("s2 = " + s2); //←なのでこの時点で「s2 = 0.3」と表示されてもこれは騙しで

if (s2 == 0.3f) {
      Debug.Log("true");
} else {
      Debug.Log("false"); //←結果IFが不成立になりこっちが呼び出されるという事
}
-------------------------------

こうなってるようです
見えない犯人の正体がようやく見えました
 
以上の事から
小数点をIFで比較する時は、必要な位以下を切り捨てる方が安全です
 
「よーし!どうしたらいいかこれでわかったぜ!ひゃっほーい」と早まってはいけません
恐ろしいことにまだ罠があります
それは小数点以下を切り捨てる関数に潜んでいます
 
小数点以下を切り捨てる関数はUnityにあります
「Mathf.Floor()」
Mathf.Floor(12.345)  →  12 になる
 
しかし小数点以下の任意の場所を切り捨てる関数はUnityに存在していません(たぶん)
(12.345 を 12.3 にしたい等)
 
そうなると多くの場合計算を駆使して実現しようとするはずです
例えば次のような感じ
-------------------------------
// 0.31 を 0.3 にしたい場合(小数点第一以下切り捨ての例)
s2 = 0.31f;
s2 = s2 * 10;                 //←0.31 を 一時的に10倍にし 3.1 にする
s2 = Mathf.Floor(s2) / 10; //←3.1 の .1 部分をFloorで切り捨て 3 にし
                                      //その後10で割る事で 0.3 になり目的達成
-------------------------------

このように小数点の位置を掛け算でずらして処理するやり方は
C言語をはじめ、色々なところで使われています
そんな背景もあり、これでOKそうですが
先程の「0.3と表示されてても実際は0.2999999 問題」が発生してる場合
Mathf.Floor(s2)の段階で
本来「3」を返してほしいのに「2」が返ってきます
-------------------------------
s2 = 0.2999f;
s2 = s2 * 10;                 //←0.2999 を 一時的に10倍にし 2.999 にする
s2 = Mathf.Floor(s2) / 10; //←2.999 の .999 部分をFloorで切り捨て 2 になる
                                      //その後10で割る事で 0.2 になり目的失敗
-------------------------------
Mathf.Floor()は容赦なく切り捨てて四捨五入はしてくれません
 
なので上のやり方では「float型が途中で予期せぬ値に変わる問題」が解決しません
どんどんfloatバグ(勝手にそう呼んでいる)の沼にハマっていきます
 
小数点以下の任意の場所を切り捨てるには次のやり方を強くオススメします
-------------------------------
// 0.31 を 0.3 にしたい場合(小数点第一以下をカットしたい例)
s2 = 0.31f;
s2 = float.Parse( s2.ToString("f1") )」
-------------------------------
何をしているかというと
「s2.ToString("f1")」で一旦float型を文字列型にして
「("f1")」の部分は "小数点第一位まで表示(第ニ位は四捨五入される)"という命令です
「("f2")」とやれば "小数点第二位まで表示(第三位は四捨五入される)"です
小数点以下が全部いらない場合は「("f0")」とやればOK
とても万能です
ちなみにf1でもF1でも動作します

「float.Parse()」は文字型をfloat型に変換し、もとの型に戻してる感じです
 
 
説明が長くなりましたが
この記事の一番最初に載せたfloat型バグの再現コードを振り返ります
-------------------------------

float s1 = 1.3f;
float s2 = s1 % 1.0f;

Debug.Log("s2 = " + s2); //←この時点で「s2 = 0.3」と表示される

if (s2 == 0.3f) {
      Debug.Log("true");
} else {
      Debug.Log("false");  //←しかしこっちが呼び出される
}
-------------------------------

次のようにすると予期せぬ値に変わる事なく
正常な結果が出るようになります
-------------------------------

float s1 = 1.3f;
float s2 = s1 % 1.0f;

Debug.Log("s2 = " + s2); //←この時点で「s2 = 0.3」と表示される

if (
float.Parse( s2.ToString("f1") ) == 0.3f) {
      Debug.Log("true");  //←こっちが呼び出される!やった!
} else {
      Debug.Log("false");
}
-------------------------------

 
以上が
「float型でハマった罠 "途中で予期せぬ値に変わる時がある"」問題でした
 
この問題は、その1~3で取り上げた複数の原因が重なり
原因究明するのに相当苦戦させられました…
 

-------------------------------

●実験環境

・Unity 5.3.5f1 Personal (64bit)

・Windows 10 Pro (64bit)

-------------------------------