【Unity】浮動小数点数の「ぼほ等しい」を判定する

次のfloat型の値が等しいかどうかを比較しようとしているんだけど…

float a = 0.1f;
float b = 1.1f - 1.0f;

// 値の表示
print($"a = {a}, b = {b}");

// 値の比較
if (a == b)
{
    print("値が等しいよ!");
}
else
{
    print("値が違うよ!");
}

等しくなるはずなのに、値が違う判定になっちゃうの😥

これは、浮動小数点誤差によるものだね。aとbは一見等しいように見えて、微妙に値が異なっているの。

C#言語で扱うfloat型double型などの浮動小数点数は、計算することで値の丸め誤差が発生してしまいます。

例えば、1.1 – 1.0という計算は値が0.1になるはずですが、2進数で表すと割り切れない数となり0.1とは微妙に異なる数になってしまいます。

このような浮動小数点数同士が等しいかどうかを比較したい場合、両者の差がある一定値以内ならほぼ等しいとみなすことで回避可能です。

// ほぼ等しい判定
if (Mathf.Approximately(a, b))
{
    print("値が等しいよ!");
}
else
{
    print("値が違うよ!");
}

また、Vector3構造体など、幾つかのUnity提供型の等価演算では、内部的にこのような比較処理を行っています。 [1]

本記事では、このような浮動小数点数のほぼ等しい判定を行う方法を解説していきます。

動作環境
  • Unity2021.1.24f1
  • .NET 4.x

Mathf.Approximately()メソッドを使う

与えられた2つのfloat型の値がほぼ等しいかどうかを判定するメソッドです。

public static bool Approximately(float a, float b);

引数には比較対象の浮動小数点数を指定します。
戻り値は、引数の値がほぼ等しければtrueそうでなければfalseです。

サンプルスクリプト

2つの値がほぼ等しいかを判定するサンプルスクリプトです。

ApproximatelyExample.cs
using UnityEngine;

public class ApproximatelyExample : MonoBehaviour
{
    private void Start()
    {
        // 丸め誤差がある場合
        float a1 = 0.1f, b1 = 1.1f - 1f;
        // 非常に大きい数で誤差が1ある場合
        float a2 = 1234567f, b2 = 1234568f;
        // 非常に大きい数で大きい誤差がある場合
        float a3 = 1234567f, b3 = 1230000f;

        // 結果出力
        print($"a1 = {a1}, b1 = {b1}, same = {Mathf.Approximately(a1, b1)}");
        print($"a2 = {a2}, b2 = {b2}, same = {Mathf.Approximately(a2, b2)}");
        print($"a3 = {a3}, b3 = {b3}, same = {Mathf.Approximately(a3, b3)}");
    }
}

実行結果

デバッグログの出力結果です。

1つ目の丸め誤差があるケースでは、ほぼ等しい判定になります。

2つ目の大きな数同士の比較では、誤差が1という大きな誤差があるにも関わらずほぼ等しい判定になります。大きな数になると値の精度が落ちるため、大きな数の比較では閾値を広げる処理が内部的に行われています。 [2]

3つ目の大きな数が大きく異なるケースでは、ほぼ等しくない判定になります。

Vector2やVector3の等価演算について

ベクトル同士の比較を行いたい場合、次のように==演算子でほぼ等しい判定ができます。

サンプルスクリプト

VectorExample.cs
using UnityEngine;

public class VectorExample : MonoBehaviour
{
    private void Start()
    {
        // 2つのほぼ等しいベクトル
        var a = new Vector2(0.1f, 0.1f);
        var b = new Vector2(0.1f, 0.10001f);

        // ほぼ等しいかどうかの判定
        print($"same = {a == b}");
    }
}

実行結果

さいごに

float型同士の値がほぼ等しいかどうかを判定するには、Mathf.Approximately()を用いれば良いです。

Vector2やVector3型など、Unityが提供しているいくつかの型の等価演算では、内部的にほぼ等しい判定を行っています。

そのため、組み込み型の浮動小数点数の等価演算を行う場合に注意すると良いでしょう。

参考サイト