Not sure why I haven't been committing more. Pretty much finished fraction, made some other small changes.

This commit is contained in:
That-One-Nerd 2024-11-07 10:36:28 -05:00
parent 0704b8eec7
commit fcee608322
16 changed files with 672 additions and 148 deletions

View File

View File

@ -1,10 +0,0 @@
using System;
namespace Nerd_STF.Abstract
{
public interface IModifiable<TSelf>
where TSelf : IModifiable<TSelf>
{
void Modify(Action<TSelf> action);
}
}

View File

@ -6,8 +6,7 @@ namespace Nerd_STF.Abstract
{ {
public interface INumberGroup<TSelf, TItem> : ICombinationIndexer<TItem>, public interface INumberGroup<TSelf, TItem> : ICombinationIndexer<TItem>,
IEnumerable<TItem>, IEnumerable<TItem>,
IEquatable<TSelf>, IEquatable<TSelf>
IModifiable<TSelf>
#if CS11_OR_GREATER #if CS11_OR_GREATER
,IInterpolable<TSelf>, ,IInterpolable<TSelf>,
ISimpleMathOperations<TSelf>, ISimpleMathOperations<TSelf>,

View File

@ -10,10 +10,6 @@ namespace Nerd_STF.Abstract
static abstract TOut Ceiling(TSelf val); static abstract TOut Ceiling(TSelf val);
static abstract TOut Floor(TSelf val); static abstract TOut Floor(TSelf val);
static abstract TOut Round(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 #endif

View File

@ -5,7 +5,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0066:Use 'switch' expression", Justification = "Only available for C#8+.")] [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", "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", "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", "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.")]

View File

@ -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<char> 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<char> str, out int places)
{
str = str.Trim();
if (str.Length == 0) goto _fail;
places = 0;
bool negative = str.StartsWith("-".AsSpan());
int result = 0;
ReadOnlySpan<char>.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.");
}
}
}

View File

@ -6,8 +6,7 @@ using System.Linq;
namespace Nerd_STF.Mathematics namespace Nerd_STF.Mathematics
{ {
public struct Angle : IComparable<Angle>, public struct Angle : IComparable<Angle>,
IEquatable<Angle>, IEquatable<Angle>
IModifiable<Angle>
#if CS11_OR_GREATER #if CS11_OR_GREATER
,IPresets2d<Angle>, ,IPresets2d<Angle>,
IFromTuple<Angle, (double, Angle.Unit)> IFromTuple<Angle, (double, Angle.Unit)>
@ -48,10 +47,10 @@ namespace Nerd_STF.Mathematics
set => revTheta = value; set => revTheta = value;
} }
public Angle Complimentary => new Angle(0.25 - MathE.AbsoluteMod(revTheta, 1)); public Angle Complimentary => new Angle(0.25 - MathE.ModAbs(revTheta, 1));
public Angle Supplimentary => new Angle(0.5 - MathE.AbsoluteMod(revTheta, 1)); public Angle Supplimentary => new Angle(0.5 - MathE.ModAbs(revTheta, 1));
public Angle Normalized => new Angle(MathE.AbsoluteMod(revTheta, 1)); public Angle Normalized => new Angle(MathE.ModAbs(revTheta, 1));
public Angle Reflected => new Angle(MathE.AbsoluteMod(-revTheta, 1)); public Angle Reflected => new Angle(MathE.ModAbs(-revTheta, 1));
private double revTheta; private double revTheta;
@ -123,12 +122,12 @@ namespace Nerd_STF.Mathematics
if (!any) if (!any)
{ {
best = ang; best = ang;
if (normalize) bestNormalized = MathE.AbsoluteMod(ang.revTheta, 1); if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1);
any = true; any = true;
} }
else if (normalize) else if (normalize)
{ {
double angNormalized = MathE.AbsoluteMod(ang.revTheta, 1); double angNormalized = MathE.ModAbs(ang.revTheta, 1);
if (angNormalized > bestNormalized) if (angNormalized > bestNormalized)
{ {
best = ang; best = ang;
@ -150,12 +149,12 @@ namespace Nerd_STF.Mathematics
if (!any) if (!any)
{ {
best = ang; best = ang;
if (normalize) bestNormalized = MathE.AbsoluteMod(ang.revTheta, 1); if (normalize) bestNormalized = MathE.ModAbs(ang.revTheta, 1);
any = true; any = true;
} }
else if (normalize) else if (normalize)
{ {
double angNormalized = MathE.AbsoluteMod(ang.revTheta, 1); double angNormalized = MathE.ModAbs(ang.revTheta, 1);
if (angNormalized < bestNormalized) if (angNormalized < bestNormalized)
{ {
best = ang; best = ang;
@ -186,8 +185,6 @@ namespace Nerd_STF.Mathematics
return angles; return angles;
} }
public void Modify(Action<Angle> action) => action(this);
public int CompareTo(Angle other) => revTheta.CompareTo(other.revTheta); public int CompareTo(Angle other) => revTheta.CompareTo(other.revTheta);
public bool Equals(Angle other) => revTheta == other.revTheta; public bool Equals(Angle other) => revTheta == other.revTheta;
#if CS8_OR_GREATER #if CS8_OR_GREATER

View File

@ -236,8 +236,6 @@ namespace Nerd_STF.Mathematics
y = this.y; y = this.y;
} }
public void Modify(Action<Float2> action) => action(this);
public bool Equals(Float2 other) => x == other.x && y == other.y; public bool Equals(Float2 other) => x == other.x && y == other.y;
#if CS8_OR_GREATER #if CS8_OR_GREATER
public override bool Equals(object? obj) public override bool Equals(object? obj)

View File

@ -262,8 +262,6 @@ namespace Nerd_STF.Mathematics
z = this.z; z = this.z;
} }
public void Modify(Action<Float3> action) => action(this);
public bool Equals(Float3 other) => x == other.x && y == other.y && z == other.z; public bool Equals(Float3 other) => x == other.x && y == other.y && z == other.z;
#if CS8_OR_GREATER #if CS8_OR_GREATER
public override bool Equals(object? obj) public override bool Equals(object? obj)

View File

@ -282,8 +282,6 @@ namespace Nerd_STF.Mathematics
z = this.z; z = this.z;
} }
public void Modify(Action<Float4> action) => action(this);
public bool Equals(Float4 other) => w == other.w && x == other.x && y == other.y && z == other.z; public bool Equals(Float4 other) => w == other.w && x == other.x && y == other.y && z == other.z;
#if CS8_OR_GREATER #if CS8_OR_GREATER
public override bool Equals(object? obj) public override bool Equals(object? obj)

