【UnityC#】Mathf.Sin()に巨大な値を入れた時の計算誤差を調べてみた

# はじめに
Unityの三角関数Mathf.Sin()を使用するとき、
計算誤差を減らすためにSin関数に渡すラジアン値θを0 ≦ θ < 2πの範囲に収めたほうが良いといわれます。

では、θ >= 2π　となるようなθを入れた場合、どの程度の大きさの誤差が発生するのでしょうか？

ちょっと気になったので調べてグラフ化してみました。

<br>
ちなみに、数学的には以下のような等式が成立します。
>sin(θ + 2π * (任意の整数) ) = sin(θ)　(θは実数とする)

# 環境
Unity2018.1.0f2
Windows 10

# 誤差の測定
## 誤差の測定区間
以下の３パターンの区間での誤差を調べてみました。
> 0 <= θ <= 36000°   (θ = 0°, 360°, 720°, 1080°, 1440°, ・・・ 36000°)
> 36000° <= θ <= 72000°
> 1800000° <= θ <= 1836000°

θは360°刻みで増加させ、それぞれの誤差を測定しました。

## 誤差の計算式
ここで、sin(θ)の誤差Eは以下のような計算で求めています。

```csharp:誤差Eの計算式
float E = Mathf.Abs(sin(theta) - sin(0.0f));
```

sin(0°)とsin(θ)の差分をとり、その絶対値を誤差としています。

差分をとるだけだと正・負の誤差が表れてしまって見づらいので、絶対値Mathf.Abs()で見やすくしています。

<br>
ちなみに、θはシータ(theta)と読みます。


# 測定結果
0 <= θ <= 36000°
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/9760b436-f029-697b-bf9d-03193d316128.png)

36000° <= θ <= 72000°
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/e9e12ab9-9744-ce6b-503d-6501bb398156.png)

1800000° <= θ <= 1836000°
![image.png](https://qiita-image-store.s3.amazonaws.com/0/44288/d7d9565e-e009-d964-7942-78b4f1728b43.png)

Mathf.Sin(theta)のthetaの数値が大きくなるほど誤差も大きくなることが読み取れます。

# ソースコード
Mathf.Sinの誤差の計測に使用したソースコードは以下になります。

```TestSin.cs
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;

	}

}
```

```FileManager.cs
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を実行しているだけです。

```Mathf.cs
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

・・・テイラー展開？