はじめに
Unityの三角関数Mathf.Sin()を使用するとき、
計算誤差を減らすためにSin関数に渡すラジアン値θを0 ≦ θ < 2πの範囲に収めたほうが良いといわれます。
では、θ >= 2π となるようなθを入れた場合、どの程度の大きさの誤差が発生するのでしょうか?
ちょっと気になったので調べてグラフ化してみました。
ちなみに、数学的には以下のような等式が成立します。 >sin(θ + 2π * (任意の整数) ) = sin(θ) (θは実数とする)
環境
Unity2018.1.0f2
Windows 10
誤差の測定
誤差の測定区間
以下の3パターンの区間での誤差を調べてみました。
0 <= θ <= 36000° (θ = 0°, 360°, 720°, 1080°, 1440°, ・・・ 36000°)
36000° <= θ <= 72000°
1800000° <= θ <= 1836000°
θは360°刻みで増加させ、それぞれの誤差を測定しました。
誤差の計算式
ここで、sin(θ)の誤差Eは以下のような計算で求めています。
float E = Mathf.Abs(sin(theta) - sin(0.0f));
sin(0°)とsin(θ)の差分をとり、その絶対値を誤差としています。
差分をとるだけだと正・負の誤差が表れてしまって見づらいので、絶対値Mathf.Abs()で見やすくしています。
ちなみに、θはシータ(theta)と読みます。
測定結果
Mathf.Sin(theta)のthetaの数値が大きくなるほど誤差も大きくなることが読み取れます。
ソースコード
Mathf.Sinの誤差の計測に使用したソースコードは以下になります。
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEditor;
public class TestSin : MonoBehaviour
{
const int k_LoopMax = 10000;
const int k_DegreeInterval = 360;
void Start()
{
Debug.Log("計測を開始します");
int largeDegree = 360;
int smallDegree = 0;
var sb = new StringBuilder();
sb.AppendFormat("計測日時 : {0}\n", System.DateTime.Now);
sb.AppendFormat("Unityバージョン : {0}\n", Application.unityVersion);
sb.AppendFormat("ループ回数 : {0}\n", k_LoopMax);
sb.AppendFormat("角度の刻み幅 : {0}\n", k_DegreeInterval);
sb.AppendFormat("i,Degree,誤差の絶対値,sin(Degree)\n",smallDegree);
int degree = smallDegree;
float radian = degree * Mathf.Deg2Rad; // ラジアン値
float baseSin = Mathf.Sin(radian); // sinのベース値
sb.AppendFormat("{0},{1},{2},{3}\n",
-1, // ループカウンタ
degree, // 角度
Mathf.Abs(Mathf.Sin(radian) - baseSin), // 誤差
Mathf.Sin(radian) // サイン値
);
for (int i = 0; i < k_LoopMax; i++)
{
degree = largeDegree + smallDegree;
radian = degree * Mathf.Deg2Rad;
sb.AppendFormat("{0},{1},{2},{3}\n",
i, // ループカウンタ
degree, // 角度
Mathf.Abs(Mathf.Sin(radian) - baseSin), // 誤差
Mathf.Sin(radian) // サイン値
);
largeDegree += k_DegreeInterval;
}
Debug.Log("計測が完了しました");
// CSV書き出し
string csvText = sb.ToString();
FileManager.Save(csvText);
Debug.Log("CSV書き出しが完了しました");
EditorApplication.isPlaying = false;
}
}
using UnityEngine;
using System.IO;
using System;
/// <summary>
/// csvファイルの書き出しを行う
/// </summary>
public class FileManager
{
// ファイル書き出し
public static void Save(string text)
{
# if UNITY_EDITOR
var now = System.DateTime.Now;
var datetime = string.Format("{0}{1:D2}{2:D2}_{3:D2}{4:D2}{5:D2}", now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second);
var path = Application.dataPath + "/" + string.Format("Sin_{0}.csv", datetime);
System.IO.StreamWriter sw = new System.IO.StreamWriter(path, false, System.Text.Encoding.GetEncoding("shift_jis"));
sw.Write(text);
sw.Flush();
sw.Close();
UnityEditor.AssetDatabase.Refresh();
# endif
}
}
おまけ: Mathf.Sinの中身について軽く調べてみた。
Mathf.Sin()の正体は System.Math.Sin()
UnityEngine.Mathf.Sin()ですが、内部的にはSystem.Math.Sinを実行しているだけです。
public static float Sin(float f) { return (float)Math.Sin(f); }
参考:
https://github.com/Unity-Technologies/UnityCsReference/blob/2018.2/Runtime/Export/Mathf.cs
System.Math.Sin()の実装
System.Math.Sin()の中身はXの多項式(polynomial)で実装されているみたいです。
https://stackoverflow.com/questions/25327821/how-can-i-see-the-source-code-of-system-math-sin
・・・テイラー展開?