View File

@ -207,8 +207,6 @@ namespace Nerd_STF.Mathematics
y = this.y; y = this.y;
} }
public void Modify(Action<Int2> action) => action(this);
public bool Equals(Int2 other) => x == other.x && y == other.y; public bool Equals(Int2 other) => x == other.x && y == other.y;
#if CS8_OR_GREATER #if CS8_OR_GREATER
public override bool Equals(object? obj) public override bool Equals(object? obj)

View File

@ -227,8 +227,6 @@ namespace Nerd_STF.Mathematics
z = this.z; z = this.z;
} }
public void Modify(Action<Int3> action) => action(this);
public bool Equals(Int3 other) => x == other.x && y == other.y && z == other.z; public bool Equals(Int3 other) => x == other.x && y == other.y && z == other.z;
#if CS8_OR_GREATER #if CS8_OR_GREATER
public override bool Equals(object? obj) public override bool Equals(object? obj)

View File

@ -239,8 +239,6 @@ namespace Nerd_STF.Mathematics
z = this.z; z = this.z;
} }
public void Modify(Action<Int4> action) => action(this);
public bool Equals(Int4 other) => w == other.w && x == other.x && y == other.y && z == other.z; public bool Equals(Int4 other) => w == other.w && x == other.x && y == other.y && z == other.z;
#if CS8_OR_GREATER #if CS8_OR_GREATER
public override bool Equals(object? obj) public override bool Equals(object? obj)

View File

