Compare commits

..

4 Commits

14 changed files with 1142 additions and 62 deletions

View File

@ -2,7 +2,7 @@
namespace Nerd_STF.Helpers
{
public static class CordicHelper
internal static class CordicHelper
{
// Starts at 4 radians. Each index downwards is half that.
// Goes from 2^2 to 2^-19.

View File

@ -1,9 +1,32 @@
using System;
using System.Runtime.CompilerServices;
namespace Nerd_STF.Helpers
{
internal static class TargetHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsFinite(double d)
{
#if CS11_OR_GREATER
return double.IsFinite(d);
#else
long bits = BitConverter.DoubleToInt64Bits(d);
return (bits & 0x7FFFFFFFFFFFFFFF) < 0x7FF0000000000000;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInfinity(double d)
{
#if CS11_OR_GREATER
return double.IsInfinity(d);
#else
long bits = BitConverter.DoubleToInt64Bits(d);
return (bits & 0x7FFFFFFFFFFFFFFF) == 0x7FF0000000000000;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLine(string content)
{
#if NETSTANDARD1_1
@ -12,6 +35,7 @@ namespace Nerd_STF.Helpers
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] EmptyArray<T>()
{
#if NETSTANDARD1_1

View File

@ -1,5 +1,6 @@
using Nerd_STF.Mathematics;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections.Generic;
using System.Text;
@ -110,5 +111,44 @@ namespace Nerd_STF.Helpers
}
return total.ToString();
}
private static readonly string dimNumSymbols = " ijk?";
#if CS8_OR_GREATER
public static string HighDimNumberToString(IEnumerable<double> terms, string? format, IFormatProvider? provider)
#else
public static string HighDimNumberToString(IEnumerable<double> terms, string format, IFormatProvider provider)
#endif
{
StringBuilder builder = new StringBuilder();
int index = 0;
bool first = true;
foreach (double term in terms)
{
if (term == 0)
{
index++;
continue;
}
if (first) builder.Append(term.ToString(format, provider));
else
{
if (term > 0)
{
builder.Append(" + ");
builder.Append(term.ToString(format, provider));
}
else
{
builder.Append(" - ");
builder.Append((-term).ToString(format, provider));
}
}
if (index > 0) builder.Append(dimNumSymbols[MathE.Min(index, dimNumSymbols.Length)]);
first = false;
index++;
}
if (first) builder.Append(0.0.ToString(format, provider));
return builder.ToString();
}
}
}

View File

@ -9,7 +9,6 @@ namespace Nerd_STF.Mathematics.Algebra
double Magnitude { get; }
static abstract TSelf ClampMagnitude(TSelf val, double minMag, double maxMag);
static abstract void ClampMagnitude(ref TSelf val, double minMag, double maxMag);
static abstract double Dot(TSelf a, TSelf b);
static abstract double Dot(IEnumerable<TSelf> vals);
}

View File

@ -286,6 +286,7 @@ namespace Nerd_STF.Mathematics
public static bool operator ==(Float2 a, Float2 b) => a.Equals(b);
public static bool operator !=(Float2 a, Float2 b) => !a.Equals(b);
public static explicit operator Float2(Complex complex) => new Float2(complex.Real, complex.Imaginary);
public static explicit operator Float2(Float3 floats) => new Float2(floats.x, floats.y);
public static explicit operator Float2(Float4 floats) => new Float2(floats.x, floats.y);
public static implicit operator Float2(Int2 ints) => new Float2(ints.x, ints.y);

View File

@ -343,6 +343,7 @@ namespace Nerd_STF.Mathematics
public static implicit operator Float4(Int4 ints) => new Float4(ints.w, ints.x, ints.y, ints.z);
public static implicit operator Float4(Float2 floats) => new Float4(0, floats.x, floats.y, 0);
public static implicit operator Float4(Float3 floats) => new Float4(0, floats.x, floats.y, floats.z);
public static implicit operator Float4(Numbers.Quaternion quat) => new Float4(quat.w, quat.x, quat.y, quat.z);
public static implicit operator Float4(Vector2 vec) => new Float4(0, vec.X, vec.Y, 0);
public static implicit operator Float4(Vector3 vec) => new Float4(0, vec.X, vec.Y, vec.Z);
public static implicit operator Float4(Vector4 vec) => new Float4(vec.W, vec.X, vec.Y, vec.Z);

View File

@ -4,8 +4,9 @@ namespace Nerd_STF.Mathematics
public interface IPresets4d<TSelf> : IPresets3d<TSelf>
where TSelf : IPresets4d<TSelf>
{
static abstract TSelf LowW { get; }
static abstract TSelf HighW { get; }
// TODO: The HighW and LowW vectors could also be called "ana" and "kata."
static abstract TSelf LowW { get; } // Kata
static abstract TSelf HighW { get; } // Ana
}
}
#endif

View File

@ -1,5 +1,6 @@
using Nerd_STF.Exceptions;
using Nerd_STF.Mathematics.Algebra;
using Nerd_STF.Mathematics.Numbers;
using System;
using System.Collections;
using System.Collections.Generic;
@ -259,6 +260,7 @@ namespace Nerd_STF.Mathematics
public static bool operator ==(Int2 a, Int2 b) => a.Equals(b);
public static bool operator !=(Int2 a, Int2 b) => !a.Equals(b);
public static explicit operator Int2(Complex complex) => new Int2((int)complex.r, (int)complex.i);
public static explicit operator Int2(Float2 floats) => new Int2((int)floats.x, (int)floats.y);
public static explicit operator Int2(Float3 floats) => new Int2((int)floats.x, (int)floats.y);
public static explicit operator Int2(Float4 floats) => new Int2((int)floats.x, (int)floats.y);

View File

@ -300,6 +300,7 @@ namespace Nerd_STF.Mathematics
public static explicit operator Int4(Float2 floats) => new Int4(0, (int)floats.x, (int)floats.y, 0);
public static explicit operator Int4(Float3 floats) => new Int4(0, (int)floats.x, (int)floats.y, (int)floats.z);
public static explicit operator Int4(Float4 floats) => new Int4((int)floats.w, (int)floats.x, (int)floats.y, (int)floats.z);
public static explicit operator Int4(Numbers.Quaternion quat) => new Int4((int)quat.w, (int)quat.x, (int)quat.y, (int)quat.z);
public static explicit operator Int4(ListTuple<double> tuple) => new Int4((int)tuple[0], (int)tuple[1], (int)tuple[2], (int)tuple[3]);
public static implicit operator Int4(ListTuple<int> tuple) => new Int4(tuple[0], tuple[1], tuple[2], tuple[3]);
public static implicit operator Int4((int, int, int, int) tuple) => new Int4(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);

View File

