SHA256
69 lines
2.9 KiB
C#
69 lines
2.9 KiB
C#
using System;
|
|
|
|
namespace ADOFAIRenderer.Util
|
|
{
|
|
/// <summary>Conversion of floating-point rates (frame rates) to integer fractions.</summary>
|
|
internal static class RationalUtil
|
|
{
|
|
/// <summary>
|
|
/// Best rational approximation Numerator / Denominator of <paramref name="value"/> by continued fraction expansion:
|
|
/// the last convergent whose numerator fits in int and whose denominator does not exceed
|
|
/// <paramref name="maxDenominator"/>. Exact for values with a short expansion (for example 60 gives 60/1). The sign is
|
|
/// carried by the numerator; 0 gives 0/1. A magnitude above int.MaxValue has no valid convergent and gives 1/0.
|
|
/// Thread-safe (pure function).
|
|
/// </summary>
|
|
/// <param name="value">Value to convert, for example a frame rate in frames per second.</param>
|
|
/// <param name="maxDenominator">Largest allowed denominator (default 100000000).</param>
|
|
/// <returns>Numerator and denominator (positive for magnitudes up to int.MaxValue).</returns>
|
|
public static (int Numerator, int Denominator) Double2MaxPrecisionInt(double value,
|
|
int maxDenominator = 100000000)
|
|
{
|
|
// 1. Edge case handling and sign detection
|
|
if (value == 0.0) return (0, 1);
|
|
|
|
var isNegative = value < 0;
|
|
var target = Math.Abs(value);
|
|
// 2. Stern-Brocot / continued fraction boundary initialization
|
|
// Starts between (0/1, 1/0) and converges towards the target value.
|
|
long m00 = 0, m01 = 1;
|
|
long m10 = 1, m11 = 0;
|
|
|
|
var x = target;
|
|
|
|
// Iterate while preventing integer overflow
|
|
while (true)
|
|
{
|
|
var ai = (long)Math.Floor(x);
|
|
|
|
// Calculate next numerator and denominator (temporarily computed as long to prevent overflow)
|
|
var nextNumerator = ai * m10 + m00;
|
|
var nextDenominator = ai * m11 + m01;
|
|
|
|
// If exceeding integer range or specified maximum denominator, return previous optimal value
|
|
if (nextNumerator > int.MaxValue || nextDenominator > maxDenominator)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Update matrix
|
|
m00 = m10; m01 = m11;
|
|
m10 = nextNumerator; m11 = nextDenominator;
|
|
|
|
// Terminate if error is negligible (precision limit reached)
|
|
if (Math.Abs(x - ai) < 1e-15) break;
|
|
|
|
// Prepare next continued fraction term
|
|
x = 1.0 / (x - ai);
|
|
}
|
|
|
|
// 3. Final determined integer numerator and denominator
|
|
var finalNumerator = (int)m10;
|
|
var finalDenominator = (int)m11;
|
|
|
|
// Restore sign
|
|
if (isNegative) finalNumerator = -finalNumerator;
|
|
|
|
return (finalNumerator, finalDenominator);
|
|
}
|
|
}
|
|
} |