@ -10,36 +10,15 @@ namespace Nerd_STF.Mathematics
{ {
public static class MathE public static class MathE
{ {
public static int Absolute(int value) => value < 0 ? -value : value; public static int Abs(int value) => value < 0 ? -value : value;
public static double Absolute(double value) => value < 0 ? -value : value; public static double Abs(double value) => value < 0 ? -value : value;
#if CS11_OR_GREATER #if CS11_OR_GREATER
public static T Absolute<T>(T num) where T : INumber<T> public static T Abs<T>(T num) where T : INumber<T>
{ {
return num < T.Zero ? -num : num; return num < T.Zero ? -num : num;
} }
#endif #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>(T value, T mod) where T : INumber<T>
{
while (value >= mod) value -= mod;
while (value < T.Zero) value += mod;
return value;
}
#endif
public static int Average(IEnumerable<int> values) public static int Average(IEnumerable<int> values)
{ {
int sum = 0; 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) public static double Average(IEquation equ, double lowerBound, double upperBound, double epsilon = 1e-3)
{ {
double sum = 0; 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]; for (double x = lowerBound; x <= upperBound; x += epsilon) sum += equ[x];
return sum / steps; return sum / steps;
} }
@ -235,6 +214,9 @@ namespace Nerd_STF.Mathematics
public static IEquation DynamicIntegral(IEquation equ, IEquation lower, IEquation upper) => public static IEquation DynamicIntegral(IEquation equ, IEquation lower, IEquation upper) =>
new Equation((double x) => equ.Integrate(lower[x], upper[x])); 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. // TODO: Gamma function at some point.
public static BigInteger FactorialBig(int num) public static BigInteger FactorialBig(int num)
{ {
@ -521,6 +503,27 @@ namespace Nerd_STF.Mathematics
return best; 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>(T value, T mod) where T : INumber<T>
{
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 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); 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) => public static IEquation Round(IEquation equ) =>
new Equation((double x) => Round(equ.Get(x))); new Equation((double x) => Round(equ.Get(x)));
#if CS11_OR_GREATER
public static int Sign<T>(T num) where T : INumber<T> =>
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) public static double Sin(double rad, int terms = 8)
{ {
bool flip = false; bool flip = false;
@ -702,6 +712,12 @@ namespace Nerd_STF.Mathematics
} }
#endif #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) public static Polynomial TaylorSeries(IEquation equ, double x, int terms)
{ {
IEquation current = equ; IEquation current = equ;

View File

@ -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 namespace Nerd_STF.Mathematics.Numbers
{ {
public struct Fraction public readonly struct Fraction : IComparable<Fraction>,
IEquatable<Fraction>,
IFormattable
#if CS11_OR_GREATER
,INumber<Fraction>,
IInterpolable<Fraction>,
IPresets1d<Fraction>,
IRoundable<Fraction>,
ISimpleMathOperations<Fraction>,
ISplittable<Fraction, (int[] nums, int[] dens)>
#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 One => new Fraction(1, 1);
public static Fraction PositiveInfinity => new Fraction(1, 0);
public static Fraction Zero => new Fraction(0, 1); public static Fraction Zero => new Fraction(0, 1);
public int numerator; public int Numerator => num;
public int denominator; 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<int> numFactors = new List<int>(MathE.PrimeFactors(MathE.Abs(num))),
denFactors = new List<int>(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) public Fraction(int numerator, int denominator)
{ {
this.numerator = numerator; num = numerator;
this.denominator = denominator; den = denominator;
if (den < 0)
{
num = -num;
den = -den;
}
} }
public static Fraction Approximate(double number, int iterations = 32) public static Fraction Approximate(double number, int iterations = 32)
{ {
// Forget what this algorithm is called. When I remember, I'll put its int num, den;
// Wikipedia page here.
if (number == 0) return Zero; if (number == 0) return Zero;
else if (number == 1) return One; else if (number == 1) return One;
else if (number % 1 == 0) return new Fraction((int)number, 1);
else if (number < 0) else if (number < 0)
{ {
Fraction result = Approximate(-number, iterations); Approximate(-number, iterations, out num, out den, out _);
result.numerator = -result.numerator; num = -num;
return result;
} }
else if (number > 1) else if (number > 1)
{ {
Approximate(number % 1, iterations, out num, out den, out _);
int whole = (int)number; int whole = (int)number;
Fraction result = Approximate(number % 1, iterations); num += whole * den;
result.numerator += whole * result.denominator;
return result;
} }
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, int minNum = 0, maxNum = 1, newNum = minNum + maxNum,
minDen = 1, maxDen = 1, newDen = minDen + maxDen; minDen = 1, maxDen = 1, newDen = minDen + maxDen;
@ -57,12 +110,456 @@ namespace Nerd_STF.Mathematics.Numbers
newDen = minDen + maxDen; newDen = minDen + maxDen;
newVal = (double)newNum / newDen; 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<Fraction> Egyptian(double number, int maxTerms) =>
Egyptian(Approximate(number, 256), maxTerms);
public static Fraction[] Egyptian(Fraction number, int maxTerms)
{
List<Fraction> parts = new List<Fraction>();
int terms = 0;
foreach (Fraction part in EgyptianE(number))
{
parts.Add(part);
terms++;
if (terms >= maxTerms) break;
}
return parts.ToArray();
}
public static IEnumerable<Fraction> EgyptianE(double number) =>
EgyptianE(Approximate(number, 256));
public static IEnumerable<Fraction> 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<char> str)
{
if (str.Length == 0) return NaN;
ReadOnlySpan<char> 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<char> 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<Fraction>.Lerp(Fraction a, Fraction b, double t, bool clamp) =>
Lerp(a, b, t, clamp, false);
#endif
public static Fraction Product(IEnumerable<Fraction> 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<Fraction> 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<int> 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<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);
static bool INumberBase<Fraction>.TryParse(ReadOnlySpan<char> str, NumberStyles style, IFormatProvider? provider, out Fraction frac) => TryParse(str, out frac);
static Fraction IParsable<Fraction>.Parse(string? str, IFormatProvider? provider) => Parse(str);
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.
private static bool TryConvertTo<T>(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<Fraction>.TryConvertFromChecked<TOther>(TOther value, out Fraction result) => TryConvertFrom(value, out result);
static bool INumberBase<Fraction>.TryConvertFromSaturating<TOther>(TOther value, out Fraction result) => TryConvertFrom(value, out result);
static bool INumberBase<Fraction>.TryConvertFromTruncating<TOther>(TOther value, out Fraction result) => TryConvertFrom(value, out result);
static bool INumberBase<Fraction>.TryConvertToChecked<TOther>(Fraction value, out TOther result) => TryConvertTo(value, out result);
static bool INumberBase<Fraction>.TryConvertToSaturating<TOther>(Fraction value, out TOther result) => TryConvertTo(value, out result);
static bool INumberBase<Fraction>.TryConvertToTruncating<TOther>(Fraction value, out TOther result) => TryConvertTo(value, out result);
#endif
public static (int[] nums, int[] dens) SplitArray(IEnumerable<Fraction> 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<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;
}
#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 implicit operator double(Fraction frac) => frac.GetValue();
public static explicit operator Fraction(double num) => Approximate(num); public static explicit operator Fraction(double num) => Approximate(num);

View File

@ -1,11 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<!-- General stuff -->
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;netcoreapp3.0;net5.0;net7.0</TargetFrameworks> <TargetFrameworks>netstandard1.1;netstandard1.3;netstandard2.1;netcoreapp3.0;net5.0;net7.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly> <ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<DebugType>embedded</DebugType> <DebugType>portable</DebugType>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
</PropertyGroup>
<!-- NuGet package customization. -->
<PropertyGroup>
<Title>Nerd_STF</Title> <Title>Nerd_STF</Title>
<Version>3.0.0-beta2</Version> <Version>3.0.0-beta2</Version>
<Authors>That_One_Nerd</Authors> <Authors>That_One_Nerd</Authors>
@ -14,7 +21,12 @@
<PackageIcon>Logo Square.png</PackageIcon> <PackageIcon>Logo Square.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/That-One-Nerd/Nerd_STF</RepositoryUrl> <RepositoryUrl>https://github.com/That-One-Nerd/Nerd_STF</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<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> <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-beta1 <PackageReleaseNotes># 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. 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)!</PackageReleaseNotes> 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)!</PackageReleaseNotes>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup> </PropertyGroup>
<!-- ItemGroup customization based on framework. -->
<!-- Mostly used to reference system packages that are not included in this version of .NET Standard. -->
<!-- TODO: Maybe this isn't good practice, and we should define environment variables for specific features (tuples, drawing, etc) instead? -->
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.1'">
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.3'">
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" />
<PackageReference Include="System.Memory" Version="4.5.5" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<!-- PropertyGroup customization based on framework. -->
<!-- Used to define environment variables based on features the framework supports. -->
<PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'"> <PropertyGroup Condition="'$(TargetFramework)'=='netstandard2.1'">
<DefineConstants>$(DefineConstants);CS8_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS8_OR_GREATER;SPAN_SUPPORT</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='netcoreapp3.0'">
<DefineConstants>$(DefineConstants);CS8_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS8_OR_GREATER;SPAN_SUPPORT</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net5.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='net5.0'">
<DefineConstants>$(DefineConstants);CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS8_OR_GREATER;CS9_OR_GREATER;SPAN_SUPPORT</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='net7.0'"> <PropertyGroup Condition="'$(TargetFramework)'=='net7.0'">
<DefineConstants>$(DefineConstants);CS10_OR_GREATER;CS11_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER</DefineConstants> <DefineConstants>$(DefineConstants);CS10_OR_GREATER;CS11_OR_GREATER;CS8_OR_GREATER;CS9_OR_GREATER;SPAN_SUPPORT</DefineConstants>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.1|AnyCPU'"> <!-- Pack extra stuff into the NuGet package. -->
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.3|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.1|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netcoreapp3.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net5.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net7.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.1|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.1|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp3.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net5.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net7.0|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<None Include="..\Extras\Logo Square.png"> <None Include="..\Extras\Logo Square.png">
@ -205,11 +187,14 @@ Anyway, that's most of the big changes! I don't know if I'll do the full changel
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
</ItemGroup> <None Include="..\Changelog.md">
<Pack>True</Pack>
<ItemGroup> <PackagePath>\</PackagePath>
<PackageReference Include="System.Drawing.Primitives" Version="4.3.0" /> </None>
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> <None Include="..\LICENSE.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup> </ItemGroup>
</Project> </Project>