Releasing Nerd_STF version 2.4.0. #88

Merged
That-One-Nerd merged 16 commits from canary into main 2023-07-10 00:25:18 -04:00
6 changed files with 444 additions and 9 deletions
Showing only changes of commit c05bd6007d - Show all commits

View File

@ -17,6 +17,12 @@
+ ArcSec(Equation)
+ ArcSin(Equation)
+ ArcTan(Equation)
+ ArcCosh(Equation)
+ ArcCoth(Equation)
+ ArcCsch(Equation)
+ ArcSech(Equation)
+ ArcSinh(Equation)
+ ArcTanh(Equation)
+ Average(Equation, float, float, float)
+ Average(Equation, Equation, Equation, float)
+ Binomial(Equation, int, float)
@ -28,14 +34,18 @@
+ Combinations(Equation, int)
+ Combinations(Equation, Equation)
+ Cos(Equation)
+ Cosh(Equation)
+ Cot(Equation)
+ Coth(Equation)
+ Csc(Equation)
+ Csch(Equation)
+ Divide(Equation, float[])
+ Divide(Equation, Equation[])
+ Factorial(Equation)
+ Floor(Equation)
+ GetValues(Equation, float, float, float)
+ InverseSqrt(Equation)
+ Log(Equation, float)
+ Max(Equation, float, float, float)
+ Min(Equation, float, float, float)
+ Permutations(Equation, int)
@ -48,7 +58,9 @@
+ Root(Equation, Equation)
+ Round(Equation)
+ Sec(Equation)
+ Sech(Equation)
+ Sin(Equation)
+ Sinh(Equation)
+ SolveBisection(Equation, float, float, float, float, int)
+ SolveEquation(Equation, float, float, float, int)
+ SolveNewton(Equation, float, float, float, int)
@ -58,6 +70,7 @@
+ Sum(Equation, float[])
+ Sum(Equation, Equation[])
+ Tan(Equation)
+ Tanh(Equation)
+ ZScore(Equation, float[])
+ ZScore(Equation, Equation[])
+ ZScore(Equation, float, float)
@ -65,8 +78,10 @@
+ InvokeMethod(Equation, MethodInfo, object?[]?)
+ InvokeMathMethod(Equation, string, object?[]?)
+ Helpers
+ CordicHelper
+ MathfHelper
+ RationalHelper
+ UnsafeHelper
* Mathematics
* Abstract
= Renamed `IPresets1D<T>` to `IPresets1d<T>`
@ -144,18 +159,34 @@
- operator >=(Int4, Int4)
- operator <=(Int4, Int4)
* Mathf
+ ArcCosh(float)
+ ArcCoth(float)
+ ArcCsch(float)
+ ArcSech(float)
+ ArcSinh(float)
+ ArcTanh(float)
+ ArcTanh2(float, float)
+ Cbrt(float)
+ Cosh(float)
+ Coth(float)
+ Csch(float)
+ IsPrime(int, PrimeCheckMethod)
+ Lerp(float, float, Equation, bool)
+ Lerp(Equation, Equation, float, bool)
+ Lerp(Equation, Equation, Equation, bool)
+ Log(float, float)
+ PrimeFactors(int)
+ PowerMod(long, long, long)
+ Sech(float)
+ Sinh(float)
+ SharedItems<T>(T[][])
+ SolveBisection(Equation, float, float, float, float, int)
+ SolveEquation(Equation, float, float, float, int)
+ SolveNewton(Equation, float, float, float, int)
= Improved the `Sqrt` method by using a solution finder.
+ Tanh(float)
= Improved the `Sqrt(float)` method by using a solution finder
= The `ArcSin(float)` method now uses a solution finder rather than the base math library
= The `Power(float, float)` method now utilizes a custom CORDIC implementation rather than the base math library
+ PrimeCheckMethod
+ Equation2d
+ Rational
@ -174,4 +205,5 @@
= Renamed `Modifier2D` to `IModifier2d`
= Renamed `Modifier2D<T>` to `IModifier2d<T>`
= Renamed `Modifier2D<IT, VT>` to `IModifier2d<IT, VT>`
= Made `Nerd_STF` allow unsafe code blocks
```

View File

@ -27,6 +27,13 @@ public static class EquationExtension
public static Equation ArcSin(this Equation equ) => x => Mathf.ArcSin(equ(x)).Radians;
public static Equation ArcTan(this Equation equ) => x => Mathf.ArcTan(equ(x)).Radians;
public static Equation ArcCosh(this Equation equ) => x => Mathf.ArcCosh(equ(x));
public static Equation ArcCoth(this Equation equ) => x => Mathf.ArcCoth(equ(x));
public static Equation ArcCsch(this Equation equ) => x => Mathf.ArcCsch(equ(x));
public static Equation ArcSech(this Equation equ) => x => Mathf.ArcSech(equ(x));
public static Equation ArcSinh(this Equation equ) => x => Mathf.ArcSinh(equ(x));
public static Equation ArcTanh(this Equation equ) => x => Mathf.ArcTanh(equ(x));
public static float Average(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
Mathf.Average(equ, min, max, step);
public static Equation Average(this Equation equ, Equation min, Equation max, float step = Calculus.DefaultStep) =>
@ -54,6 +61,10 @@ public static class EquationExtension
public static Equation Cot(this Equation equ) => x => Mathf.Cot(equ(x));
public static Equation Csc(this Equation equ) => x => Mathf.Csc(equ(x));
public static Equation Cosh(this Equation equ) => x => Mathf.Cosh(equ(x));
public static Equation Coth(this Equation equ) => x => Mathf.Coth(equ(x));
public static Equation Csch(this Equation equ) => x => Mathf.Csch(equ(x));
public static Equation Divide(this Equation equ, params float[] dividends) =>
x => Mathf.Divide(equ(x), dividends);
public static Equation Divide(this Equation equ, params Equation[] dividends) => delegate (float x)
@ -72,6 +83,8 @@ public static class EquationExtension
public static Equation InverseSqrt(this Equation equ) => x => Mathf.InverseSqrt(equ(x));
public static Equation Log(this Equation equ, float @base) => x => Mathf.Log(@base, equ(x));
public static float Max(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
Mathf.Max(equ, min, max, step);
public static float Min(this Equation equ, float min, float max, float step = Calculus.DefaultStep) =>
@ -108,6 +121,9 @@ public static class EquationExtension
public static Equation Sec(this Equation equ) => x => Mathf.Sec(equ(x));
public static Equation Sin(this Equation equ) => x => Mathf.Sin(equ(x));
public static Equation Sech(this Equation equ) => x => Mathf.Sech(equ(x));
public static Equation Sinh(this Equation equ) => x => Mathf.Sinh(equ(x));
public static float SolveBisection(this Equation equ, float initialA, float initialB, float tolerance = 1e-5f,
int maxIterations = 1000) =>
Mathf.SolveBisection(equ, initialA, initialB, tolerance, maxIterations);
@ -146,6 +162,8 @@ public static class EquationExtension
public static Equation Tan(this Equation equ) => x => Mathf.Tan(equ(x));
public static Equation Tanh(this Equation equ) => x => Mathf.Tanh(equ(x));
public static Equation ZScore(this Equation equ, params float[] vals) => x => Mathf.ZScore(equ(x), vals);
public static Equation ZScore(this Equation equ, params Equation[] vals) => delegate (float x)
{

View File

@ -0,0 +1,317 @@
namespace Nerd_STF.Helpers;
// TODO: Make this internal
// Putting this in here for future reference:
// CORDIC is basically just splitting up an
// operation into more smaller operations.
// For example, turning sin(5rad) into
// sin(4rad + 1rad), which can be turned into
// the formula cos(4rad)cos(1rad) - sin(4rad)sin(1rad),
// to which we know the values of each part.
// Then we just do this iteratively on a bunch
// of powers of 2.
public static class CordicHelper
{
private static readonly float[] p_cosTable =
{
0.540302305868f, // cos(2^0)
0.87758256189f, // cos(2^-1)
0.968912421711f, // cos(2^-2)
0.992197667229f, // cos(2^-3)
0.9980475107f, // cos(2^-4)
0.999511758485f, // cos(2^-5)
0.999877932171f, // cos(2^-6)
0.999969482577f, // cos(2^-7)
0.999992370615f, // cos(2^-8)
0.999998092652f, // cos(2^-9)
0.999999523163f, // cos(2^-10)
0.999999880791f, // cos(2^-11)
0.999999970198f, // cos(2^-12)
0.999999992549f, // cos(2^-13)
0.999999998137f, // cos(2^-14)
0.999999999534f // cos(2^-15)
};
private static readonly float[] p_sinTable =
{
0.841470984808f, // sin(2^0)
0.479425538604f, // sin(2^-1)
0.247403959255f, // sin(2^-2)
0.124674733385f, // sin(2^-3)
0.0624593178424f, // sin(2^-4)
0.0312449139853f, // sin(2^-5)
0.0156243642249f, // sin(2^-6)
0.00781242052738f, // sin(2^-7)
0.0039062400659f, // sin(2^-8)
0.00195312375824f, // sin(2^-9)
0.00097656234478f, // sin(2^-10)
0.000488281230597f, // sin(2^-11)
0.000244140622575f, // sin(2^-12)
0.000122070312197f, // sin(2^-13)
0.0000610351562121f, // sin(2^-14)
0.0000305175781203f // sin(2^-15)
};
private static readonly float[] p_coshTable =
{
1.54308063482f, // cosh(2^0)
1.12762596521f, // cosh(2^-1)
1.03141309988f, // cosh(2^-2)
1.00782267783f, // cosh(2^-3)
1.00195376087f, // cosh(2^-4)
1.00048832099f, // cosh(2^-5)
1.0001220728f, // cosh(2^-6)
1.00003051773f, // cosh(2^-7)
1.0000076294f, // cosh(2^-8)
1.00000190735f, // cosh(2^-9)
1.00000047684f, // cosh(2^-10)
1.00000011921f, // cosh(2^-11)
1.0000000298f, // cosh(2^-12)
1.00000000745f, // cosh(2^-13)
1.00000000186f, // cosh(2^-14)
1.00000000047f, // cosh(2^-15)
};
private static readonly float[] p_sinhTable =
{
1.17520119364f, // sinh(2^0)
0.521095305494f, // sinh(2^-1)
0.252612316808f, // sinh(2^-2)
0.125325775241f, // sinh(2^-3)
0.0625406980522f, // sinh(2^-4)
0.0312550865114f, // sinh(2^-5)
0.0156256357906f, // sinh(2^-6)
0.0078125794731f, // sinh(2^-7)
0.00390625993412f, // sinh(2^-8)
0.00195312624176f, // sinh(2^-9)
0.00097656265522f, // sinh(2^-10)
0.000488281269403f, // sinh(2^-11)
0.000244140627425f, // sinh(2^-12)
0.000122070312803f, // sinh(2^-13)
0.0000610351562879f, // sinh(2^-14)
0.0000305175781297f, // sinh(2^-15)
};
private static readonly Dictionary<(float bas, int depth), float[]> p_expTables;
static CordicHelper()
{
p_expTables = new();
}
// This was originally intended to replace the Mathf.Cos
// and Mathf.Sin functions, but it ended up being considerably
// slower. In the future if it gets optimized, I might then
// choose to replace it.
// REMEMBER: When implementing, remember to use Mathf.AbsoluteMod,
// because that's what I was intending when I wrote this.
public static (float sin, float cos) CalculateTrig(float x, int iterations)
{
float approximateX = 0,
approximateCos = 1,
approximateSin = 0;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
// We need to find the biggest step that'll move us
// closer to the real X (without overshooting).
float diffX = x - approximateX;
// This is assuming that cosTable and sinTable
// have the same length.
for (int j = 0; j < p_cosTable.Length; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
if (diffX >= incX)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Get the sin and cos values for this power of two.
float valCos = p_cosTable[j],
valSin = p_sinTable[j];
// Do the products.
float newCos = approximateCos * valCos - approximateSin * valSin,
newSin = approximateCos * valSin + approximateSin * valCos;
// Apply differences
approximateX += incX;
approximateCos = newCos;
approximateSin = newSin;
break;
}
}
}
// Sin and cos should be pretty accurate by now,
// so we can return them.
return (approximateSin, approximateCos);
}
public static (float sinh, float cosh) CalculateHyperTrig(float x, int iterations)
{
float approximateX = 0,
approximateCosh = 1,
approximateSinh = 0;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
// We need to find the biggest step that'll move us
// closer to the real X (without overshooting).
float diffX = x - approximateX;
// This is assuming that cosTable and sinTable
// have the same length.
for (int j = 0; j < p_coshTable.Length; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
if (diffX >= incX)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Get the sin and cos values for this power of two.
float valCosh = p_coshTable[j],
valSinh = p_sinhTable[j];
// Do the products.
float newCosh = approximateCosh * valCosh + approximateSinh * valSinh,
newSinh = approximateCosh * valSinh + approximateSinh * valCosh;
// Apply differences
approximateX += incX;
approximateCosh = newCosh;
approximateSinh = newSinh;
break;
}
}
}
// Sin and cos should be pretty accurate by now,
// so we can return them.
return (approximateSinh, approximateCosh);
}
public static float ExpAnyBase(float bas, float pow, int tableDepth, int iterations)
{
// We need to auto-generate a table of values for this number the user enters.
float[] table;
if (p_expTables.ContainsKey((bas, tableDepth)))
{
// Table was already generated, so we can reuse it.
table = p_expTables[(bas, tableDepth)];
}
else
{
// Calculate a table for the CORDIC system by
// applying sequential square roots.
table = new float[tableDepth];
table[0] = bas;
for (int i = 1; i < tableDepth; i++) table[i] = Mathf.Sqrt(table[i - 1]);
p_expTables.Add((bas, tableDepth), table);
}
// Now we can perform the CORDIC method.
float approximateX = 0, approximateVal = 1;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
// We need to find the biggest step that'll move us
// closer to the real X (without overshooting).
float diffX = pow - approximateX;
for (int j = 0; j < tableDepth; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
if (diffX >= incX)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Get the power value for this power of two.
float val = table[j];
// Apply our value.
approximateX += incX;
approximateVal *= val;
break;
}
}
}
// Value should be pretty accurate by now,
// so we can return it.
return approximateVal;
}
public static float LogAnyBase(float bas, float val, int tableDepth, int iterations)
{
// We need to auto-generate a table of values for this number the user enters.
// However, we can use the already existing exponent tables and just swap the
// indexes and the values.
float[] table;
if (p_expTables.ContainsKey((bas, tableDepth)))
{
// Table was already generated, so we can reuse it.
table = p_expTables[(bas, tableDepth)];
}
else
{
// Calculate a table for the CORDIC system by
// applying sequential square roots.
table = new float[tableDepth];
table[0] = bas;
for (int i = 1; i < tableDepth; i++) table[i] = Mathf.Sqrt(table[i - 1]);
p_expTables.Add((bas, tableDepth), table);
}
// Now we can perform the CORDIC method.
float approximateX = 0, approximateVal = 1;
// Iterate continuously until it gets better.
for (int i = 0; i < iterations; i++)
{
float diffY = val / approximateVal;
for (int j = 0; j < table.Length; j++)
{
// The amount the difference will shrink.
float incX = FastGenExp2((sbyte)-j);
float newVal = table[j];
if (diffY >= newVal)
{
// Because here we go big to small, the first one that triggers
// this if statement should also be the biggest one that can.
// Apply our value.
approximateX += incX;
approximateVal *= newVal;
break;
}
}
}
// Value should be pretty accurate by now,
// so we can return it.
return approximateX;
}
// An extremely fast way to generate 2 to
// the power of p. I say "generate" because I'm
// just messing with the mantissa's data and
// not doing any real math.
private static float FastGenExp2(sbyte p)
{
int data = (((p - 1) ^ 0b10000000) << 23) & ~(1 << 31);
return UnsafeHelper.SwapType<int, float>(data);
}
}

View File

@ -0,0 +1,13 @@
namespace Nerd_STF.Helpers;
// These are all the unsafe functions I couldn't make safe. I don't want too much
// unsafe code, so this is where I put all of it that I require.
internal static unsafe class UnsafeHelper
{
// Forcefully change the type of an object
// without changing the data of the object.
public static NT SwapType<CT, NT>(CT obj)
where CT : unmanaged
where NT : unmanaged
=> *(NT*)&obj;
}

View File

@ -19,19 +19,28 @@ public static class Mathf
}
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 ArcSin(float value)
{
if (value > 1 || value < -1) throw new ArgumentOutOfRangeException(nameof(value));
return (SolveNewton(x => Sin(x) - value, 0), Angle.Type.Degrees);
}
public static Angle ArcTan(float value) => ArcSin(value / Sqrt(1 + value * value));
public static Angle ArcTan2(float a, float b) => ArcTan(a / b);
// I would've much rather used CORDIC for these inverses,
// but I can't think of an intuitive way to do it, so I'll
// hold off for now.
public static float ArcCosh(float value) => Log(Constants.E, value + Sqrt(value * value - 1));
public static float ArcCoth(float value) => Log(Constants.E, (1 + value) / (value - 1)) / 2;
public static float ArcCsch(float value) => Log(Constants.E, (1 + Sqrt(1 + value * value)) / value);
public static float ArcSech(float value) => Log(Constants.E, (1 + Sqrt(1 - value * value)) / value);
public static float ArcSinh(float value) => Log(Constants.E, value + Sqrt(value * value + 1));
public static float ArcTanh(float value) => Log(Constants.E, (1 + value) / (1 - value)) / 2;
public static float ArcTanh2(float a, float b) => ArcTanh(a / b);
public static float Average(Equation equ, float min, float max, float step = Calculus.DefaultStep)
{
List<float> vals = new();
@ -69,12 +78,23 @@ public static class Mathf
public static float Cos(Angle angle) => Cos(angle.Radians);
public static float Cos(float radians) => Sin(radians + Constants.HalfPi);
public static float Cosh(float value)
{
if (value == 0) return 1;
else if (value < 0) return Cosh(-value);
else return CordicHelper.CalculateHyperTrig(value, 16).cosh;
}
public static float Cot(Angle angle) => Cot(angle.Radians);
public static float Cot(float radians) => Cos(radians) / Sin(radians);
public static float Coth(float value) => 1 / Tanh(value);
public static float Csc(Angle angle) => Csc(angle.Radians);
public static float Csc(float radians) => 1 / Sin(radians);
public static float Csch(float value) => 1 / Sinh(value);
public static float Divide(float val, params float[] dividends) => val / Product(dividends);
public static int Divide(int val, params int[] dividends) => val / Product(dividends);
@ -161,6 +181,13 @@ public static class Mathf
public static Equation Lerp(Equation a, Equation b, Equation t, bool clamp = true) =>
x => Lerp(a(x), b(x), t(x), clamp);
public static float Log(float @base, float val)
{
if (val <= 0) throw new ArgumentOutOfRangeException(nameof(val));
else if (val < 1) return -Log(@base, 1 / val);
else return CordicHelper.LogAnyBase(@base, val, 16, 16);
}
public static Equation MakeEquation(Dictionary<float, float> vals) => delegate (float x)
{
if (vals.Count < 1) throw new UndefinedException();
@ -331,7 +358,12 @@ public static class Mathf
return total;
}
public static float Power(float num, float pow) => (float)Math.Pow(num, pow);
public static float Power(float num, float pow)
{
if (pow == 0) return 1;
else if (pow < 0) return 1 / Power(num, -pow);
else return CordicHelper.ExpAnyBase(num, pow, 16, 16);
}
public static float Power(float num, int pow)
{
if (pow <= 0) return 0;
@ -377,6 +409,8 @@ public static class Mathf
public static float Sec(Angle angle) => Sec(angle.Radians);
public static float Sec(float radians) => 1 / Cos(radians);
public static float Sech(float value) => 1 / Cosh(value);
public static T[] SharedItems<T>(params T[][] arrays) where T : IEquatable<T>
{
if (arrays.Length < 1) return Array.Empty<T>();
@ -409,6 +443,13 @@ public static class Mathf
+ (j * x * x * x * x * x * x * x * x * x);
}
public static float Sinh(float value)
{
if (value == 0) return 0;
else if (value < 0) return -Sinh(-value);
else return CordicHelper.CalculateHyperTrig(value, 16).sinh;
}
public static float SolveBisection(Equation equ, float initialA, float initialB, float tolerance = 1e-5f,
int maxIterations = 1000)
{
@ -502,6 +543,19 @@ public static class Mathf
public static float Tan(Angle angle) => Tan(angle.Radians);
public static float Tan(float radians) => Sin(radians) / Cos(radians);
public static float Tanh(float value)
{
float cosh, sinh;
if (value < 0)
{
(cosh, sinh) = CordicHelper.CalculateHyperTrig(-value, 16);
sinh = -sinh;
}
else (cosh, sinh) = CordicHelper.CalculateHyperTrig(value, 16);
return cosh / sinh;
}
public static T[] UniqueItems<T>(params T[] vals) where T : IEquatable<T>
{
List<T> unique = new();

View File

@ -40,6 +40,7 @@ Anyway, that's it for this update. The longest delay was just getting this proje
<EnforceCodeStyleInBuild>False</EnforceCodeStyleInBuild>
<AssemblyOriginatorKeyFile>C:\Users\kyley\Desktop\Misc Items\Private Keys\SNA\Nerd_STF.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">