From fcee608322d2611439ed83a24a1756da9aa3f43e Mon Sep 17 00:00:00 2001 From: That-One-Nerd Date: Thu, 7 Nov 2024 10:36:28 -0500 Subject: [PATCH] Not sure why I haven't been committing more. Pretty much finished fraction, made some other small changes. --- LICENSE => LICENSE.md | 0 Nerd_STF/Abstract/IModifiable.cs | 10 - Nerd_STF/Abstract/INumberGroup.cs | 3 +- Nerd_STF/Abstract/IRoundable.cs | 4 - Nerd_STF/GlobalSuppressions.cs | 10 +- Nerd_STF/Helpers/ParseHelper.cs | 54 +++ Nerd_STF/Mathematics/Angle.cs | 21 +- Nerd_STF/Mathematics/Float2.cs | 2 - Nerd_STF/Mathematics/Float3.cs | 2 - Nerd_STF/Mathematics/Float4.cs | 2 - Nerd_STF/Mathematics/Int2.cs | 2 - Nerd_STF/Mathematics/Int3.cs | 2 - Nerd_STF/Mathematics/Int4.cs | 2 - Nerd_STF/Mathematics/MathE.cs | 66 +-- Nerd_STF/Mathematics/Numbers/Fraction.cs | 533 ++++++++++++++++++++++- Nerd_STF/Nerd_STF.csproj | 107 ++--- 16 files changed, 672 insertions(+), 148 deletions(-) rename LICENSE => LICENSE.md (100%) delete mode 100644 Nerd_STF/Abstract/IModifiable.cs create mode 100644 Nerd_STF/Helpers/ParseHelper.cs diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/Nerd_STF/Abstract/IModifiable.cs b/Nerd_STF/Abstract/IModifiable.cs deleted file mode 100644 index caf4781..0000000 --- a/Nerd_STF/Abstract/IModifiable.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Nerd_STF.Abstract -{ - public interface IModifiable - where TSelf : IModifiable - { - void Modify(Action action); - } -} diff --git a/Nerd_STF/Abstract/INumberGroup.cs b/Nerd_STF/Abstract/INumberGroup.cs index b263029..6137148 100644 --- a/Nerd_STF/Abstract/INumberGroup.cs +++ b/Nerd_STF/Abstract/INumberGroup.cs @@ -6,8 +6,7 @@ namespace Nerd_STF.Abstract { public interface INumberGroup : ICombinationIndexer, IEnumerable, - IEquatable, - IModifiable + IEquatable #if CS11_OR_GREATER ,IInterpolable, ISimpleMathOperations, diff --git a/Nerd_STF/Abstract/IRoundable.cs b/Nerd_STF/Abstract/IRoundable.cs index b9c7fdd..11d31cc 100644 --- a/Nerd_STF/Abstract/IRoundable.cs +++ b/Nerd_STF/Abstract/IRoundable.cs @@ -10,10 +10,6 @@ namespace Nerd_STF.Abstract static abstract TOut Ceiling(TSelf val); static abstract TOut Floor(TSelf val); static abstract TOut Round(TSelf val); - - static abstract void Ceiling(ref TSelf val); - static abstract void Floor(ref TSelf val); - static abstract void Round(ref TSelf val); } } #endif diff --git a/Nerd_STF/GlobalSuppressions.cs b/Nerd_STF/GlobalSuppressions.cs index a8199f3..d4a681a 100644 --- a/Nerd_STF/GlobalSuppressions.cs +++ b/Nerd_STF/GlobalSuppressions.cs @@ -5,7 +5,9 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")] -[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")] -[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")] -[assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")] +[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")] +[assembly: SuppressMessage("Style", "IDE0083:Use pattern matching", Justification = "Only available for C#9+")] +[assembly: SuppressMessage("Style", "IDE0090:Use 'new(...)'", Justification = "Only available for C#9+")] +[assembly: SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Only available for C#8+. Also, what does applying 'readonly' to a method even do?")] +[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Justification = "Not supported in .NET Standard 1.1 and 1.3.")] +[assembly: SuppressMessage("Performance", "CA1846:Prefer 'AsSpan' over 'Substring'", Justification = "Not supported in .NET Standard 1.1 and 1.3.")] diff --git a/Nerd_STF/Helpers/ParseHelper.cs b/Nerd_STF/Helpers/ParseHelper.cs new file mode 100644 index 0000000..9490d2c --- /dev/null +++ b/Nerd_STF/Helpers/ParseHelper.cs @@ -0,0 +1,54 @@ +using System; + +namespace Nerd_STF.Helpers +{ + internal static class ParseHelper + { + // TODO: Allow parsing more stuff (hexadecimal). + public static double ParseDouble(ReadOnlySpan str) + { + // Turns out this is less accurate than copying and modifying + // the code from ParseDoubleWholeDecimals. I think because applying + // 0.1 to the whole number is worse than 0.1 to a each individual + // decimal point. + int raw = ParseDoubleWholeDecimals(str, out int places); + double value = raw; + for (int i = 0; i < places; i++) value *= 0.1; + return value; + } + public static int ParseDoubleWholeDecimals(ReadOnlySpan str, out int places) + { + str = str.Trim(); + if (str.Length == 0) goto _fail; + places = 0; + + bool negative = str.StartsWith("-".AsSpan()); + + int result = 0; + ReadOnlySpan.Enumerator stepper = str.GetEnumerator(); + if (negative) stepper.MoveNext(); + bool decFound = false; + while (stepper.MoveNext()) + { + char c = stepper.Current; + if (c == ',') continue; + else if (c == '.') + { + decFound = true; + continue; + } + + if (c < '0' || c > '9') goto _fail; + int value = c - '0'; + + result = result * 10 + value; + if (decFound) places++; + } + + return negative ? -result : result; + + _fail: + throw new FormatException("Cannot parse double from span."); + } + } +} diff --git a/Nerd_STF/Mathematics/Angle.cs b/Nerd_STF/Mathematics/Angle.cs index 1cda956..a790620 100644 --- a/Nerd_STF/Mathematics/Angle.cs +++ b/Nerd_STF/Mathematics/Angle.cs @@ -6,8 +6,7 @@ using System.Linq; namespace Nerd_STF.Mathematics { public struct Angle : IComparable, - IEquatable, - IModifiable + IEquatable #if CS11_OR_GREATER ,IPresets2d, IFromTuple @@ -48,10 +47,10 @@ namespace Nerd_STF.Mathematics set => revTheta = value; } - public Angle Complimentary => new Angle(0.25 - MathE.AbsoluteMod(revTheta, 1)); - public Angle Supplimentary => new Angle(0.5 - MathE.AbsoluteMod(revTheta, 1)); - public Angle Normalized => new Angle(MathE.AbsoluteMod(revTheta, 1)); - public Angle Reflected => new Angle(MathE.AbsoluteMod(-revTheta, 1)); + public Angle Complimentary => new Angle(0.25 - MathE.ModAbs(revTheta, 1)); + public Angle Supplimentary => new Angle(0.5 - MathE.ModAbs(revTheta, 1)); + public Angle Normalized => new Angle(MathE.ModAbs(revTheta, 1)); + public Angle Reflected => new Angle(MathE.ModAbs(-revTheta, 1)); private double revTheta; @@ -123,12 +122,12 @@ namespace Nerd_STF.Mathematics if (!any) { best = ang; - if (normalize) bestNormalized = MathE.AbsoluteMod(ang.revTheta, 1); + if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1); any = true; } else if (normalize) { - double angNormalized = MathE.AbsoluteMod(ang.revTheta, 1); + double angNormalized = MathE.ModAbs(ang.revTheta, 1); if (angNormalized > bestNormalized) { best = ang; @@ -150,12 +149,12 @@ namespace Nerd_STF.Mathematics if (!any) { best = ang; - if (normalize) bestNormalized = MathE.AbsoluteMod(ang.revTheta, 1); + if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1); any = true; } else if (normalize) { - double angNormalized = MathE.AbsoluteMod(ang.revTheta, 1); + double angNormalized = MathE.ModAbs(ang.revTheta, 1); if (angNormalized < bestNormalized) { best = ang; @@ -186,8 +185,6 @@ namespace Nerd_STF.Mathematics return angles; } - public void Modify(Action action) => action(this); - public int CompareTo(Angle other) => revTheta.CompareTo(other.revTheta); public bool Equals(Angle other) => revTheta == other.revTheta; #if CS8_OR_GREATER diff --git a/Nerd_STF/Mathematics/Float2.cs b/Nerd_STF/Mathematics/Float2.cs index 3ee6cf2..c4a478f 100644 --- a/Nerd_STF/Mathematics/Float2.cs +++ b/Nerd_STF/Mathematics/Float2.cs @@ -236,8 +236,6 @@ namespace Nerd_STF.Mathematics y = this.y; } - public void Modify(Action action) => action(this); - public bool Equals(Float2 other) => x == other.x && y == other.y; #if CS8_OR_GREATER public override bool Equals(object? obj) diff --git a/Nerd_STF/Mathematics/Float3.cs b/Nerd_STF/Mathematics/Float3.cs index aaa29f1..a40bc83 100644 --- a/Nerd_STF/Mathematics/Float3.cs +++ b/Nerd_STF/Mathematics/Float3.cs @@ -262,8 +262,6 @@ namespace Nerd_STF.Mathematics z = this.z; } - public void Modify(Action action) => action(this); - public bool Equals(Float3 other) => x == other.x && y == other.y && z == other.z; #if CS8_OR_GREATER public override bool Equals(object? obj) diff --git a/Nerd_STF/Mathematics/Float4.cs b/Nerd_STF/Mathematics/Float4.cs index 2d3ac7a..5dbb6af 100644 --- a/Nerd_STF/Mathematics/Float4.cs +++ b/Nerd_STF/Mathematics/Float4.cs @@ -282,8 +282,6 @@ namespace Nerd_STF.Mathematics z = this.z; } - public void Modify(Action action) => action(this); - public bool Equals(Float4 other) => w == other.w && x == other.x && y == other.y && z == other.z; #if CS8_OR_GREATER public override bool Equals(object? obj) diff --git a/Nerd_STF/Mathematics/Int2.cs b/Nerd_STF/Mathematics/Int2.cs index d6356e5..6430b96 100644 --- a/Nerd_STF/Mathematics/Int2.cs +++ b/Nerd_STF/Mathematics/Int2.cs @@ -207,8 +207,6 @@ namespace Nerd_STF.Mathematics y = this.y; } - public void Modify(Action action) => action(this); - public bool Equals(Int2 other) => x == other.x && y == other.y; #if CS8_OR_GREATER public override bool Equals(object? obj) diff --git a/Nerd_STF/Mathematics/Int3.cs b/Nerd_STF/Mathematics/Int3.cs index beb20a9..4bb1519 100644 --- a/Nerd_STF/Mathematics/Int3.cs +++ b/Nerd_STF/Mathematics/Int3.cs @@ -227,8 +227,6 @@ namespace Nerd_STF.Mathematics z = this.z; } - public void Modify(Action action) => action(this); - public bool Equals(Int3 other) => x == other.x && y == other.y && z == other.z; #if CS8_OR_GREATER public override bool Equals(object? obj) diff --git a/Nerd_STF/Mathematics/Int4.cs b/Nerd_STF/Mathematics/Int4.cs index e1ab017..5d752cb 100644 --- a/Nerd_STF/Mathematics/Int4.cs +++ b/Nerd_STF/Mathematics/Int4.cs @@ -239,8 +239,6 @@ namespace Nerd_STF.Mathematics z = this.z; } - public void Modify(Action action) => action(this); - public bool Equals(Int4 other) => w == other.w && x == other.x && y == other.y && z == other.z; #if CS8_OR_GREATER public override bool Equals(object? obj) diff --git a/Nerd_STF/Mathematics/MathE.cs b/Nerd_STF/Mathematics/MathE.cs index 345e1a6..f05401d 100644 --- a/Nerd_STF/Mathematics/MathE.cs +++ b/Nerd_STF/Mathematics/MathE.cs @@ -10,36 +10,15 @@ namespace Nerd_STF.Mathematics { public static class MathE { - public static int Absolute(int value) => value < 0 ? -value : value; - public static double Absolute(double value) => value < 0 ? -value : value; + public static int Abs(int value) => value < 0 ? -value : value; + public static double Abs(double value) => value < 0 ? -value : value; #if CS11_OR_GREATER - public static T Absolute(T num) where T : INumber + public static T Abs(T num) where T : INumber { return num < T.Zero ? -num : num; } #endif - public static int AbsoluteMod(int value, int mod) - { - while (value >= mod) value -= mod; - while (value < 0) value += mod; - return value; - } - public static double AbsoluteMod(double value, double mod) - { - while (value >= mod) value -= mod; - while (value < 0) value += mod; - return value; - } -#if CS11_OR_GREATER - public static T AbsoluteMod(T value, T mod) where T : INumber - { - while (value >= mod) value -= mod; - while (value < T.Zero) value += mod; - return value; - } -#endif - public static int Average(IEnumerable values) { int sum = 0; @@ -78,7 +57,7 @@ namespace Nerd_STF.Mathematics public static double Average(IEquation equ, double lowerBound, double upperBound, double epsilon = 1e-3) { double sum = 0; - double steps = Absolute(upperBound - lowerBound) / epsilon; + double steps = Abs(upperBound - lowerBound) / epsilon; for (double x = lowerBound; x <= upperBound; x += epsilon) sum += equ[x]; return sum / steps; } @@ -235,6 +214,9 @@ namespace Nerd_STF.Mathematics public static IEquation DynamicIntegral(IEquation equ, IEquation lower, IEquation upper) => new Equation((double x) => equ.Integrate(lower[x], upper[x])); + public static double EulersMethod(IEquation equ, double refX, double deltaX) => + equ.Derive()[refX] * deltaX + equ[refX]; + // TODO: Gamma function at some point. public static BigInteger FactorialBig(int num) { @@ -521,6 +503,27 @@ namespace Nerd_STF.Mathematics return best; } + public static int ModAbs(int value, int mod) + { + while (value >= mod) value -= mod; + while (value < 0) value += mod; + return value; + } + public static double ModAbs(double value, double mod) + { + while (value >= mod) value -= mod; + while (value < 0) value += mod; + return value; + } +#if CS11_OR_GREATER + public static T ModAbs(T value, T mod) where T : INumber + { + while (value >= mod) value -= mod; + while (value < T.Zero) value += mod; + return value; + } +#endif + public static BigInteger NprBig(int n, int r) => FactorialBig(n - r + 1, n); public static int Npr(int n, int r) => (int)Factorial(n - r + 1, n); @@ -623,6 +626,13 @@ namespace Nerd_STF.Mathematics public static IEquation Round(IEquation equ) => new Equation((double x) => Round(equ.Get(x))); +#if CS11_OR_GREATER + public static int Sign(T num) where T : INumber => + num > T.Zero ? 1 : num < T.Zero ? -1 : 0; +#endif + public static int Sign(double num) => num > 0 ? 1 : num < 0 ? -1 : 0; + public static int Sign(int num) => num > 0 ? 1 : num < 0 ? -1 : 0; + public static double Sin(double rad, int terms = 8) { bool flip = false; @@ -702,6 +712,12 @@ namespace Nerd_STF.Mathematics } #endif + public static Linear TangentLine(IEquation equ, double x) + { + double slope = equ.Derive()[x]; + return new Linear(slope, equ[x] - slope * x); + } + public static Polynomial TaylorSeries(IEquation equ, double x, int terms) { IEquation current = equ; diff --git a/Nerd_STF/Mathematics/Numbers/Fraction.cs b/Nerd_STF/Mathematics/Numbers/Fraction.cs index c4b4701..383b19d 100644 --- a/Nerd_STF/Mathematics/Numbers/Fraction.cs +++ b/Nerd_STF/Mathematics/Numbers/Fraction.cs @@ -1,41 +1,94 @@ -using System.Numerics; +using Nerd_STF.Abstract; +using Nerd_STF.Helpers; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; namespace Nerd_STF.Mathematics.Numbers { - public struct Fraction + public readonly struct Fraction : IComparable, + IEquatable, + IFormattable +#if CS11_OR_GREATER + ,INumber, + IInterpolable, + IPresets1d, + IRoundable, + ISimpleMathOperations, + ISplittable +#endif { + public static Fraction NaN => new Fraction(0, 0); + public static Fraction NegativeInfinity => new Fraction(-1, 0); public static Fraction One => new Fraction(1, 1); + public static Fraction PositiveInfinity => new Fraction(1, 0); public static Fraction Zero => new Fraction(0, 1); - public int numerator; - public int denominator; + public int Numerator => num; + public int Denominator => den; + + public int Whole => num / den; + public Fraction Partial => new Fraction(num % den, den); + + public Fraction Simplified + { + get + { + int newNum = num, newDen = den; + List numFactors = new List(MathE.PrimeFactors(MathE.Abs(num))), + denFactors = new List(MathE.PrimeFactors(den)); + foreach (int fac in numFactors) + { + if (!denFactors.Contains(fac)) continue; + newNum /= fac; + newDen /= fac; + denFactors.Remove(fac); + } + return new Fraction(newNum, newDen); + } + } + public Fraction Reciprocal => new Fraction(den, num); + + private readonly int num, den; public Fraction(int numerator, int denominator) { - this.numerator = numerator; - this.denominator = denominator; + num = numerator; + den = denominator; + + if (den < 0) + { + num = -num; + den = -den; + } } public static Fraction Approximate(double number, int iterations = 32) { - // Forget what this algorithm is called. When I remember, I'll put its - // Wikipedia page here. - + int num, den; if (number == 0) return Zero; else if (number == 1) return One; + else if (number % 1 == 0) return new Fraction((int)number, 1); else if (number < 0) { - Fraction result = Approximate(-number, iterations); - result.numerator = -result.numerator; - return result; + Approximate(-number, iterations, out num, out den, out _); + num = -num; } else if (number > 1) { + Approximate(number % 1, iterations, out num, out den, out _); int whole = (int)number; - Fraction result = Approximate(number % 1, iterations); - result.numerator += whole * result.denominator; - return result; + num += whole * den; } + else Approximate(number, iterations, out num, out den, out _); + return new Fraction(num, den); + } + private static void Approximate(double number, int iterations, out int num, out int den, out double error) + { + // Forget what this algorithm is called. When I remember, I'll put its + // Wikipedia page here. int minNum = 0, maxNum = 1, newNum = minNum + maxNum, minDen = 1, maxDen = 1, newDen = minDen + maxDen; @@ -57,12 +110,456 @@ namespace Nerd_STF.Mathematics.Numbers newDen = minDen + maxDen; newVal = (double)newNum / newDen; } - return new Fraction(newNum, newDen); + num = newNum; + den = newDen; + error = MathE.Abs(newVal - number); } - public double GetValue() => (double)numerator / denominator; + public static IEnumerable Egyptian(double number, int maxTerms) => + Egyptian(Approximate(number, 256), maxTerms); + public static Fraction[] Egyptian(Fraction number, int maxTerms) + { + List parts = new List(); + int terms = 0; + foreach (Fraction part in EgyptianE(number)) + { + parts.Add(part); + terms++; + if (terms >= maxTerms) break; + } + return parts.ToArray(); + } + public static IEnumerable EgyptianE(double number) => + EgyptianE(Approximate(number, 256)); + public static IEnumerable EgyptianE(Fraction number) + { + int wholes = number.Whole; + if (wholes > 0) yield return new Fraction(wholes, 1); - public override string ToString() => $"{numerator} / {denominator}"; + number = number.Partial; + int newDen = 2, curNum = number.num, curDen = number.den; + while (curNum > 0 && newDen <= curDen) + { + if (curNum * newDen >= curDen) + { + yield return new Fraction(1, newDen); + curNum = curNum * newDen - curDen; + curDen *= newDen; + } + else newDen++; + } + } + +#if CS8_OR_GREATER + public static Fraction Parse(string? str) => +#else + public static Fraction Parse(string str) => +#endif + str is null ? NaN : Parse(str.AsSpan()); + public static Fraction Parse(ReadOnlySpan str) + { + if (str.Length == 0) return NaN; + + ReadOnlySpan numSpan, denSpan; + str = str.Trim(); + char first = str[0]; + if (first == '\\') + { + // TeX format. + if (str.Length >= 5 && str.StartsWith("\\frac".AsSpan())) str = str.Slice(5); + else if (str.Length >= 6 && str.Slice(2, 4).Equals("frac".AsSpan(), StringComparison.Ordinal)) str = str.Slice(6); // Allows for \sfrac or things like that. + else goto _fail; + + if (!str.StartsWith("{".AsSpan()) || !str.EndsWith("}".AsSpan())) goto _fail; + int separator = str.IndexOf(','); + if (separator == -1 || separator == 1 || separator == str.Length - 2) goto _fail; + + numSpan = str.Slice(1, separator - 1); + denSpan = str.Slice(separator + 1, str.Length - separator - 2); + } + else + { + // Standard fraction format. + char[] allowedSeparators = new char[] { '/', ':' }; + int separator = -1; + foreach (char c in allowedSeparators) + { + int newSep = str.IndexOf(c); + if (newSep == -1) continue; + else + { + if (separator != -1) goto _fail; // More than one separator. + else separator = newSep; + } + } + if (separator == 0 || separator == str.Length - 1) goto _fail; + + numSpan = str.Slice(0, separator); + denSpan = str.Slice(separator + 1, str.Length - separator - 1); + } + + int top = ParseHelper.ParseDoubleWholeDecimals(numSpan, out int topPlaces), + bot = ParseHelper.ParseDoubleWholeDecimals(denSpan, out int botPlaces); + int topDen = 1, botDen = 1; + for (int i = 0; i < topPlaces; i++) topDen *= 10; + for (int i = 0; i < botPlaces; i++) botDen *= 10; + + Fraction topF = new Fraction(top, topDen), botF = new Fraction(bot, botDen); + return topF / botF; + + _fail: + throw new FormatException("Cannot parse fraction from span."); + } +#if CS8_OR_GREATER + public static bool TryParse(string? str, out Fraction frac) => +#else + public static bool TryParse(string str, out Fraction frac) => +#endif + TryParse(str.AsSpan(), out frac); + public static bool TryParse(ReadOnlySpan str, out Fraction frac) + { + try + { + frac = Parse(str); + return true; + } + catch + { + frac = NaN; + return false; + } + } + + public static Fraction Abs(Fraction val) => new Fraction(MathE.Abs(val.num), val.den); + public static Fraction Ceiling(Fraction val) + { + int newNum = val.num; + if (val.num % val.den != 0) + { + if (val.num > 0) newNum += val.den - (val.num % val.den); + else newNum -= val.num % val.den; + } + return new Fraction(newNum, val.den); + } + public static Fraction Clamp(Fraction val, Fraction min, Fraction max) + { + int lcm = MathE.Lcm(val.den, min.den, max.den); + int valFac = val.den / lcm, minFac = min.den / lcm, maxFac = max.den / lcm; + int trueVal = val.num * valFac, trueMin = min.num * minFac, trueMax = max.num * maxFac; + if (trueVal > trueMax) return max; + else if (trueVal < trueMin) return min; + else return val; + } + public static Fraction Floor(Fraction val) + { + int newNum = val.num; + if (val.num % val.den != 0) + { + if (val.num > 0) newNum -= val.num % val.den; + else newNum -= val.den + (val.num % val.den); + } + return new Fraction(newNum, val.den); + } + public static Fraction Lerp(Fraction a, Fraction b, double t, bool clamp = true, bool fast = false) + { + if (fast) + { + int aNum = a.num * b.den, bNum = b.num * a.den, cDen = a.den * b.den; + int cNum = (int)(aNum + t * (bNum - aNum)); + return new Fraction(cNum, cDen); + } + else return Approximate(MathE.Lerp(a, b, t, clamp), 128); + } +#if CS11_OR_GREATER + static Fraction IInterpolable.Lerp(Fraction a, Fraction b, double t, bool clamp) => + Lerp(a, b, t, clamp, false); +#endif + public static Fraction Product(IEnumerable vals) + { + bool any = false; + int resultNum = 1, resultDen = 1; + foreach (Fraction frac in vals) + { + any = true; + resultNum *= frac.num; + resultDen *= frac.den; + } + return any ? new Fraction(resultNum, resultDen) : Zero; + } + public static Fraction Round(Fraction val) + { + int half = val.den / 2; + int newNum = val.num; + if (val.num > 0) + { + if (val.num % val.den > half) newNum += val.den - (val.num % val.den); + else newNum -= val.num % val.den; + } + else + { + if (-val.num % val.den > half) newNum -= val.den + (val.num % val.den); + else newNum -= val.num % val.den; + } + return new Fraction(newNum, val.den); + } + public static Fraction Sum(IEnumerable vals) + { + bool any = false; + Fraction result = Zero; + foreach (Fraction frac in vals) + { + any = true; + result += frac; + } + return any ? result : NaN; + } + +#if CS8_OR_GREATER + private static bool TryConvertFrom(object? value, out Fraction result) +#else + private static bool TryConvertFrom(object value, out Fraction result) +#endif + { + if (value is null) + { + result = NaN; + return false; + } + else if (value is Fraction valueFrac) result = valueFrac; + 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; + return false; + } + return true; + } + + public static bool IsCanonical(Fraction val) + { + IEnumerable factorsNum = MathE.PrimeFactorsE(MathE.Abs(val.num)), + factorsDen = MathE.PrimeFactorsE(val.den), + shared = factorsNum.Where(x => factorsDen.Contains(x)); + return shared.Any(); + } + 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 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; + public static bool IsNegative(Fraction val) => val.num < 0; + public static bool IsNegativeInfinity(Fraction val) => val.den == 0 && val.num < 0; + public static bool IsNormal(Fraction val) => val.den != 0 && val.num != 0; + public static bool IsOddInteger(Fraction val) => + val.num % val.den == 0 && val.num / val.den % 2 == 1; + public static bool IsPositive(Fraction val) => val.num > 0; + public static bool IsPositiveInfinity(Fraction val) => val.den == 0 && val.num > 0; + 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.IsComplexNumber(Fraction val) => false; + static bool INumberBase.IsImaginaryNumber(Fraction val) => false; + static bool INumberBase.IsSubnormal(Fraction val) => false; // What does this mean??? + static Fraction INumberBase.Parse(string? str, NumberStyles style, IFormatProvider? provider) => Parse(str); + static Fraction INumberBase.Parse(ReadOnlySpan str, NumberStyles style, IFormatProvider? provider) => Parse(str); + static bool INumberBase.TryParse(string? str, NumberStyles style, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac); + static bool INumberBase.TryParse(ReadOnlySpan str, NumberStyles style, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac); + static Fraction IParsable.Parse(string? str, IFormatProvider? provider) => Parse(str); + static bool IParsable.TryParse(string? str, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac); + static Fraction ISpanParsable.Parse(ReadOnlySpan str, IFormatProvider? provider) => Parse(str); + static bool ISpanParsable.TryParse(ReadOnlySpan str, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac); + static Fraction IAdditiveIdentity.AdditiveIdentity => Zero; + static Fraction IMultiplicativeIdentity.MultiplicativeIdentity => One; + static int INumberBase.Radix => 2; // Not super sure what to put here. + + private static bool TryConvertTo(Fraction frac, out T value) + { + object? tempValue; + + if (typeof(T) == typeof(Fraction)) tempValue = frac; + 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!; + return false; + } + + value = (T)tempValue; + return true; + } + static bool INumberBase.TryConvertFromChecked(TOther value, out Fraction result) => TryConvertFrom(value, out result); + static bool INumberBase.TryConvertFromSaturating(TOther value, out Fraction result) => TryConvertFrom(value, out result); + static bool INumberBase.TryConvertFromTruncating(TOther value, out Fraction result) => TryConvertFrom(value, out result); + static bool INumberBase.TryConvertToChecked(Fraction value, out TOther result) => TryConvertTo(value, out result); + static bool INumberBase.TryConvertToSaturating(Fraction value, out TOther result) => TryConvertTo(value, out result); + static bool INumberBase.TryConvertToTruncating(Fraction value, out TOther result) => TryConvertTo(value, out result); +#endif + + public static (int[] nums, int[] dens) SplitArray(IEnumerable vals) + { + int count = vals.Count(); + int[] nums = new int[count], dens = new int[count]; + int index = 0; + foreach (Fraction val in vals) + { + nums[index] = val.num; + dens[index] = val.den; + } + return (nums, dens); + } + + public double GetValue() => (double)num / den; + +#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 Fraction otherFrac) return CompareTo(otherFrac); + else if (TryConvertFrom(other, out Fraction otherConvert)) return CompareTo(otherConvert); + else return -1; + } + public int CompareTo(Fraction other) => (num * other.den).CompareTo(other.num * den); + public bool Equals(Fraction other) => num * other.den == other.num * den; +#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 Fraction otherFrac) return Equals(otherFrac); + else if (TryConvertFrom(other, out Fraction otherConvert)) return Equals(otherConvert); + else return false; + } + public override int GetHashCode() => + (int)(((uint)num.GetHashCode() & 0xFFFF0000u) | ((uint)den.GetHashCode() & 0x0000FFFFu)); +#if CS8_OR_GREATER + public override string ToString() => ToString(null, null); + public string ToString(string? format) => ToString(format, null); + public string ToString(IFormatProvider? provider) => ToString(null, provider); + public string ToString(string? format, IFormatProvider? provider) => $"{num.ToString(format)}/{den.ToString(format)}"; +#else + public override string ToString() => ToString(null, null); + public string ToString(string format) => ToString(format, null); + public string ToString(IFormatProvider provider) => ToString(null, provider); + public string ToString(string format, IFormatProvider provider) => $"{num.ToString(format)}/{den.ToString(format)}"; +#endif + +#if CS11_OR_GREATER + public bool TryFormat(Span dest, out int charsWritten, ReadOnlySpan 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; + } +#endif + + public static Fraction operator +(Fraction a) => a; + public static Fraction operator +(Fraction a, Fraction b) + { + int lcm = MathE.Lcm(a.den, b.den); + int facA = lcm / a.den, facB = lcm / b.den; + return new Fraction(a.num * facA + b.num * facB, lcm); + } + public static Fraction operator +(Fraction a, int b) => new Fraction(a.num + b * a.den, a.den); + public static Fraction operator +(Fraction a, double b) => a * Approximate(b); + public static Fraction operator ++(Fraction a) => new Fraction(a.num + a.den, a.den); + public static Fraction operator -(Fraction a) => new Fraction(-a.num, a.den); + public static Fraction operator -(Fraction a, Fraction b) + { + int lcm = MathE.Lcm(a.den, b.den); + int facA = lcm / a.den, facB = lcm / b.den; + return new Fraction(a.num * facA - b.num * facB, lcm); + } + public static Fraction operator -(Fraction a, int b) => new Fraction(a.num - b * a.den, a.den); + public static Fraction operator -(Fraction a, double b) => a * Approximate(b); + public static Fraction operator --(Fraction a) => new Fraction(a.num - a.den, a.den); + public static Fraction operator *(Fraction a, Fraction b) => new Fraction(a.num * b.num, a.den * b.den); + public static Fraction operator *(Fraction a, int b) => new Fraction(a.num * b, a.den); + public static Fraction operator *(Fraction a, double b) => a * Approximate(b); + public static Fraction operator /(Fraction a, Fraction b) => new Fraction(a.num * b.den, a.den * b.num); + public static Fraction operator /(Fraction a, int b) => new Fraction(a.num, a.den * b); + public static Fraction operator /(Fraction a, double b) => a / Approximate(b); + public static Fraction operator %(Fraction a, Fraction b) + { + // c = a / b + // f = b * mod(c, 1) + int cNum = a.num * b.den, cDen = a.den * b.num; + if (cDen < 0) + { + cNum = -cNum; + cDen = -cDen; + } + cNum = MathE.ModAbs(cNum, cDen); // Fractional portion. + return new Fraction(b.num * cNum, b.den * cDen); + } + public static Fraction operator %(Fraction a, int b) + { + // c = a / b + // f = b * mod(c, 1) + int cNum = a.num, cDen = a.den * b; + if (cDen < 0) + { + cNum = -cNum; + cDen = -cDen; + } + cNum = MathE.ModAbs(cNum, cDen); // Fractional portion. + return new Fraction(b * cNum, cDen); + } + public static Fraction operator %(Fraction a, double b) => a % Approximate(b); + public static Fraction operator ^(Fraction a, Fraction b) => new Fraction(a.num + b.num, a.den + b.den); + public static Fraction operator ~(Fraction a) => a.Reciprocal; + public static bool operator ==(Fraction a, Fraction b) => a.Equals(b); + public static bool operator !=(Fraction a, Fraction b) => !a.Equals(b); + public static bool operator >(Fraction a, Fraction b) => a.CompareTo(b) > 0; + public static bool operator <(Fraction a, Fraction b) => a.CompareTo(b) < 0; + public static bool operator >=(Fraction a, Fraction b) => a.CompareTo(b) >= 0; + public static bool operator <=(Fraction a, Fraction b) => a.CompareTo(b) <= 0; + // TODO: Comparisons with a double on the right (maybe). public static implicit operator double(Fraction frac) => frac.GetValue(); public static explicit operator Fraction(double num) => Approximate(num); diff --git a/Nerd_STF/Nerd_STF.csproj b/Nerd_STF/Nerd_STF.csproj index a22f8b4..416fe12 100644 --- a/Nerd_STF/Nerd_STF.csproj +++ b/Nerd_STF/Nerd_STF.csproj @@ -1,11 +1,18 @@  + + netstandard1.1;netstandard1.3;netstandard2.1;netcoreapp3.0;net5.0;net7.0 true True - embedded + portable True + + + + + Nerd_STF 3.0.0-beta2 That_One_Nerd @@ -14,7 +21,12 @@ Logo Square.png README.md https://github.com/That-One-Nerd/Nerd_STF + MIT + True + snupkg 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 + + # Nerd_STF v3.0-beta1 Hi! Pretty much nothing has remained the same from version 2. There are plenty of breaking changes, and the betas will have plenty of missing features from 2.4.1. The betas will continue until every feature from 2.4.1 has been added to 3.0 or scrapped. @@ -123,78 +135,48 @@ I've tried to use this library when working with Windows Forms a few times. Prob --- Anyway, that's most of the big changes! I don't know if I'll do the full changelog like I have before. It takes a really long time to compile for large updates. We'll see. Thanks for checking out the update and I hope you use it well (or wait for the release version, that's fine too)! - MIT - True - snupkg + + + + + + + + + + + + + + + + + + + - $(DefineConstants);CS8_OR_GREATER + $(DefineConstants);CS8_OR_GREATER;SPAN_SUPPORT enable - $(DefineConstants);CS8_OR_GREATER + $(DefineConstants);CS8_OR_GREATER;SPAN_SUPPORT enable - $(DefineConstants);CS8_OR_GREATER;CS9_OR_GREATER + $(DefineConstants);CS8_OR_GREATER;CS9_OR_GREATER;SPAN_SUPPORT enable - $(DefineConstants);CS10_OR_GREATER;CS11_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER + $(DefineConstants);CS10_OR_GREATER;CS11_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER;SPAN_SUPPORT enable - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - - - - portable - + + @@ -205,11 +187,14 @@ Anyway, that's most of the big changes! I don't know if I'll do the full changel True \ - - - - - + + True + \ + + + True + \ +