mirror of
https://github.com/n00mkrad/flowframes.git
synced 2025-12-15 16:07:45 +01:00
Much better fraction approximation
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Flowframes.MiscUtils;
|
||||
using System;
|
||||
|
||||
namespace Flowframes.Data
|
||||
{
|
||||
@@ -6,21 +7,21 @@ namespace Flowframes.Data
|
||||
{
|
||||
public long Numerator = 0;
|
||||
public long Denominator = 1;
|
||||
public static Fraction Zero = new Fraction(0, 0);
|
||||
public static Fraction Zero = new Fraction(0, 1);
|
||||
|
||||
public Fraction() { }
|
||||
|
||||
public Fraction(long numerator, long denominator)
|
||||
{
|
||||
this.Numerator = numerator;
|
||||
this.Denominator = denominator;
|
||||
Numerator = numerator;
|
||||
Denominator = denominator;
|
||||
|
||||
//If denominator negative...
|
||||
if (this.Denominator < 0)
|
||||
if (Denominator < 0)
|
||||
{
|
||||
//...move the negative up to the numerator
|
||||
this.Numerator = -this.Numerator;
|
||||
this.Denominator = -this.Denominator;
|
||||
Numerator = -Numerator;
|
||||
Denominator = -Denominator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,11 +33,10 @@ namespace Flowframes.Data
|
||||
|
||||
public Fraction(float value)
|
||||
{
|
||||
Numerator = (value * 10000f).RoundToInt();
|
||||
Denominator = 10000;
|
||||
var reducedFrac = GetReduced();
|
||||
Numerator = reducedFrac.Numerator;
|
||||
Denominator = reducedFrac.Denominator;
|
||||
int maxDigits = 4;
|
||||
var (num, den) = FractionHelper.FloatToApproxFraction(value, maxDigits);
|
||||
Numerator = num;
|
||||
Denominator = den;
|
||||
}
|
||||
|
||||
public Fraction(string text)
|
||||
@@ -72,7 +72,7 @@ namespace Flowframes.Data
|
||||
Numerator = floatFrac.Numerator;
|
||||
Denominator = floatFrac.Denominator;
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,16 +129,16 @@ namespace Flowframes.Data
|
||||
Fraction modifiedFraction = this;
|
||||
|
||||
//Cannot reduce to smaller denominators
|
||||
if (targetDenominator < this.Denominator)
|
||||
if (targetDenominator < Denominator)
|
||||
return modifiedFraction;
|
||||
|
||||
//The target denominator must be a factor of the current denominator
|
||||
if (targetDenominator % this.Denominator != 0)
|
||||
if (targetDenominator % Denominator != 0)
|
||||
return modifiedFraction;
|
||||
|
||||
if (this.Denominator != targetDenominator)
|
||||
if (Denominator != targetDenominator)
|
||||
{
|
||||
long factor = targetDenominator / this.Denominator;
|
||||
long factor = targetDenominator / Denominator;
|
||||
modifiedFraction.Denominator = targetDenominator;
|
||||
modifiedFraction.Numerator *= factor;
|
||||
}
|
||||
@@ -165,8 +165,8 @@ namespace Flowframes.Data
|
||||
//Make sure only a single negative sign is on the numerator
|
||||
if (modifiedFraction.Denominator < 0)
|
||||
{
|
||||
modifiedFraction.Numerator = -this.Numerator;
|
||||
modifiedFraction.Denominator = -this.Denominator;
|
||||
modifiedFraction.Numerator = -Numerator;
|
||||
modifiedFraction.Denominator = -Denominator;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -180,7 +180,7 @@ namespace Flowframes.Data
|
||||
public Fraction GetReciprocal()
|
||||
{
|
||||
//Flip the numerator and the denominator
|
||||
return new Fraction(this.Denominator, this.Numerator);
|
||||
return new Fraction(Denominator, Numerator);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -496,6 +496,7 @@
|
||||
<Compile Include="Media\HwEncCheck.cs" />
|
||||
<Compile Include="Media\TimestampUtils.cs" />
|
||||
<Compile Include="MiscUtils\Benchmarker.cs" />
|
||||
<Compile Include="MiscUtils\FractionHelper.cs" />
|
||||
<Compile Include="MiscUtils\FrameRename.cs" />
|
||||
<Compile Include="MiscUtils\ModelDownloadFormUtils.cs" />
|
||||
<Compile Include="MiscUtils\NmkdStopwatch.cs" />
|
||||
|
||||
94
CodeLegacy/MiscUtils/FractionHelper.cs
Normal file
94
CodeLegacy/MiscUtils/FractionHelper.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
|
||||
namespace Flowframes.MiscUtils
|
||||
{
|
||||
internal class FractionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a float (<paramref name="value"/>) to an approximated fraction that is as close to the original value as possible, with a limit on the number of digits for numerator and denominator (<paramref name="maxDigits"/>).
|
||||
/// </summary>
|
||||
public static (int Numerator, int Denominator) FloatToApproxFraction(float value, int maxDigits = 4)
|
||||
{
|
||||
if (float.IsNaN(value) || float.IsInfinity(value))
|
||||
throw new ArgumentException("Value must be a finite float.");
|
||||
|
||||
// Special case: zero
|
||||
if (Math.Abs(value) < float.Epsilon)
|
||||
return (0, 1);
|
||||
|
||||
// Determine the sign and work with absolute value for searching.
|
||||
int sign = Math.Sign(value);
|
||||
double target = Math.Abs((double)value);
|
||||
|
||||
// Upper bound for numerator/denominator based on max digits
|
||||
// e.g. if maxDigits = 4, limit = 9999
|
||||
int limit = (int)Math.Pow(10, maxDigits) - 1;
|
||||
|
||||
// We'll track the best fraction found
|
||||
double bestError = double.MaxValue;
|
||||
int bestNum = 0;
|
||||
int bestDen = 1;
|
||||
|
||||
// Simple brute-force search over all possible denominators
|
||||
for (int d = 1; d <= limit; d++)
|
||||
{
|
||||
// Round the numerator for the current denominator
|
||||
int n = (int)Math.Round(target * d);
|
||||
|
||||
// If n is 0, skip (except the value might be < 0.5/d, but continue searching)
|
||||
if (n == 0)
|
||||
continue;
|
||||
|
||||
// If the numerator exceeds the limit, skip
|
||||
if (n > limit)
|
||||
continue;
|
||||
|
||||
// Evaluate how close n/d is to the target
|
||||
double fractionValue = (double)n / d;
|
||||
double error = Math.Abs(fractionValue - target);
|
||||
|
||||
// If it's closer, record it as our best
|
||||
if (error < bestError)
|
||||
{
|
||||
bestError = error;
|
||||
bestNum = n;
|
||||
bestDen = d;
|
||||
}
|
||||
}
|
||||
|
||||
// Reapply the sign to the numerator
|
||||
bestNum *= sign;
|
||||
|
||||
// Reduce fraction by GCD (to get simplest form)
|
||||
int gcd = GCD(bestNum, bestDen);
|
||||
bestNum /= gcd;
|
||||
bestDen /= gcd;
|
||||
|
||||
// If the denominator is 1 after reduction, just return the integer
|
||||
if (bestDen == 1)
|
||||
{
|
||||
return (bestNum, 1);
|
||||
}
|
||||
|
||||
// Otherwise return "numerator/denominator"
|
||||
Logger.Log($"Approximated fraction for {value}: {bestNum}/{bestDen} (={((float)bestNum / bestDen).ToString("0.0#######")})", true);
|
||||
return (bestNum, bestDen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the greatest common divisor (Euclid's algorithm).
|
||||
/// </summary>
|
||||
private static int GCD(int a, int b)
|
||||
{
|
||||
a = Math.Abs(a);
|
||||
b = Math.Abs(b);
|
||||
while (b != 0)
|
||||
{
|
||||
int t = b;
|
||||
b = a % b;
|
||||
a = t;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user