@ -680,26 +680,38 @@ namespace Nerd_STF.Mathematics
return flip ? result : -result;
}
public static double Sin(Angle angle, int terms = 8) =>
Sin(angle.Radians, terms);
public static IEquation Sin(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Sin(inputRad[x], terms));
public static double Cos(double rad, int terms = 8) =>
Sin(rad + Constants.HalfPi, terms);
public static double Cos(Angle angle, int terms = 8) =>
Cos(angle.Radians, terms);
public static IEquation Cos(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Cos(inputRad[x], terms));
public static double Tan(double rad, int terms = 8) =>
Sin(rad + Constants.HalfPi, terms) / Sin(rad, terms);
public static double Tan(Angle angle, int terms = 8) =>
Tan(angle.Radians, terms);
public static IEquation Tan(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Tan(inputRad[x], terms));
public static double Csc(double rad, int terms = 8) =>
1 / Sin(rad, terms);
public static double Csc(Angle angle, int terms = 8) =>
Csc(angle.Radians, terms);
public static IEquation Csc(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Csc(inputRad[x], terms));
public static double Sec(double rad, int terms = 8) =>
1 / Sin(rad + Constants.HalfPi, terms);
public static double Sec(Angle angle, int terms = 8) =>
Sec(angle.Radians, terms);
public static IEquation Sec(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Sec(inputRad[x], terms));
public static double Cot(double rad, int terms = 8) =>
Sin(rad, terms) / Sin(rad + Constants.HalfPi, terms);
public static double Cot(Angle angle, int terms = 8) =>
Cot(angle.Radians, terms);
public static IEquation Cot(IEquation inputRad, int terms = 8) =>
new Equation((double x) => Cot(inputRad[x], terms));

View File

