float誤差回避チートシート


アジェンダ

はじめに

floatなどの浮動小数点数には丸め誤差がありますが、丸め誤差によっていくつかの問題が発生します。
例えば、0.96f + 0.03f + 0.01fは0.99999994という数値になりますが、このままfloorを取ってしまうと0になってしまいます。
本記事では、Unityの丸め誤差による問題の回避方法を紹介します。

計算機イプシロン

/// <summary>
/// 計算機イプシロン
/// </summary>
private const float Epsilon = 1E-06f;

プロジェクトで使用する計算機イプシロンを定義します。
計算機イプシロンは、「1より大きい最小の数と1との差」で定義されますが、複数回演算することによって誤差が積み重なることがあるため、実際のプロジェクトでは本来の数値よりもすこし大きめの数値で定義して運用します。
float.EpsilonとMathf.Epsilonは計算機イプシロンではないため使えません。

Floor

/// <summary>
/// 小数を切り捨て
/// </summary>
/// <param name="value">値</param>
/// <returns>切り捨てられた値</returns>
public static float Floor(float value) => Mathf.Floor(value + Epsilon);

Ceiling

/// <summary>
/// 小数を切り上げ
/// </summary>
/// <param name="value">値</param>
/// <returns>切り上げられた値</returns>
public static float Ceil(float value) => Mathf.Ceil(value - Epsilon);

Approximately

/// <summary>
/// aとbが近似しているかを取得
/// </summary>
/// <param name="a">値</param>
/// <param name="b">値</param>
/// <returns>aとbが近似しているか (true: そう, false: 違う)</returns>
public static bool Approximately(float a, float b) => Mathf.Abs(b - a) < Epsilon;

Mathf.Approximately(0.0f, 0.0000001f)はfalseと返却されるため、これをtrueとしたいなら自前で作成しなければいけません。
Mathf.Approximatelyは相対誤差で判定しているので、0.0000001fと比較するなら0.00000010000001fなどと比較するのが正しい使い方です。

GreaterThan

/// <summary>
/// aがbより大きいかを取得
/// </summary>
/// <param name="a">値</param>
/// <param name="b">値</param>
/// <returns>aがbより大きいか (true: そう, false: 違う)</returns>
public static bool GreaterThan(float a, float b) => a > b + Epsilon;

GreaterThanOrEqual

/// <summary>
/// aがb以上かを取得
/// </summary>
/// <param name="a">値</param>
/// <param name="b">値</param>
/// <returns>aがb以上か (true: そう, false: 違う)</returns>
public static bool GreaterThanOrEqual(float a, float b) => a > b - Epsilon;

LessThan

/// <summary>
/// aがbより小さいかを取得
/// </summary>
/// <param name="a">値</param>
/// <param name="b">値</param>
/// <returns>aがbより小さいか (true: そう, false: 違う)</returns>
public static bool LessThan(float a, float b) => a < b - Epsilon;

LessThanOrEqual

/// <summary>
/// aがb以下かを取得
/// </summary>
/// <param name="a">値</param>
/// <param name="b">値</param>
/// <returns>aがb以下か (true: そう, false: 違う)</returns>
public static bool LessThanOrEqual(float a, float b) => a < b + Epsilon;

おわりに

https://gist.github.com/DuceMonster/536cfc2a40923c3235928ffed68d3458
Gistにソースコードをアップしましたので、こちらをご参考ください。
好みで拡張メソッドにするのも良いと思います。

© 2020-2021 Manicreator