417 lines
14 KiB
C#
417 lines
14 KiB
C#
namespace Nerd_STF.Mathematics;
|
|
|
|
public static class Mathf
|
|
{
|
|
public static float Absolute(float val) => val < 0 ? -val : val;
|
|
public static int Absolute(int val) => val < 0 ? -val : val;
|
|
|
|
public static float AbsoluteMod(float val, float mod)
|
|
{
|
|
while (val >= mod) val -= mod;
|
|
while (val < 0) val += mod;
|
|
return val;
|
|
}
|
|
public static int AbsoluteMod(int val, int mod)
|
|
{
|
|
while (val >= mod) val -= mod;
|
|
while (val < 0) val += mod;
|
|
return val;
|
|
}
|
|
|
|
public static Angle ArcCos(float value) => ArcSin(-value) + Angle.Quarter;
|
|
|
|
public static Angle ArcCot(float value) => ArcCos(value / Sqrt(1 + value * value));
|
|
|
|
public static Angle ArcCsc(float value) => ArcSin(1 / value);
|
|
|
|
public static Angle ArcSec(float value) => ArcCos(1 / value);
|
|
|
|
// Maybe one day I'll have a polynomial for this, but the RMSE for an order 10 polynomial is only 0.00876.
|
|
public static Angle ArcSin(float value) => new((float)Math.Asin(value), Angle.Type.Radians);
|
|
|
|
public static Angle ArcTan(float value) => ArcSin(value / Sqrt(1 + value * value));
|
|
public static Angle ArcTan2(float a, float b) => ArcTan(a / b);
|
|
|
|
public static float Average(Equation equ, float min, float max, float step = Calculus.DefaultStep)
|
|
{
|
|
List<float> vals = new();
|
|
for (float x = min; x <= max; x += step) vals.Add(equ(x));
|
|
return Average(vals.ToArray());
|
|
}
|
|
public static float Average(params float[] vals) => Sum(vals) / vals.Length;
|
|
public static int Average(params int[] vals) => Sum(vals) / vals.Length;
|
|
|
|
public static float Binomial(int n, int total, float successRate) =>
|
|
Combinations(total, n) * Power(successRate, n) * Power(1 - successRate, total - n);
|
|
|
|
public static int Ceiling(float val)
|
|
{
|
|
float mod = val % 1;
|
|
return (int)(mod == 0 ? val : (val + (1 - mod)));
|
|
}
|
|
|
|
public static float Clamp(float val, float min, float max)
|
|
{
|
|
if (max < min) throw new ArgumentOutOfRangeException(nameof(max),
|
|
nameof(max) + " must be greater than or equal to " + nameof(min) + ".");
|
|
val = val < min ? min : val;
|
|
val = val > max ? max : val;
|
|
return val;
|
|
}
|
|
public static int Clamp(int val, int min, int max) => (int)Clamp((float)val, min, max);
|
|
|
|
// nCr (n = total, r = size)
|
|
public static int Combinations(int total, int size) => Factorial(total) /
|
|
(Factorial(size) * Factorial(total - size));
|
|
|
|
public static float Cos(Angle angle) => Cos(angle.Radians);
|
|
public static float Cos(float radians) => Sin(radians + Constants.HalfPi);
|
|
|
|
public static float Cot(Angle angle) => Cot(angle.Radians);
|
|
public static float Cot(float radians) => Cos(radians) / Sin(radians);
|
|
|
|
public static float Csc(Angle angle) => Csc(angle.Radians);
|
|
public static float Csc(float radians) => 1 / Sin(radians);
|
|
|
|
public static float Divide(float val, params float[] dividends) => val / Product(dividends);
|
|
public static int Divide(int val, params int[] dividends) => val / Product(dividends);
|
|
|
|
public static float Dot(float[] a, float[] b)
|
|
{
|
|
if (a.Length != b.Length) throw new InvalidSizeException("Both arrays must have the same length");
|
|
float[] vals = new float[a.Length];
|
|
for (int i = 0; i < a.Length; i++) vals[i] = a[i] * b[i];
|
|
return Sum(vals);
|
|
}
|
|
public static float Dot(params float[][] vals)
|
|
{
|
|
float[] res = new float[vals[0].Length];
|
|
for (int i = 0; i < res.Length; i++)
|
|
{
|
|
float m = 1;
|
|
for (int j = 0; j < vals.Length; j++) m *= vals[j][i];
|
|
res[i] = m;
|
|
}
|
|
return Sum(res);
|
|
}
|
|
|
|
public static int Factorial(int amount)
|
|
{
|
|
if (amount < 0) return 0;
|
|
int val = 1;
|
|
for (int i = 2; i <= amount; i++) val *= i;
|
|
return val;
|
|
}
|
|
|
|
public static int[] Factors(int val)
|
|
{
|
|
List<int> factors = new();
|
|
factors.Add(1);
|
|
for (int i = 2; i < val; i++) if (val % i == 0) factors.Add(i);
|
|
factors.Add(val);
|
|
return factors.ToArray();
|
|
}
|
|
|
|
public static int Floor(float val) => (int)(val - (val % 1));
|
|
|
|
public static Dictionary<float, float> GetValues(Equation equ, float min, float max,
|
|
float step = Calculus.DefaultStep)
|
|
{
|
|
Dictionary<float, float> vals = new();
|
|
for (float x = min; x <= max; x += step) vals.Add(x, equ(x));
|
|
return vals;
|
|
}
|
|
|
|
public static int GreatestCommonFactor(params int[] vals)
|
|
{
|
|
int loops = Min(vals);
|
|
for (int i = loops; i > 0; i--)
|
|
{
|
|
bool fit = true;
|
|
foreach (int v in vals) fit &= v % i == 0;
|
|
if (fit) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
public static float InverseSqrt(float val) => 1 / Sqrt(val);
|
|
|
|
public static int LeastCommonMultiple(params int[] vals) => Product(vals) / GreatestCommonFactor(vals);
|
|
|
|
public static float Lerp(float a, float b, float t, bool clamp = true)
|
|
{
|
|
float v = a + t * (b - a);
|
|
if (clamp) v = Clamp(v, Min(a, b), Max(a, b));
|
|
return v;
|
|
}
|
|
public static int Lerp(int a, int b, float value, bool clamp = true) => (int)Lerp((float)a, b, value, clamp);
|
|
|
|
public static Equation MakeEquation(Dictionary<float, float> vals) => delegate (float x)
|
|
{
|
|
if (vals.Count < 1) throw new UndefinedException();
|
|
if (vals.Count == 1) return vals.Values.First();
|
|
|
|
if (vals.ContainsKey(x)) return vals[x];
|
|
float? min, max;
|
|
|
|
if (x < (min = vals.Keys.Min()))
|
|
{
|
|
max = vals.Keys.Where(x => x != min).Min();
|
|
float distX = x - min.Value, distAB = max.Value - min.Value;
|
|
return Lerp(vals[min.Value], vals[max.Value], distX / distAB, false);
|
|
}
|
|
else if (x > (max = vals.Keys.Max()))
|
|
{
|
|
min = vals.Keys.Where(x => x != max).Max();
|
|
float distX = x - min.Value, distAB = max.Value - min.Value;
|
|
return Lerp(vals[min.Value], vals[max.Value], distX / distAB, false);
|
|
}
|
|
|
|
float curDistMax = float.MaxValue, curDistMin = float.MaxValue;
|
|
foreach (float keyX in vals.Keys)
|
|
{
|
|
float dist = Absolute(keyX - x);
|
|
if (keyX < x && dist <= curDistMin)
|
|
{
|
|
min = keyX;
|
|
curDistMin = dist;
|
|
}
|
|
if (keyX > x && dist <= curDistMax)
|
|
{
|
|
max = keyX;
|
|
curDistMax = dist;
|
|
}
|
|
}
|
|
|
|
if (!min.HasValue || !max.HasValue || min == max) throw new UndefinedException();
|
|
float all = max.Value - min.Value, diff = x - min.Value;
|
|
return Lerp(vals[min.Value], vals[max.Value], diff / all);
|
|
};
|
|
|
|
public static float Max(Equation equ, float min, float max, float step = Calculus.DefaultStep)
|
|
{
|
|
float Y = equ(min);
|
|
for (float x = min; x <= max; x += step)
|
|
{
|
|
float val = equ(x);
|
|
Y = val > Y ? val : Y;
|
|
}
|
|
return Y;
|
|
}
|
|
public static float Max(params float[] vals)
|
|
{
|
|
if (vals.Length < 1) return 0;
|
|
float val = vals[0];
|
|
foreach (float d in vals) val = d > val ? d : val;
|
|
return val;
|
|
}
|
|
public static int Max(params int[] vals)
|
|
{
|
|
if (vals.Length < 1) return 0;
|
|
int val = vals[0];
|
|
foreach (int i in vals) val = i > val ? i : val;
|
|
return val;
|
|
}
|
|
public static T? Max<T>(params T[] vals) where T : IComparable<T>
|
|
{
|
|
if (vals.Length < 1) return default;
|
|
T val = vals[0];
|
|
foreach (T t in vals) val = t.CompareTo(val) > 0 ? t : val;
|
|
return val;
|
|
}
|
|
|
|
public static float Median(params float[] vals)
|
|
{
|
|
float index = (vals.Length - 1) * 0.5f;
|
|
if (index % 1 == 0) return vals[(int)index];
|
|
float valA = vals[(int)index], valB = vals[(int)index + 1];
|
|
return (valA + valB) * 0.5f;
|
|
}
|
|
public static int Median(params int[] vals) => Median<int>(vals);
|
|
public static T Median<T>(params T[] vals) => vals[(vals.Length - 1) / 2];
|
|
|
|
public static float Min(Equation equ, float min, float max, float step = Calculus.DefaultStep)
|
|
{
|
|
float Y = equ(min);
|
|
for (float x = min; x <= max; x += step)
|
|
{
|
|
float val = equ(x);
|
|
Y = val < Y ? val : Y;
|
|
}
|
|
return Y;
|
|
}
|
|
public static float Min(params float[] vals)
|
|
{
|
|
if (vals.Length < 1) return 0;
|
|
float val = vals[0];
|
|
foreach (float d in vals) val = d < val ? d : val;
|
|
return val;
|
|
}
|
|
public static int Min(params int[] vals)
|
|
{
|
|
if (vals.Length < 1) return 0;
|
|
int val = vals[0];
|
|
foreach (int i in vals) val = i < val ? i : val;
|
|
return val;
|
|
}
|
|
public static T? Min<T>(params T[] vals) where T : IComparable<T>
|
|
{
|
|
if (vals.Length < 1) return default;
|
|
T val = vals[0];
|
|
foreach (T t in vals) val = t.CompareTo(val) < 0 ? t : val;
|
|
return val;
|
|
}
|
|
|
|
public static (T value, int occurences) Mode<T>(params T[] vals) where T : IEquatable<T>
|
|
{
|
|
if (vals.Length < 1) throw new ArgumentException("List must contain at least 1 element.", nameof(vals));
|
|
(T value, int occurences) = (vals[0], -1);
|
|
for (int i = 0; i < vals.Length; i++)
|
|
{
|
|
int count = vals.Count(x => x.Equals(vals[i]));
|
|
if (count > occurences)
|
|
{
|
|
value = vals[i];
|
|
occurences = count;
|
|
}
|
|
}
|
|
return (value, occurences);
|
|
}
|
|
|
|
// nPr (n = total, r = size)
|
|
public static int Permutations(int total, int size) => Factorial(total) / Factorial(total - size);
|
|
|
|
public static float Product(params float[] vals)
|
|
{
|
|
if (vals.Length < 1) return 0;
|
|
float val = 1;
|
|
foreach (float d in vals) val *= d;
|
|
return val;
|
|
}
|
|
public static int Product(params int[] vals)
|
|
{
|
|
if (vals.Length < 1) return 0;
|
|
int val = 1;
|
|
foreach (int i in vals) val *= i;
|
|
return val;
|
|
}
|
|
public static float Product(Equation equ, float min, float max, float step = 1)
|
|
{
|
|
float total = 1;
|
|
for (float f = min; f <= max; f += step) total *= equ(f);
|
|
return total;
|
|
}
|
|
|
|
public static float Power(float num, float pow) => (float)Math.Pow(num, pow);
|
|
public static float Power(float num, int pow)
|
|
{
|
|
if (pow <= 0) return 0;
|
|
if (pow == 1) return num;
|
|
float val = 1;
|
|
float abs = Absolute(pow);
|
|
for (int i = 0; i < abs; i++) val *= num;
|
|
if (pow < 1) val = 1 / val;
|
|
return val;
|
|
}
|
|
public static int Power(int num, int pow)
|
|
{
|
|
if (pow == 1) return num;
|
|
if (pow < 1) return 0;
|
|
int val = 1;
|
|
for (int i = 0; i < pow; i++) val *= num;
|
|
return val;
|
|
}
|
|
|
|
public static int PowerMod(int num, int pow, int mod)
|
|
{
|
|
if (pow == 1) return num;
|
|
if (pow < 1) return 0;
|
|
int val = 1;
|
|
int abs = Absolute(pow);
|
|
for (int i = 0; i < abs; i++) val = val * num % mod;
|
|
return val;
|
|
}
|
|
|
|
public static float Root(float value, float index) => (float)Math.Exp(Math.Log(value) / index);
|
|
|
|
public static float Round(float num) => num % 1 >= 0.5 ? Ceiling(num) : Floor(num);
|
|
public static float Round(float num, float nearest) => nearest * Round(num / nearest);
|
|
public static int RoundInt(float num) => (int)Round(num);
|
|
|
|
public static float Sec(Angle angle) => Sec(angle.Radians);
|
|
public static float Sec(float radians) => 1 / Cos(radians);
|
|
|
|
public static float Sin(Angle angle) => Sin(angle.Radians);
|
|
public static float Sin(float radians)
|
|
{
|
|
// Really close polynomial to sin(x) (when modded by 2pi). RMSE of 0.000003833
|
|
const float a = 0.000013028f,
|
|
b = 0.999677f,
|
|
c = 0.00174164f,
|
|
d = -0.170587f,
|
|
e = 0.0046494f,
|
|
f = 0.00508955f,
|
|
g = 0.00140205f,
|
|
h = -0.000577413f,
|
|
i = 0.0000613134f,
|
|
j = -0.00000216852f;
|
|
float x = AbsoluteMod(radians, Constants.Tau);
|
|
|
|
return
|
|
a + (b * x) + (c * x * x) + (d * x * x * x) + (e * x * x * x * x) + (f * x * x * x * x * x)
|
|
+ (g * x * x * x * x * x * x) + (h * x * x * x * x * x * x * x) + (i * x * x * x * x * x * x * x * x)
|
|
+ (j * x * x * x * x * x * x * x * x * x);
|
|
}
|
|
|
|
public static float Sqrt(float value) => Root(value, 2);
|
|
|
|
public static float Subtract(float num, params float[] vals) => num - Sum(vals);
|
|
public static int Subtract(int num, params int[] vals) => num - Sum(vals);
|
|
|
|
public static float Sum(params float[] vals)
|
|
{
|
|
float val = 0;
|
|
foreach (float d in vals) val += d;
|
|
return val;
|
|
}
|
|
public static int Sum(params int[] vals)
|
|
{
|
|
int val = 0;
|
|
foreach (int i in vals) val += i;
|
|
return val;
|
|
}
|
|
public static float Sum(Equation equ, float min, float max, float step = 1)
|
|
{
|
|
float total = 0;
|
|
for (float f = min; f <= max; f += step) total += equ(f);
|
|
return total;
|
|
}
|
|
|
|
// Known as stdev
|
|
public static float StandardDeviation(params float[] vals) => Sqrt(Variance(vals));
|
|
|
|
public static float Tan(Angle angle) => Tan(angle.Radians);
|
|
public static float Tan(float radians) => Sin(radians) / Cos(radians);
|
|
|
|
public static T[] UniqueItems<T>(params T[] vals) where T : IEquatable<T>
|
|
{
|
|
List<T> unique = new();
|
|
foreach (T item in vals) if (!unique.Any(x => x.Equals(item))) unique.Add(item);
|
|
return unique.ToArray();
|
|
}
|
|
|
|
public static float Variance(params float[] vals)
|
|
{
|
|
float mean = Average(vals), sum = 0;
|
|
for (int i = 0; i < vals.Length; i++)
|
|
{
|
|
float val = vals[i] - mean;
|
|
sum += val * val;
|
|
}
|
|
return sum / (vals.Length - 1);
|
|
}
|
|
|
|
public static float ZScore(float val, params float[] vals) => ZScore(val, Average(vals), StandardDeviation(vals));
|
|
public static float ZScore(float val, float mean, float stdev) => (val - mean) / stdev;
|
|
}
|