@ -0,0 +1,511 @@
using Nerd_STF.Exceptions;
using Nerd_STF.Helpers;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
namespace Nerd_STF.Mathematics.Numbers
{
public struct Complex : IComparable<Complex>,
IEquatable<Complex>,
IFormattable,
INumberGroup<Complex, double>
#if CS11_OR_GREATER
,INumber<Complex>,
IFromTuple<Complex, (double, double)>,
IInterpolable<Complex>,
IPresets2d<Complex>,
IRoundable<Complex>,
ISimpleMathOperations<Complex>,
ISplittable<Complex, (double[] reals, double[] imaginaries)>,
IVectorOperations<Complex>
#endif
{
public static Complex Down => new Complex(0, 1);
public static Complex Left => new Complex(-1, 0);
public static Complex Right => new Complex(1, 0);
public static Complex Up => new Complex(0, -1);
public static Complex One => new Complex(1, 0);
public static Complex Zero => new Complex(0, 0);
public static Complex NaN => new Complex(double.NaN, double.NaN);
public Complex Conjugate => new Complex(r, -i);
public double Magnitude => MathE.Sqrt(r * r + i * i);
public double MagnitudeSqr => r * r + i * i;
public double Real
{
get => r;
set => r = value;
}
public double Imaginary
{
get => i;
set => i = value;
}
public double r, i;
public Complex(double real, double imaginary)
{
r = real;
i = imaginary;
}
public double this[int index]
{
get
{
switch (index)
{
case 0: return r;
case 1: return i;
default: throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0: r = value; break;
case 1: i = value; break;
default: throw new ArgumentOutOfRangeException(nameof(index));
}
}
}
public ListTuple<double> this[string key]
{
get
{
double[] items = new double[key.Length];
for (int i = 0; i < key.Length; i++)
{
char c = key[i];
switch (c)
{
case 'r': items[i] = r; break;
case 'i': items[i] = this.i; break;
default: throw new ArgumentException("Invalid key.", nameof(key));
}
}
return new ListTuple<double>(items);
}
set
{
IEnumerator<double> stepper = value.GetEnumerator();
for (int i = 0; i < key.Length; i++)
{
char c = key[i];
stepper.MoveNext();
switch (c)
{
case 'r': r = stepper.Current; break;
case 'i': this.i = stepper.Current; break;
default: throw new ArgumentException("Invalid key.", nameof(key));
}
}
}
}
#if CS8_OR_GREATER
public static Complex Parse(string? str) =>
#else
public static Complex Parse(string str) =>
#endif
str is null ? NaN : Parse(str.AsSpan());
public static Complex Parse(ReadOnlySpan<char> str)
{
if (TryParse(str, out Complex result)) return result;
else throw new FormatException("Cannot parse complex number from input.");
}
#if CS8_OR_GREATER
public static bool TryParse(string? str, out Complex frac) =>
#else
public static bool TryParse(string str, out Complex frac) =>
#endif
TryParse(str.AsSpan(), out frac);
public static bool TryParse(ReadOnlySpan<char> str, out Complex num)
{
if (str.Length == 0)
{
num = NaN;
return false;
}
str = str.Trim();
int signFirst, signSecond;
if (str.StartsWith("+".AsSpan()))
{
signFirst = 1;
str = str.Slice(1);
}
else if (str.StartsWith("-".AsSpan()))
{
signFirst = -1;
str = str.Slice(1);
}
else signFirst = 1;
ReadOnlySpan<char> first, second;
int splitIndex = str.IndexOf('+');
if (splitIndex == -1) splitIndex = str.IndexOf('-');
if (splitIndex != -1)
{
first = str;
second = ReadOnlySpan<char>.Empty;
}
else
{
first = str.Slice(0, splitIndex);
second = str.Slice(splitIndex);
}
first = first.Trim();
second = second.Trim();
if (second.StartsWith("+".AsSpan()))
{
signSecond = 1;
second = second.Slice(1).Trim();
}
else if (str.StartsWith("-".AsSpan()))
{
signSecond = -1;
second = second.Slice(1).Trim();
}
else signSecond = 1;
bool firstIsImag;
if (first.EndsWith("i".AsSpan()))
{
firstIsImag = true;
first = first.Slice(0, first.Length - 1).Trim();
}
else if (second.EndsWith("i".AsSpan()))
{
firstIsImag = false;
second = first.Slice(0, second.Length - 1).Trim();
}
else
{
num = NaN;
return false;
}
double firstNum = ParseHelper.ParseDouble(first) * signFirst,
secondNum = ParseHelper.ParseDouble(second) * signSecond;
if (firstIsImag) num = new Complex(secondNum, firstNum);
else num = new Complex(firstNum, secondNum);
return true;
}
public static Complex Abs(Complex num) => new Complex(num.Magnitude, 0);
public static Complex Ceiling(Complex num) =>
new Complex(MathE.Ceiling(num.r),
MathE.Ceiling(num.i));
public static Complex Clamp(Complex num, Complex min, Complex max) =>
new Complex(MathE.Clamp(num.r, min.r, max.r),
MathE.Clamp(num.i, min.i, max.i));
public static Complex ClampMagnitude(Complex num, double minMag, double maxMag)
{
Complex copy = num;
ClampMagnitude(ref copy, minMag, maxMag);
return copy;
}
public static void ClampMagnitude(ref Complex num, double minMag, double maxMag)
{
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
double mag = num.Magnitude;
if (mag < minMag)
{
double factor = minMag / mag;
num.r *= factor;
num.i *= factor;
}
else if (mag > maxMag)
{
double factor = maxMag / mag;
num.r *= factor;
num.i *= factor;
}
}
public static double Dot(Complex a, Complex b) => a.r * b.r + a.i * b.i;
public static double Dot(IEnumerable<Complex> values)
{
double r = 1, i = 1;
foreach (Complex val in values)
{
r *= val.r;
i *= val.i;
}
return r + i;
}
public static Complex Floor(Complex num) =>
new Complex(MathE.Floor(num.r),
MathE.Floor(num.i));
public static Complex Lerp(Complex a, Complex b, double t, bool clamp = true) =>
new Complex(MathE.Lerp(a.r, b.r, t, clamp),
MathE.Lerp(a.i, b.i, t, clamp));
public static Complex Product(IEnumerable<Complex> vals)
{
bool any = false;
Complex result = One;
foreach (Complex val in vals)
{
any = true;
result *= val;
}
return any ? result : Zero;
}
public static Complex Round(Complex val) =>
new Complex(MathE.Round(val.r),
MathE.Round(val.i));
public static Complex Sum(IEnumerable<Complex> vals)
{
double resultR = 0;
double resultI = 0;
foreach (Complex val in vals)
{
resultR += val.r;
resultI += val.i;
}
return new Complex(resultR, resultI);
}
#if CS8_OR_GREATER
private static bool TryConvertFrom(object? value, out Complex result)
#else
private static bool TryConvertFrom(object value, out Complex result)
#endif
{
if (value is Complex vComplex) result = vComplex;
else if (value is Fraction vFrac) result = new Complex(vFrac.GetValue(), 0);
else if (value is Float2 vFloat2) result = new Complex(vFloat2.x, vFloat2.y);
else if (value is Vector2 vVector2) result = new Complex(vVector2.X, vVector2.Y);
else if (value is Int2 vInt2) result = new Complex(vInt2.x, vInt2.y);
else if (value is double vDouble) result = new Complex(vDouble, 0);
else if (value is float vSingle) result = new Complex(vSingle, 0);
#if NET5_0_OR_GREATER
else if (value is Half vHalf) result = new Complex((double)vHalf, 0);
#endif
else if (value is int vInt32) result = new Complex(vInt32, 0);
else
{
result = new Complex(0, 0);
return false;
}
return true;
}
public static bool IsEvenInteger(Complex value) => value.i == 0 && value.r % 2 == 0;
public static bool IsOddInteger(Complex value) => value.i == 0 && value.r % 2 == 1;
public static bool IsFinite(Complex value) => TargetHelper.IsFinite(value.r) &&
TargetHelper.IsFinite(value.i);
public static bool IsInfinity(Complex value) => TargetHelper.IsInfinity(value.r) ||
TargetHelper.IsInfinity(value.i);
public static bool IsInteger(Complex value) => value.i == 0 && value.r % 1 == 0;
public static bool IsNaN(Complex value)
{
// NaN never equals itself.
#pragma warning disable CS1718
return (value.r != value.r) ||
(value.i != value.i);
#pragma warning restore CS1718
}
public static bool IsNegative(Complex value) => value.r < 0;
public static bool IsNegativeInfinity(Complex value) => value.r == double.NegativeInfinity ||
value.i == double.NegativeInfinity;
public static bool IsNormal(Complex value) => false; // ??? uhh i think this is right
public static bool IsPositive(Complex value) => value.r > 0;
public static bool IsPositiveInfinity(Complex value) => value.r == double.PositiveInfinity ||
value.i == double.PositiveInfinity;
public static bool IsRealNumber(Complex value) => value.i == 0;
public static bool IsZero(Complex value) => value.r == 0 && value.i == 0;
public static Complex MaxMagnitude(Complex a, Complex b) => a.MagnitudeSqr > b.MagnitudeSqr ? a : b;
public static Complex MinMagnitude(Complex a, Complex b) => a.MagnitudeSqr < b.MagnitudeSqr ? a : b;
#if CS11_OR_GREATER
static Complex INumberBase<Complex>.MaxMagnitudeNumber(Complex a, Complex b) => MaxMagnitude(a, b);
static Complex INumberBase<Complex>.MinMagnitudeNumber(Complex a, Complex b) => MinMagnitude(a, b);
static bool INumberBase<Complex>.IsCanonical(Complex value) => true;
static bool INumberBase<Complex>.IsComplexNumber(Complex value) => value.i != 0;
static bool INumberBase<Complex>.IsImaginaryNumber(Complex value) => value.i != 0;
static bool INumberBase<Complex>.IsSubnormal(Complex value) => false; // What does this mean???
static Complex INumberBase<Complex>.Parse(string str, NumberStyles numStyles, IFormatProvider? provider) => Parse(str);
static Complex INumberBase<Complex>.Parse(ReadOnlySpan<char> str, NumberStyles numStyles, IFormatProvider? provider) => Parse(str);
static Complex IParsable<Complex>.Parse(string str, IFormatProvider? provider) => Parse(str);
static Complex ISpanParsable<Complex>.Parse(ReadOnlySpan<char> str, IFormatProvider? provider) => Parse(str);
static bool INumberBase<Complex>.TryParse(string? str, NumberStyles numStyles, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
static bool INumberBase<Complex>.TryParse(ReadOnlySpan<char> str, NumberStyles numStyles, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
static bool IParsable<Complex>.TryParse(string? str, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
static bool ISpanParsable<Complex>.TryParse(ReadOnlySpan<char> str, IFormatProvider? provider, out Complex num) => TryParse(str, out num);
static Complex IAdditiveIdentity<Complex, Complex>.AdditiveIdentity => Zero;
static Complex IMultiplicativeIdentity<Complex, Complex>.MultiplicativeIdentity => One;
static int INumberBase<Complex>.Radix => 2; // Not super sure what to put here.
private static bool TryConvertTo<T>(Complex num, out T result)
{
object? tempValue;
if (typeof(T) == typeof(Complex)) tempValue = num;
else if (typeof(T) == typeof(Fraction)) tempValue = Fraction.Approximate(num.r);
else if (typeof(T) == typeof(Float2)) tempValue = new Float2(num.r, num.i);
else if (typeof(T) == typeof(Vector2)) tempValue = new Vector2((float)num.r, (float)num.i);
else if (typeof(T) == typeof(Int2)) tempValue = new Int2((int)num.r, (int)num.i);
else if (typeof(T) == typeof(double)) tempValue = num.r;
else if (typeof(T) == typeof(float)) tempValue = (float)num.r;
#if NET5_0_OR_GREATER
else if (typeof(T) == typeof(Half)) tempValue = (Half)num.r;
#endif
else if (typeof(T) == typeof(int)) tempValue = (int)num.r;
else
{
result = default!;
return false;
}
result = (T)tempValue;
return true;
}
static bool INumberBase<Complex>.TryConvertFromChecked<TOther>(TOther value, out Complex result) => TryConvertFrom(value, out result);
static bool INumberBase<Complex>.TryConvertFromSaturating<TOther>(TOther value, out Complex result) => TryConvertFrom(value, out result);
static bool INumberBase<Complex>.TryConvertFromTruncating<TOther>(TOther value, out Complex result) => TryConvertFrom(value, out result);
static bool INumberBase<Complex>.TryConvertToChecked<TOther>(Complex value, out TOther result) => TryConvertTo(value, out result);
static bool INumberBase<Complex>.TryConvertToSaturating<TOther>(Complex value, out TOther result) => TryConvertTo(value, out result);
static bool INumberBase<Complex>.TryConvertToTruncating<TOther>(Complex value, out TOther result) => TryConvertTo(value, out result);
#endif
public static (double[] reals, double[] imaginaries) SplitArray(IEnumerable<Complex> vals)
{
int count = vals.Count();
double[] reals = new double[count], imaginaries = new double[count];
int index = 0;
foreach (Complex val in vals)
{
reals[index] = val.r;
imaginaries[index] = val.i;
index++;
}
return (reals, imaginaries);
}
public IEnumerator<double> GetEnumerator()
{
yield return r;
yield return i;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int CompareTo(double other) => MagnitudeSqr.CompareTo(MathE.Abs(other * other));
public int CompareTo(Complex other) => MagnitudeSqr.CompareTo(other.MagnitudeSqr);
#if CS8_OR_GREATER
public int CompareTo(object? other)
#else
public int CompareTo(object other)
#endif
{
if (other is null) return 1;
else if (other is Complex otherComplex) return CompareTo(otherComplex);
else if (other is double otherDouble) return CompareTo(otherDouble);
else if (TryConvertFrom(other, out Complex otherConvert)) return CompareTo(otherConvert);
else return 1;
}
public bool Equals(double other) => r == other && i == 0;
public bool Equals(Complex other) => r == other.r && i == other.i;
#if CS8_OR_GREATER
public override bool Equals(object? other)
#else
public override bool Equals(object other)
#endif
{
if (other is null) return false;
else if (other is Complex otherComplex) return Equals(otherComplex);
else if (TryConvertFrom(other, out Complex otherConvert)) return Equals(otherConvert);
else return false;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToStringHelper.HighDimNumberToString(this, null, null);
#if CS8_OR_GREATER
public string ToString(string? format) => ToStringHelper.HighDimNumberToString(this, format, null);
public string ToString(IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
#else
public string ToString(string format) => ToStringHelper.HighDimNumberToString(this, format, null);
public string ToString(IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
public string ToString(string format, IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
#endif
public double[] ToArray() => new double[] { r, i };
public Fill<double> ToFill()
{
Complex @this = this;
return i => @this[i];
}
public List<double> ToList() => new List<double>() { r, i };
#if CS11_OR_GREATER
public bool TryFormat(Span<char> dest, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
{
// Not really great, but I don't want to do this right now.
string result = ToString(format.ToString(), provider);
result.CopyTo(dest);
charsWritten = result.Length;
return true;
}
static Complex IIncrementOperators<Complex>.operator ++(Complex a) => new Complex(a.r + 1, a.i);
static Complex IDecrementOperators<Complex>.operator --(Complex a) => new Complex(a.r - 1, a.i);
#endif
public static Complex operator +(Complex a) => a;
public static Complex operator +(Complex a, Complex b) => new Complex(a.r + b.r, a.i + b.i);
public static Complex operator +(Complex a, double b) => new Complex(a.r + b, a.i);
public static Complex operator -(Complex a) => new Complex(-a.r, -a.i);
public static Complex operator -(Complex a, Complex b) => new Complex(a.r - b.r, a.i - b.i);
public static Complex operator -(Complex a, double b) => new Complex(a.r - b, a.i);
public static Complex operator *(Complex a, Complex b) => new Complex(a.r * b.r - a.i * b.i, a.r * b.i + a.i * b.r);
public static Complex operator *(Complex a, double b) => new Complex(a.r * b, a.i * b);
public static Complex operator /(Complex a, Complex b)
{
double scaleFactor = 1 / (b.r * b.r + b.i * b.i);
return new Complex((a.r * b.r + a.i * b.i) * scaleFactor,
(a.i * b.r - a.r * b.i) * scaleFactor);
}
public static Complex operator /(Complex a, double b) => new Complex(a.r / b, a.i / b);
public static Complex operator %(Complex a, Complex b)
{
// TODO: Maybe expand and inline this. Don't feel like it at the moment.
return a + b * Ceiling(-a / b);
}
public static Complex operator %(Complex a, double b) => a % new Complex(b, 0);
public static bool operator ==(Complex a, Complex b) => a.Equals(b);
public static bool operator !=(Complex a, Complex b) => !a.Equals(b);
public static bool operator >(Complex a, Complex b) => a.CompareTo(b) > 0;
public static bool operator <(Complex a, Complex b) => a.CompareTo(b) < 0;
public static bool operator >=(Complex a, Complex b) => a.CompareTo(b) >= 0;
public static bool operator <=(Complex a, Complex b) => a.CompareTo(b) <= 0;
public static implicit operator Complex(System.Numerics.Complex num) => new Complex(num.Real, num.Imaginary);
public static implicit operator Complex(Float2 group) => new Complex(group.x, group.y);
public static explicit operator Complex(Int2 group) => new Complex(group.x, group.y);
public static implicit operator Complex(ListTuple<double> tuple) => new Complex(tuple[0], tuple[1]);
public static explicit operator Complex(Quaternion quat) => new Complex(quat.w, quat.x);
public static implicit operator Complex(ValueTuple<double, double> tuple) => new Complex(tuple.Item1, tuple.Item2);
public static explicit operator Complex(Vector2 group) => new Complex(group.X, group.Y);
public static implicit operator System.Numerics.Complex(Complex num) => new Complex(num.r, num.i);
public static implicit operator ListTuple<double>(Complex num) => new ListTuple<double>(num.r, num.i);
public static implicit operator ValueTuple<double, double>(Complex num) => (num.r, num.i);
}
}

View File

@ -325,25 +325,13 @@ namespace Nerd_STF.Mathematics.Numbers
return false;
}
else if (value is Fraction valueFrac) result = valueFrac;
else if (value is Complex valueComp) result = Approximate(valueComp.r);
else if (value is double valueDouble) result = Approximate(valueDouble);
else if (value is float valueSingle) result = Approximate(valueSingle);
#if NET5_0_OR_GREATER
else if (value is Half valueHalf) result = Approximate((double)valueHalf);
#endif
#if NET7_0_OR_GREATER
else if (value is UInt128 valueUInt128) result = new Fraction((int)valueUInt128, 1);
else if (value is Int128 valueInt128) result = new Fraction((int)valueInt128, 1);
#endif
else if (value is ulong valueUInt64) result = new Fraction((int)valueUInt64, 1);
else if (value is long valueInt64) result = new Fraction((int)valueInt64, 1);
else if (value is uint valueUInt32) result = new Fraction((int)valueUInt32, 1);
else if (value is int valueInt32) result = new Fraction(valueInt32, 1);
else if (value is ushort valueUInt16) result = new Fraction(valueUInt16, 1);
else if (value is short valueInt16) result = new Fraction(valueInt16, 1);
else if (value is byte valueUInt8) result = new Fraction(valueUInt8, 1);
else if (value is sbyte valueInt8) result = new Fraction(valueInt8, 1);
else if (value is IntPtr valueInt) result = new Fraction((int)valueInt, 1);
else if (value is UIntPtr valueUInt) result = new Fraction((int)valueUInt, 1);
else
{
result = NaN;
@ -361,7 +349,7 @@ namespace Nerd_STF.Mathematics.Numbers
}
public static bool IsEvenInteger(Fraction val) =>
val.num % val.den == 0 && val.num / val.den % 2 == 0;
public static bool IsFinite(Fraction val) => val.den != 0 || val.num != 0;
public static bool IsFinite(Fraction val) => val.den != 0;
public static bool IsInfinity(Fraction val) => val.den == 0 && val.num != 0;
public static bool IsInteger(Fraction val) => val.num % val.den == 0;
public static bool IsNaN(Fraction val) => val.num == 0 && val.den == 0;
@ -375,13 +363,12 @@ namespace Nerd_STF.Mathematics.Numbers
public static bool IsRealNumber(Fraction val) => val.den != 0;
public static bool IsZero(Fraction val) => val.num == 0 && val.den != 0;
public static Fraction MaxMagnitude(Fraction a, Fraction b) => a > b ? a : b;
public static Fraction MaxMagnitudeNumber(Fraction a, Fraction b) => a > b ? a : b;
public static Fraction MinMagnitude(Fraction a, Fraction b) => a < b ? a : b;
public static Fraction MinMagnitudeNumber(Fraction a, Fraction b) => a < b ? a : b;
#if CS11_OR_GREATER
static bool INumberBase<Fraction>.IsComplexNumber(Fraction val) => false;
static bool INumberBase<Fraction>.IsImaginaryNumber(Fraction val) => false;
static bool INumberBase<Fraction>.IsSubnormal(Fraction val) => false; // What does this mean???
static Fraction INumberBase<Fraction>.Parse(string? str, NumberStyles style, IFormatProvider? provider) => Parse(str);
static Fraction INumberBase<Fraction>.Parse(ReadOnlySpan<char> str, NumberStyles style, IFormatProvider? provider) => Parse(str);
static bool INumberBase<Fraction>.TryParse(string? str, NumberStyles style, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
@ -390,32 +377,26 @@ namespace Nerd_STF.Mathematics.Numbers
static bool IParsable<Fraction>.TryParse(string? str, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
static Fraction ISpanParsable<Fraction>.Parse(ReadOnlySpan<char> str, IFormatProvider? provider) => Parse(str);
static bool ISpanParsable<Fraction>.TryParse(ReadOnlySpan<char> str, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
static Fraction IAdditiveIdentity<Fraction, Fraction>.AdditiveIdentity => Zero;
static Fraction IMultiplicativeIdentity<Fraction, Fraction>.MultiplicativeIdentity => One;
static int INumberBase<Fraction>.Radix => 2; // Not super sure what to put here.
static Fraction INumberBase<Fraction>.MaxMagnitudeNumber(Fraction a, Fraction b) => a > b ? a : b;
static Fraction INumberBase<Fraction>.MinMagnitudeNumber(Fraction a, Fraction b) => a < b ? a : b;
private static bool TryConvertTo<T>(Fraction frac, out T value)
{
object? tempValue;
if (typeof(T) == typeof(Fraction)) tempValue = frac;
else if (typeof(T) == typeof(Complex)) tempValue = (frac.GetValue(), 0);
else if (typeof(T) == typeof(double)) tempValue = frac.GetValue();
else if (typeof(T) == typeof(float)) tempValue = (float)frac.GetValue();
#if NET5_0_OR_GREATER
else if (typeof(T) == typeof(Half)) tempValue = (Half)frac.GetValue();
#endif
#if NET7_0_OR_GREATER
else if (typeof(T) == typeof(UInt128)) tempValue = (UInt128)frac.GetValue();
else if (typeof(T) == typeof(Int128)) tempValue = (Int128)frac.GetValue();
#endif
else if (typeof(T) == typeof(ulong)) tempValue = (ulong)frac.GetValue();
else if (typeof(T) == typeof(long)) tempValue = (long)frac.GetValue();
else if (typeof(T) == typeof(uint)) tempValue = (uint)frac.GetValue();
else if (typeof(T) == typeof(int)) tempValue = (int)frac.GetValue();
else if (typeof(T) == typeof(ushort)) tempValue = (ushort)frac.GetValue();
else if (typeof(T) == typeof(short)) tempValue = (short)frac.GetValue();
else if (typeof(T) == typeof(byte)) tempValue = (byte)frac.GetValue();
else if (typeof(T) == typeof(sbyte)) tempValue = (sbyte)frac.GetValue();
else
{
value = default!;

View File

@ -0,0 +1,336 @@
using Nerd_STF.Exceptions;
using Nerd_STF.Helpers;
using Nerd_STF.Mathematics.Algebra;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace Nerd_STF.Mathematics.Numbers
{
public struct Quaternion : IComparable<double>,
IEnumerable<double>,
IFormattable,
INumberGroup<Quaternion, double>
#if CS11_OR_GREATER
,//INumber<Quaternion>, Maybe some day.
IFromTuple<Quaternion, (double, double, double, double)>,
IInterpolable<Quaternion>,
IPresets4d<Quaternion>,
IRoundable<Quaternion>,
ISimpleMathOperations<Quaternion>,
ISplittable<Quaternion, (double[] Ws, double[] Xs, double[] Ys, double[] Zs)>,
IVectorOperations<Quaternion>
#endif
{
public static Quaternion Backward => new Quaternion(0, 0, 0, -1);
public static Quaternion Down => new Quaternion(0, 0, -1, 0);
public static Quaternion Forward => new Quaternion(0, 0, 0, 1);
public static Quaternion HighW => new Quaternion(1, 0, 0, 0);
public static Quaternion Left => new Quaternion(0, -1, 0, 0);
public static Quaternion LowW => new Quaternion(-1, 0, 0, 0);
public static Quaternion Right => new Quaternion(0, 1, 0, 0);
public static Quaternion Up => new Quaternion(0, 0, 1, 0);
public static Quaternion One => new Quaternion(1, 1, 1, 1);
public static Quaternion Zero => new Quaternion(0, 0, 0, 0);
public Quaternion Conjugate => new Quaternion(w, -x, -y, -z);
public double Magnitude => MathE.Sqrt(w * w + x * x + y * y + z * z);
public double MagnitudeSqr => w * w + x * x + y * y + z * z;
public double w, x, y, z;
public Quaternion(double w, double x, double y, double z)
{
this.w = w;
this.x = x;
this.y = y;
this.z = z;
}
public Quaternion(IEnumerable<double> nums)
{
w = 0;
x = 0;
y = 0;
z = 0;
int index = 0;
foreach (double item in nums)
{
this[index] = item;
index++;
if (index == 4) break;
}
}
public Quaternion(Fill<double> fill)
{
w = fill(0);
x = fill(1);
y = fill(2);
z = fill(3);
}
public double this[int index]
{
get
{
switch (index)
{
case 0: return w;
case 1: return x;
case 2: return y;
case 3: return z;
default: throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
{
switch (index)
{
case 0: w = value; break;
case 1: x = value; break;
case 2: y = value; break;
case 3: z = value; break;
default: throw new ArgumentOutOfRangeException(nameof(index));
}
}
}
public ListTuple<double> this[string key]
{
get
{
double[] items = new double[key.Length];
for (int i = 0; i < key.Length; i++)
{
char c = key[i];
switch (c)
{
case 'w': items[i] = w; break;
case 'x': items[i] = x; break;
case 'y': items[i] = y; break;
case 'z': items[i] = z; break;
default: throw new ArgumentException("Invalid key.", nameof(key));
}
}
return new ListTuple<double>(items);
}
set
{
IEnumerator<double> stepper = value.GetEnumerator();
for (int i = 0; i < key.Length; i++)
{
char c = key[i];
stepper.MoveNext();
switch (c)
{
case 'w': w = stepper.Current; break;
case 'x': x = stepper.Current; break;
case 'y': y = stepper.Current; break;
case 'z': z = stepper.Current; break;
default: throw new ArgumentException("Invalid key.", nameof(key));
}
}
}
}
public static Quaternion Abs(Quaternion num) => new Quaternion(num.Magnitude, 0, 0, 0);
public static Quaternion Ceiling(Quaternion num) =>
new Quaternion(MathE.Ceiling(num.w),
MathE.Ceiling(num.x),
MathE.Ceiling(num.y),
MathE.Ceiling(num.z));
public static Quaternion Ceiling(Quaternion num, Quaternion min, Quaternion max) =>
new Quaternion(MathE.Clamp(num.w, min.w, max.w),
MathE.Clamp(num.x, min.x, max.x),
MathE.Clamp(num.y, min.y, max.y),
MathE.Clamp(num.z, min.z, max.z));
public static Quaternion ClampMagnitude(Quaternion num, double minMag, double maxMag)
{
Quaternion copy = num;
ClampMagnitude(ref copy, minMag, maxMag);
return copy;
}
public static void ClampMagnitude(ref Quaternion num, double minMag, double maxMag)
{
if (minMag > maxMag) throw new ClampOrderMismatchException(nameof(minMag), nameof(maxMag));
double mag = num.Magnitude;
double factor;
if (mag < minMag) factor = minMag / mag;
else if (mag > maxMag) factor = maxMag / mag;
else factor = 1;
num.w *= factor;
num.x *= factor;
num.y *= factor;
num.z *= factor;
}
public static double Dot(Quaternion a, Quaternion b) => a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z;
public static double Dot(IEnumerable<Quaternion> nums)
{
double w = 1, x = 1, y = 1, z = 1;
foreach (Quaternion q in nums)
{
w *= q.w;
x *= q.x;
y *= q.y;
z *= q.z;
}
return w + x + y + z;
}
public static Quaternion Floor(Quaternion num) =>
new Quaternion(MathE.Floor(num.w),
MathE.Floor(num.x),
MathE.Floor(num.y),
MathE.Floor(num.z));
public static Quaternion Lerp(Quaternion a, Quaternion b, double t, bool clamp = true) =>
new Quaternion(MathE.Lerp(a.w, b.w, t, clamp),
MathE.Lerp(a.x, b.x, t, clamp),
MathE.Lerp(a.y, b.y, t, clamp),
MathE.Lerp(a.z, b.z, t, clamp));
public static Quaternion Product(IEnumerable<Quaternion> nums)
{
bool any = false;
Quaternion result = One;
foreach (Quaternion q in nums)
{
any = true;
result *= q;
}
return any ? result : Zero;
}
public static Quaternion Round(Quaternion num) =>
new Quaternion(MathE.Round(num.w),
MathE.Round(num.x),
MathE.Round(num.y),
MathE.Round(num.z));
public static Quaternion Sum(IEnumerable<Quaternion> nums)
{
bool any = false;
Quaternion result = One;
foreach (Quaternion q in nums)
{
any = true;
result += q;
}
return any ? result : Zero;
}
public static (double[] Ws, double[] Xs, double[] Ys, double[] Zs) SplitArray(IEnumerable<Quaternion> nums)
{
int count = nums.Count();
double[] Ws = new double[count],
Xs = new double[count],
Ys = new double[count],
Zs = new double[count];
int index = 0;
foreach (Quaternion q in nums)
{
Ws[index] = q.w;
Xs[index] = q.x;
Ys[index] = q.y;
Zs[index] = q.z;
index++;
}
return (Ws, Xs, Ys, Zs);
}
public IEnumerator<double> GetEnumerator()
{
yield return w;
yield return x;
yield return y;
yield return z;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int CompareTo(double other) => MagnitudeSqr.CompareTo(other * other);
public int CompareTo(Quaternion other) => MagnitudeSqr.CompareTo(other.MagnitudeSqr);
#if CS8_OR_GREATER
public int CompareTo(object? other)
#else
public int CompareTo(object other)
#endif
{
if (other is null) return 1;
else if (other is Quaternion otherQuat) return CompareTo(otherQuat);
else if (other is double otherNum) return CompareTo(otherNum);
//else if (TryConvertFrom(other, out Quaternion otherConvert)) return CompareTo(otherConvert);
else return 1;
}
public bool Equals(double other) => w == other && x == 0 && y == 0 && z == 0;
public bool Equals(Complex other) => w == other.r && x == other.i && y == 0 && z == 0;
public bool Equals(Quaternion other) => w == other.w && x == other.x && y == other.y && z == other.z;
#if CS8_OR_GREATER
public override bool Equals(object? other)
#else
public override bool Equals(object other)
#endif
{
if (other is null) return false;
else if (other is Quaternion otherQuat) return Equals(otherQuat);
else if (other is Complex otherComp) return Equals(otherComp);
else if (other is double otherNum) return Equals(otherNum);
//else if (TryConvertFrom(other, out Quaternion otherConvert)) return Equals(otherConvert);
else return false;
}
public override int GetHashCode() => base.GetHashCode();
public override string ToString() => ToStringHelper.HighDimNumberToString(this, null, null);
#if CS8_OR_GREATER
public string ToString(string? format) => ToStringHelper.HighDimNumberToString(this, format, null);
public string ToString(IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
public string ToString(string? format, IFormatProvider? provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
#else
public string ToString(string format) => ToStringHelper.HighDimNumberToString(this, format, null);
public string ToString(IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, null, provider);
public string ToString(string format, IFormatProvider provider) => ToStringHelper.HighDimNumberToString(this, format, provider);
#endif
public double[] ToArray() => new double[] { w, x, y, z };
public Fill<double> ToFill()
{
Quaternion @this = this;
return i => @this[i];
}
public List<double> ToList() => new List<double>() { w, x, y, z };
public static Quaternion operator +(Quaternion a) => a;
public static Quaternion operator +(Quaternion a, Quaternion b) => new Quaternion(a.w + b.w, a.x + b.x, a.y + b.y, a.z + b.z);
public static Quaternion operator +(Quaternion a, Complex b) => new Quaternion(a.w + b.r, a.x + b.i, a.y, a.z);
public static Quaternion operator +(Quaternion a, double b) => new Quaternion(a.w + b, a.x, a.y, a.z);
public static Quaternion operator -(Quaternion a) => new Quaternion(-a.w, -a.x, -a.y, -a.z);
public static Quaternion operator -(Quaternion a, Quaternion b) => new Quaternion(a.w - b.w, a.x - b.x, a.y - b.y, a.z - b.z);
public static Quaternion operator -(Quaternion a, Complex b) => new Quaternion(a.w - b.r, a.x - b.i, a.y, a.z);
public static Quaternion operator -(Quaternion a, double b) => new Quaternion(a.w - b, a.x, a.y, a.z);
public static Quaternion operator *(Quaternion a, Quaternion b) =>
new Quaternion(a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w);
public static Quaternion operator *(Quaternion a, Complex b) =>
new Quaternion(a.w * b.r - a.x * b.i,
a.x * b.r + a.w * b.i,
a.y * b.r + a.z * b.i,
a.z * b.r - a.y * b.i);
public static Quaternion operator *(Quaternion a, double b) => new Quaternion(a.w * b, a.x * b, a.y * b, a.z * b);
public static bool operator ==(Quaternion a, Quaternion b) => a.Equals(b);
public static bool operator !=(Quaternion a, Quaternion b) => !a.Equals(b);
public static bool operator >(Quaternion a, Quaternion b) => a.CompareTo(b) > 0;
public static bool operator <(Quaternion a, Quaternion b) => a.CompareTo(b) < 0;
public static bool operator >=(Quaternion a, Quaternion b) => a.CompareTo(b) >= 0;
public static bool operator <=(Quaternion a, Quaternion b) => a.CompareTo(b) <= 0;
public static implicit operator Quaternion(Complex complex) => new Quaternion(complex.r, complex.i, 0, 0);
public static implicit operator Quaternion(Float4 group) => new Quaternion(group.w, group.x, group.y, group.z);
public static explicit operator Quaternion(Int4 group) => new Quaternion(group.w, group.x, group.y, group.z);
public static implicit operator Quaternion(ListTuple<double> tuple) => new Quaternion(tuple[0], tuple[1], tuple[2], tuple[3]);
public static explicit operator Quaternion(System.Numerics.Quaternion quat) => new Quaternion(quat.W, quat.X, quat.Y, quat.Z);
public static implicit operator Quaternion(ValueTuple<double, double, double, double> tuple) => new Quaternion(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
public static implicit operator ListTuple<double>(Quaternion quat) => new ListTuple<double>(quat.w, quat.x, quat.y, quat.z);
public static implicit operator System.Numerics.Quaternion(Quaternion quat) => new System.Numerics.Quaternion((float)quat.x, (float)quat.y, (float)quat.z, (float)quat.w);
public static implicit operator ValueTuple<double, double, double, double>(Quaternion quat) => (quat.w, quat.x, quat.y, quat.z);
}
}

View File

@ -3,7 +3,7 @@
<!-- General stuff -->
<PropertyGroup>
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;net46;net462;net47;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<DebugType>portable</DebugType>
@ -15,7 +15,7 @@
<PropertyGroup>
<Title>Nerd_STF</Title>
<Version>3.0.0-beta3</Version>
<Version>3.0.0</Version>
<Authors>That_One_Nerd</Authors>
<Description>A general-purpose mathematics library for C#.</Description>
<PackageProjectUrl>https://github.com/That-One-Nerd/Nerd_STF</PackageProjectUrl>
@ -28,20 +28,169 @@
<PackageTags>c#;csharp;c sharp;math;mathematics;mathametics;maths;color;rgb;rgba;cmyk;cmyka;hsv;hsva;calculus;linear algebra;linalg;linearalgebra;matrix;matrix2x2;matrix 2x2;matrix3x3;matrix 3x3;matrix4x4;matrix 4x4;matrix multiplication;vector;vector2d;vector3d;vector2;vector3;float2;float3;float4;int2;int3;int4;angle;geometry;vert;line;polygon;triangle;quadrilateral;sphere;circle;number system;numbersystem;complex numbers;complex;2d numbers;2dnumbers;quaternions;4d numbers;4dnumbers;equation;equations;polynomial;quadratic;linear equation</PackageTags>
<!-- Sorry this is stupidly long, wish I could have linked a markdown file instead. -->
<PackageReleaseNotes># Nerd_STF v3.0-beta3
<PackageReleaseNotes># Nerd_STF Version 3.0
Nerd_STF 3.0 is approaching a stable release, but I've still got some things that I still want to add and things that I feel like are unsatisfactory. So here's another beta release. There will probably only be one or two more before a final version is ready. This is easily the most ambitious project I've done in such a small amount of time, so I think you can bear with me as I bring this part of it to completition.
It's time to get this thing out of beta.
Here's what's new:
Nerd_STF 3.0 is finally out! Honestly, there's not a whole lot of tweaks here from the last prerelease, because I got bored of implementing `INumber` methods for the `Quaternion` class, so I'm putting that on hold for now.
For those that haven't seen the pre-releases, the v3.0 update is basically an entire rewrite of the previous library. Almost everything made it over, but there's also a lot of breaking changes. Here's the gist:
# What's New
## More Compatibility
Nerd_STF now targets several versions of .NET and .NET Standard, making it basically run anywhere. You can use [this website](https://dotnet.microsoft.com/en-us/platform/dotnet-standard#versions) to see what different versions of .NET Standard support, but if your project uses a version of .NET that was released in the last 10 years, chances are Nerd_STF supports it.
In addition, Nerd_STF uses some of the new C# features while still retaining older compatibility. If you want to use Nerd_STF in your .NET 8.0 project, you will reference the version of Nerd_STF compiled for .NET 7.0 and retain those fancy new interface features (among others) found in C# 11. Nullability support has been added to all versions of .NET that use C# 8 and above. And if I decide to use more new C# features for Nerd_STF, I'll just target another version of .NET.
## Committed to Doubles
Nerd_STF is a precision library, not one meant to be highly optimized at the sacrifice of precision. So I've decided to fully commit to doubles. The double groups are still called `Float2`, `Float3` and `Float4`, because `Double2` doesn't have quite the same ring to it now, does it? Hope it doesn't get too confusing.
But all math functions are now using doubles (with a few exceptions).
## 'w' Goes in Front Now
I think this is how it should have been. I was really breaking the rules of the alphabet before. Previously in a `Float4`, the `w` component was fourth. Now it is first. The order goes w, x, y, z. You know, how it should.
This means though that casting a `Float3` to a `Float4` will put the extra zero at the start, not the end (because `x` -&gt; `x` in the cast).
```csharp
Float3 xyz = (5, 6, 7);
Float4 wxyz = xyz; // Gives (0, 5, 6, 7)
```
This also means that truncating a `Float4` removes the front `w` first, giving some odd results.
```csharp
Float2 xy = (10, 9);
Float4 wxyz = xy; // Gives (0, 10, 9, 0)
```
```csharp
Float4 wxyz = (9, 8, 7, 6);
Float2 xy = (Float2)wxyz; // Must be explicitly stated. Yields (8, 7)
```
But `x` always goes to `x` when casting between groups, same with the other variables. Hopefully that'll make more sense.
## Combination Indexers
One thing I've always been envious of was HLSL's ability to easily make a subset of a group.
```c++
float3 group = float3(1, 2, 3);
float2 part = group.yz; // Like this.
```
And I had a crude version of this in Nerd_STF before, with properties for `XY`, `YZW`, and stuff like that. But you couldn't do things out of order (for example, you could never do `.ZY`). Also, the naming scheme would not make very much sense. `x` was always the first item Now, you can do it with an indexer.
```csharp
Float4 wxyz = (1, 2, 3, 4);
IEnumerable&lt;double&gt; zyx = wxyz["zyx"]; // Yields [ 4, 3, 2 ]
```
I think you get it, it makes sense. It returns an IEnumerable though, so support has been added in the group constructors to read data from an IEnumerable. You can also set things this way.
```csharp
Float4 wxyz = (1, 2, 3, 4);
wxyz["xy"] = [ 9, 8 ]; // Yields (9, 8, 3, 4)
```
You can also have duplicates. Why you would want duplicates is beyond me. And the order can be whatever you'd like.
```csharp
Float4 wxyz = (1, 2, 3, 4);
IEnumerable&lt;double&gt; nums = wxyz["wyyxzzwy"]; // Yields [ 1, 3, 3, 2, 4, 4, 1, 3 ]
```
## Better Equations
The previous equation system was just a delegate to a method. While it worked for unoptimized things, it won't automatically give precise results. So that system has been overhauled.
Now, every equation derives from the `IEquation` interface, which defines a few operators (most importantly the `Get(double)` method, which is intended to evaluate the equation at the given input). And there are multiple types. There's the base `Equation` type that replicates the method delegate it used to be, but there are also now `Polynomial` equations which specialize in... uh... polynomials, including `Quadratic` and `Linear` along with the dynamic `Polynomial` type.
The indexer is equivalent to calling the `Get(double)` method.
Creating your own is easy, simply derive from the interface and implement the methods required. You should never throw an exception if the two equations you are adding (or multiplying or whatever) are not the same type. If they cannot be combined in a nice way, you should default to the delegate-based approach. Here is an example:
```csharp
public IEquation Add(IEquation other) {
if (other is YourEquation yourEqu) {
// Properly add your two equations.
} else {
// Unknown other equation type, do a basic addition system.
return new Equation(x =&gt; Get(x) + other.Get(x));
}
}
```
And in practice, you should avoid referring to a general equation by its type. Go by the interface operators instead.
```csharp
double Fun(double x) =&gt; 0.5 * MathE.Sin(x);
Equation a = (Equation)Fun; // The traditional delegate approach from previous versions.
Polynomial b = new Polynomial(1, 5, 4); // x^2 + 5x + 4
IEquation c = a.Add(b).Multiply(2); // Result is technically an `Equation`, but we should not cast here.
```
## Renamed the `Mathf` class.
I chose that name because I thought Unity did it well, but I also intend for this project to be compatible with Unity. So I've renamed it to `MathE`. I'm still iffy on that name. I'll commit to one before this project goes out of beta, but it might change until then. Other ideas I'm considering are `Mathe` and `Math2`. Feel free to give your input!
## Support for `System.Drawing` types.
I've tried to use this library when working with Windows Forms a few times. Problem is, it sucks having to manually set the variables from `Point` and `Size`. So Nerd_STF 3.0 now does that for you, with implicit casts to and from both, along with their float variations.
**It's worth mentioning that `Float2` is a double group, while `PointF` is a float group. Data *will* be lost slightly when implicitly casting. Watch out!**
## List Tuples
In the beta1, I introduced **Combination Indexers** for the double and int groups, however a problem was that they only returned `IEnumerable`s. So while some interesting things were supported, some things were not.
```csharp
Float4 wxyz = (1, 2, 3, 4);
IEnumerable&lt;double&gt; vals1 = wxyz["xy"]; // Yields [2, 3]
Float2 vals2 = wxyz["xy"]; // Not allowed!
```
And that kind of sucked. So I created the `ListTuple&lt;T&gt;` type. It's job is to act like a regular tuple, but be able to impliclty convert to either an `IEnumerable` or a regular `ValueTuple&lt;T&gt;`, thus allowing conversions to the double and int groups indirectly. Now, all combination indexers return a `ListTuple` instead of an `IEnumerable`.
Under the hood, the `ListTuple` actually uses an array, but you get the idea.
```csharp
Float4 wxyz = (1, 2, 3, 4);
ListTuple&lt;double&gt; vals1 = wxyz["xy"]; // Yields (2, 3)
Float2 vals2 = vals1; // Yields (2, 3)
IEnumerable&lt;double&gt; vals3 = vals1; // Yields [2, 3]
```
Problem is, now the names have the potential to make much less sense.
```csharp
Float4 wxyz = (1, 2, 3, 4);
Float2 xy = wxyz["xy"]; // x &lt;- x, y &lt;- y
Float2 wz = wxyz["wz"]; // x &lt;- w, y &lt;- z
```
But whatever. You can always stick to using `IEnumerable`s if you want.
## No More `*.Abstract`
I got rid of all the `Abstract` namespaces, since they don't really make much sense in the grand scheme of things. They've all been moved to the namespace that applies to them most (eg. `INumberGroup` went to `Nerd_STF.Mathematics`, `ICombinationIndexer` went to `Nerd_STF` since it applies to more than just mathematics).
## The `Fraction` Type
This type originally went under the name of `Rational` in Nerd_STF 2.x, but that name is actually incorrect, right? So in the rework, it changed names. But it also can do much more now thanks to the `INumber` interface added in .NET 7.0. If you're using that framework or above, the fraction type is fully compatible with that type, and all the math functions in `MathE` and elsewhere that use `INumber` will work with it.
Can I just say that the `INumber` interface is really annoying to write a type for? There's so many weird casting functions and a whole lot of methods that even the .NET developers will hide in public declarations. Why have them at all?
## And Best of All, Matrices
Oh yeah, we're adding those things again. We've got hard-coded `Matrix2x2`, `Matrix3x3`, and `Matrix4x4` classes, as well as a dynamic-sized `Matrix` class fully implemented. The `ToString()` methods are much better with the new implementation than previously, and the `GetHashCode()` methods give different results even if the numbers have their positions swapped (which they originally didn't do).
And they're much faster. Much, much faster. Don't get me wrong, multiplying a 4x4 matrix still requires 64 multiplications and 48 additions, which is quite a lot, but my original implementation was littered with many method calls, easily doubling the runtime. I have now super-inlined basically all of the static matrix code. And I mean, replacing all method calls with nothing but multiplication and addition for things like the determinants, the cofactors, the inverses, and more. Don't look at the source, it's really ugly.
**Note that the dynamic matrix class is not optimized this way, only the constant-size classes.**
## `Fill&lt;T&gt;` and `Fill2d&lt;T&gt;` are back!
I thought the `Fill&lt;T&gt;` delegate was slightly redundant, since you could probably just pass an `IEnumerable` or something instead, but I've learned that it's really easy to pass a Fill delegate in places where an IEnumerable would be annoying. I might add support for Fill stuff in the future, such as an extension list, but for now I've just re-added the delegate and made most types support it in a constructor.
## Slight Matrix Constructor Change
I think I got the meaning of the `byRows` parameter mixed up in my head, so I have swapped its meaning to what it (I think) should be. The default value has also changed, so unless you've been explicitly using it you won't notice a difference.
## And Best of All: Colors!
I have had plenty of time to think about how I could have done colors better in the previous iteration of the library, and I've come up with this, which I think is slightly better.
@ -74,13 +223,13 @@ void MethodB(IndexedColor&lt;ColorRGB&gt; color)
}
```
Anyway, that's all I've got for now. I'm not sure what will be next up, but here's what's left to do:
- Complex numbers and quaternions.
- More color types and formats.
- Bit-offset compatible streams.
- Fix bugs/inconveniences I've noted.
---
I think the Image type will be completely reworked and might be what version 3.1 is.</PackageReleaseNotes>
Anyway, that's a lot of stuff. It's a big update. You should download it!
Sorry my original writing has been a bit off. I kind of forgot about this project again. I can't promise a new update any time soon, but maybe there'll be one!
Enjoy the full release!</PackageReleaseNotes>
</PropertyGroup>
<!-- ItemGroup customization based on framework. -->
@ -93,7 +242,7 @@ I think the Image type will be completely reworked and might be what version 3.1
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <!-- Newer versions not supported. -->
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- Version that comes with .NET has vulnerabilities. -->
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <!-- Newer version not supported. -->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
@ -102,13 +251,48 @@ I think the Image type will be completely reworked and might be what version 3.1
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <!-- Newer versions not supported. -->
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <!-- Version that comes with .NET has vulnerabilities. -->
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <!-- Newer version not supported. -->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net46'">
<PackageReference Include="System.Memory" Version="4.5.5" /> <!-- Newer versions not supported. -->
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <!-- Newer versions not supported. -->
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.Memory" Version="4.6.2" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net47'">
<PackageReference Include="System.Memory" Version="4.6.2" />
</ItemGroup>
<!-- PropertyGroup customization based on framework. -->
<!-- Used to define environment variables based on features the framework supports. -->
<PropertyGroup Condition="'$(TargetFramework)'=='net471'">
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.1'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net46'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net462'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net47'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
@ -127,19 +311,6 @@ I think the Image type will be completely reworked and might be what version 3.1
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.1'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<DefineConstants>$(DefineConstants);CS7_OR_GREATER;CS8_OR_GREATER</DefineConstants>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Pack extra stuff into the NuGet package. -->
<ItemGroup>