Skip to content

Commit

Permalink
Implement a different decimal to fraction algorithm for UFraction32 a…
Browse files Browse the repository at this point in the history
…nd have GPSLatitudeLongitude use doubles to preserve coordinate accuracy when writing fractional second values. This fixes oozcitak#103 and presumably the cause oozcitak#99.
  • Loading branch information
RudyTheDev committed Aug 10, 2022
1 parent a5b3478 commit 93058b4
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 53 deletions.
12 changes: 6 additions & 6 deletions ExifLibrary/ExifExtendedProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,15 @@ public class GPSLatitudeLongitude : ExifURationalArray
public MathEx.UFraction32 Minutes { get { return mValue[1]; } set { mValue[1] = value; } }
public MathEx.UFraction32 Seconds { get { return mValue[2]; } set { mValue[2] = value; } }

public static explicit operator float(GPSLatitudeLongitude obj) { return obj.ToFloat(); }
public float ToFloat()
public static explicit operator double(GPSLatitudeLongitude obj) { return obj.ToDouble(); }
public double ToDouble()
{
return (float)Degrees + ((float)Minutes) / 60.0f + ((float)Seconds) / 3600.0f;
return (double)Degrees + ((double)Minutes) / 60.0f + ((double)Seconds) / 3600.0f;
}

public override string ToString()
{
return string.Format("{0:F2}°{1:F2}'{2:F2}\"", (float)Degrees, (float)Minutes, (float)Seconds);
return string.Format("{0:F2}°{1:F2}'{2:F6}\"", (double)Degrees, (double)Minutes, (double)Seconds);
}

public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value)
Expand All @@ -381,8 +381,8 @@ public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value)
;
}

public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s)
: base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) })
public GPSLatitudeLongitude(ExifTag tag, double d, double m, double s, double accuracy = 0.000001)
: base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d, accuracy), new MathEx.UFraction32(m, accuracy), new MathEx.UFraction32(s, accuracy) })
{
;
}
Expand Down
90 changes: 43 additions & 47 deletions ExifLibrary/MathEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,8 +1083,8 @@ public UFraction32(float value)
;
}

public UFraction32(double value)
: this(FromDouble(value))
public UFraction32(double value, double accuracy = 0.000001)
: this(FromDouble(value, accuracy))
{
;
}
Expand Down Expand Up @@ -1283,59 +1283,55 @@ public int CompareTo(UFraction32 obj)
/// </summary>
/// <param name="value">The floating-point number to be converted.</param>
/// <returns>The rational representation of value.</returns>
private static UFraction32 FromDouble(double value)
/// <remarks>From https://stackoverflow.com/a/42085412/8047867</remarks>
private static UFraction32 FromDouble(double value, double accuracy = 0.000001)
{
if (value < 0)
throw new ArgumentException("value cannot be negative.", "value");
if (accuracy <= 0.0 || accuracy >= 1.0)
{
throw new ArgumentOutOfRangeException(nameof(accuracy), "Must be > 0 and < 1.");
}

if (double.IsNaN(value))
return UFraction32.NaN;
else if (double.IsInfinity(value))
return UFraction32.Infinity;
int sign = Math.Sign(value);

double f = value;
double forg = f;
uint lnum = 0;
uint lden = 1;
uint num = 1;
uint den = 0;
double lasterr = 1.0;
uint a = 0;
int currIteration = 0;
while (true)
if (sign == -1)
{
if (++currIteration > MaximumIterations) break;
value = Math.Abs(value);
}

a = (uint)Math.Floor(f);
f = f - (double)a;
if (Math.Abs(f) < double.Epsilon)
break;
f = 1.0 / f;
if (double.IsInfinity(f))
break;
uint cnum = num * a + lnum;
uint cden = den * a + lden;
if (Math.Abs((double)cnum / (double)cden - forg) < double.Epsilon)
break;
double err = ((double)cnum / (double)cden - (double)num / (double)den) / ((double)num / (double)den);
// Are we converging?
if (err >= lasterr)
break;
lasterr = err;
lnum = num;
lden = den;
num = cnum;
den = cden;
// Accuracy is the maximum relative error; convert to absolute maxError
double maxError = sign == 0 ? accuracy : value * accuracy;

int n = (int) Math.Floor(value);
value -= n;

if (value < maxError)
{
return new UFraction32((uint)(sign * n), (uint)1);
}
uint fnum = num * a + lnum;
uint fden = den * a + lden;

if (fden > 0)
lasterr = value - ((double)fnum / (double)fden);
else
lasterr = double.PositiveInfinity;
if (1 - maxError < value)
{
return new UFraction32((uint)(sign * (n + 1)), (uint)1);
}

double z = value;
int previousDenominator = 0;
int denominator = 1;
int numerator;

do
{
z = 1.0 / (z - (int) z);
int temp = denominator;
denominator = denominator * (int) z + previousDenominator;
previousDenominator = temp;
numerator = Convert.ToInt32(value * denominator);
}
while (Math.Abs(value - (double) numerator / denominator) > maxError && z != (int) z);

return new UFraction32(fnum, fden, lasterr);
return new UFraction32((uint)((n * denominator + numerator) * sign), (uint)denominator);

// todo: actual error not returned/stored
}

/// <summary>Converts the string representation of a fraction to a Fraction type.</summary>
Expand Down

0 comments on commit 93058b4

Please sign in to comment.