diff --git a/src/ImageProcessorCore/Numerics/BigRational.cs b/src/ImageProcessorCore/Numerics/BigRational.cs
new file mode 100644
index 0000000000..9659796740
--- /dev/null
+++ b/src/ImageProcessorCore/Numerics/BigRational.cs
@@ -0,0 +1,219 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Text;
+
+ internal struct BigRational : IEquatable
+ {
+ private bool IsIndeterminate
+ {
+ get
+ {
+ if (Denominator != 0)
+ return false;
+
+ return Numerator == 0;
+ }
+ }
+
+ private bool IsInteger => Denominator == 1;
+
+ private bool IsNegativeInfinity
+ {
+ get
+ {
+ if (Denominator != 0)
+ return false;
+
+ return Numerator == -1;
+ }
+ }
+
+ private bool IsPositiveInfinity
+ {
+ get
+ {
+ if (Denominator != 0)
+ return false;
+
+ return Numerator == 1;
+ }
+ }
+
+ private bool IsZero
+ {
+ get
+ {
+ if (Denominator != 1)
+ return false;
+
+ return Numerator == 0;
+ }
+ }
+
+ private static long GreatestCommonDivisor(long a, long b)
+ {
+ return b == 0 ? a : GreatestCommonDivisor(b, a % b);
+ }
+
+ private void Simplify()
+ {
+ if (IsIndeterminate)
+ return;
+
+ if (IsNegativeInfinity)
+ return;
+
+ if (IsPositiveInfinity)
+ return;
+
+ if (IsInteger)
+ return;
+
+ if (IsZero)
+ return;
+
+ if (Numerator == 0)
+ {
+ Denominator = 0;
+ return;
+ }
+
+ if (Numerator == Denominator)
+ {
+ Numerator = 1;
+ Denominator = 1;
+ }
+
+ long gcd = GreatestCommonDivisor(Math.Abs(Numerator), Math.Abs(Denominator));
+ if (gcd > 1)
+ {
+ Numerator = Numerator / gcd;
+ Denominator = Denominator / gcd;
+ }
+ }
+
+ public BigRational(long numerator, long denominator)
+ : this(numerator, denominator, false)
+ {
+ }
+
+ public BigRational(long numerator, long denominator, bool simplify)
+ {
+ Numerator = numerator;
+ Denominator = denominator;
+
+ if (simplify)
+ Simplify();
+ }
+
+ public BigRational(double value, bool bestPrecision)
+ {
+ if (double.IsNaN(value))
+ {
+ Numerator = Denominator = 0;
+ return;
+ }
+
+ if (double.IsPositiveInfinity(value))
+ {
+ Numerator = 1;
+ Denominator = 0;
+ return;
+ }
+
+ if (double.IsNegativeInfinity(value))
+ {
+ Numerator = -1;
+ Denominator = 0;
+ return;
+ }
+
+ Numerator = 1;
+ Denominator = 1;
+
+ double val = Math.Abs(value);
+ double df = Numerator / Denominator;
+ double epsilon = bestPrecision ? double.Epsilon : .000001;
+
+ while (Math.Abs(df - val) > epsilon)
+ {
+ if (df < val)
+ Numerator++;
+ else
+ {
+ Denominator++;
+ Numerator = (int)(val * Denominator);
+ }
+
+ df = Numerator / (double)Denominator;
+ }
+
+ if (value < 0.0)
+ Numerator *= -1;
+
+ Simplify();
+ }
+
+ public long Denominator
+ {
+ get;
+ private set;
+ }
+
+ public long Numerator
+ {
+ get;
+ private set;
+ }
+
+ public bool Equals(BigRational other)
+ {
+ if (Denominator == other.Denominator)
+ return Numerator == other.Numerator;
+
+ if (Numerator == 0 && Denominator == 0)
+ return other.Numerator == 0 && other.Denominator == 0;
+
+ if (other.Numerator == 0 && other.Denominator == 0)
+ return Numerator == 0 && Denominator == 0;
+
+ return (Numerator * other.Denominator) == (Denominator * other.Numerator);
+ }
+
+ public override int GetHashCode()
+ {
+ return ((Numerator * 397) ^ Denominator).GetHashCode();
+ }
+
+ public string ToString(IFormatProvider provider)
+ {
+ if (IsIndeterminate)
+ return "[ Indeterminate ]";
+
+ if (IsPositiveInfinity)
+ return "[ PositiveInfinity ]";
+
+ if (IsNegativeInfinity)
+ return "[ NegativeInfinity ]";
+
+ if (IsZero)
+ return "0";
+
+ if (IsInteger)
+ return Numerator.ToString(provider);
+
+ StringBuilder sb = new StringBuilder();
+ sb.Append(Numerator.ToString(provider));
+ sb.Append("/");
+ sb.Append(Denominator.ToString(provider));
+
+ return sb.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Numerics/Rational.cs b/src/ImageProcessorCore/Numerics/Rational.cs
index 090af6e3ca..1de71fa860 100644
--- a/src/ImageProcessorCore/Numerics/Rational.cs
+++ b/src/ImageProcessorCore/Numerics/Rational.cs
@@ -7,11 +7,9 @@ namespace ImageProcessorCore
{
using System;
using System.Globalization;
- using System.Numerics;
- using System.Text;
///
- /// Represents a number that can be expressed as a fraction
+ /// Represents a number that can be expressed as a fraction.
///
///
/// This is a very simplified implimentation of a rational number designed for use with metadata only.
@@ -19,532 +17,183 @@ namespace ImageProcessorCore
public struct Rational : IEquatable
{
///
- /// Represents a rational object that is not a number; NaN (0, 0)
+ /// Initializes a new instance of the struct.
///
- public static Rational Indeterminate = new Rational(BigInteger.Zero);
-
- ///
- /// Represents a rational object that is equal to 0 (0, 1)
- ///
- public static Rational Zero = new Rational(BigInteger.Zero);
-
- ///
- /// Represents a rational object that is equal to 1 (1, 1)
- ///
- public static Rational One = new Rational(BigInteger.One);
-
- ///
- /// Represents a Rational object that is equal to negative infinity (-1, 0)
- ///
- public static readonly Rational NegativeInfinity = new Rational(BigInteger.MinusOne, BigInteger.Zero);
-
- ///
- /// Represents a Rational object that is equal to positive infinity (1, 0)
- ///
- public static readonly Rational PositiveInfinity = new Rational(BigInteger.One, BigInteger.Zero);
-
- ///
- /// The maximum number of decimal places
- ///
- private const int DoubleMaxScale = 308;
-
- ///
- /// The maximum precision (numbers after the decimal point)
- ///
- private static readonly BigInteger DoublePrecision = BigInteger.Pow(10, DoubleMaxScale);
-
- ///
- /// Represents double.MaxValue
- ///
- private static readonly BigInteger DoubleMaxValue = (BigInteger)double.MaxValue;
-
- ///
- /// Represents double.MinValue
- ///
- private static readonly BigInteger DoubleMinValue = (BigInteger)double.MinValue;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- ///
- /// The number above the line in a vulgar fraction showing how many of the parts
- /// indicated by the denominator are taken.
- ///
- ///
- /// The number below the line in a vulgar fraction; a divisor.
- ///
- public Rational(uint numerator, uint denominator)
- : this()
- {
- this.Numerator = numerator;
- this.Denominator = denominator;
-
- this.Simplify();
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- ///
- /// The number above the line in a vulgar fraction showing how many of the parts
- /// indicated by the denominator are taken.
- ///
- ///
- /// The number below the line in a vulgar fraction; a divisor.
- ///
- public Rational(int numerator, int denominator)
- : this()
+ ///The to convert to an instance of this type.
+ public Rational(double value)
+ : this(value, false)
{
- this.Numerator = numerator;
- this.Denominator = denominator;
-
- this.Simplify();
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
- ///
- /// The number above the line in a vulgar fraction showing how many of the parts
- /// indicated by the denominator are taken.
- ///
- ///
- /// The number below the line in a vulgar fraction; a divisor.
- ///
- public Rational(BigInteger numerator, BigInteger denominator)
- : this()
+ ///The to convert to an instance of this type.
+ ///Specifies if the instance should be created with the best precision possible.
+ public Rational(double value, bool bestPrecision)
{
- this.Numerator = numerator;
- this.Denominator = denominator;
+ BigRational rational = new BigRational(Math.Abs(value), bestPrecision);
- this.Simplify();
+ Numerator = (uint)rational.Numerator;
+ Denominator = (uint)rational.Denominator;
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
- /// The big integer to create the rational from.
- public Rational(BigInteger value)
- : this()
+ /// The integer to create the rational from.
+ public Rational(uint value)
+ : this(value, 1)
{
- this.Numerator = value;
- this.Denominator = BigInteger.One;
-
- this.Simplify();
}
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the struct.
///
- /// The double to create the rational from.
- public Rational(double value)
- : this()
- {
- if (double.IsNaN(value))
- {
- this = Indeterminate;
- return;
- }
-
- if (double.IsPositiveInfinity(value))
- {
- this = PositiveInfinity;
- return;
- }
-
- if (double.IsNegativeInfinity(value))
- {
- this = NegativeInfinity;
- return;
- }
-
- // TODO: Not happy with parsing a string like this. I should be able to use maths but maths is HARD!
- this = Parse(value.ToString("R", CultureInfo.InvariantCulture));
- }
-
- ///
- /// Gets the numerator of a number.
- ///
- public BigInteger Numerator { get; private set; }
-
- ///
- /// Gets the denominator of a number.
- ///
- public BigInteger Denominator { get; private set; }
-
- ///
- /// Gets a value indicating whether this instance is indeterminate.
- ///
- public bool IsIndeterminate
+ /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.
+ /// The number below the line in a vulgar fraction; a divisor.
+ public Rational(uint numerator, uint denominator)
+ : this(numerator, denominator, true)
{
- get
- {
- if (this.Denominator != BigInteger.Zero)
- {
- return false;
- }
-
- return this.Numerator == BigInteger.Zero;
- }
}
///
- /// Gets a value indicating whether this instance is an integer (n, 1)
+ /// Initializes a new instance of the struct.
///
- public bool IsInteger => this.Denominator == BigInteger.One;
-
- ///
- /// Gets a value indicating whether this instance is equal to 0 (0, 1)
- ///
- public bool IsZero
+ /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.
+ /// The number below the line in a vulgar fraction; a divisor.
+ /// Specified if the rational should be simplified.
+ public Rational(uint numerator, uint denominator, bool simplify)
{
- get
- {
- if (this.Denominator != BigInteger.One)
- {
- return false;
- }
+ BigRational rational = new BigRational(numerator, denominator, simplify);
- return this.Numerator == BigInteger.Zero;
- }
+ Numerator = (uint)rational.Numerator;
+ Denominator = (uint)rational.Denominator;
}
///
- /// Gets a value indicating whether this instance is equal to 1 (1, 1)
+ /// Determines whether the specified instances are considered equal.
///
- public bool IsOne
+ /// The first to compare.
+ /// The second to compare.
+ ///
+ public static bool operator ==(Rational left, Rational right)
{
- get
- {
- if (this.Denominator != BigInteger.One)
- {
- return false;
- }
-
- return this.Numerator == BigInteger.One;
- }
+ return Equals(left, right);
}
///
- /// Gets a value indicating whether this instance is equal to negative infinity (-1, 0)
+ /// Determines whether the specified instances are not considered equal.
///
- public bool IsNegativeInfinity
+ /// The first to compare.
+ /// The second to compare.
+ ///
+ public static bool operator !=(Rational left, Rational right)
{
- get
- {
- if (this.Denominator != BigInteger.Zero)
- {
- return false;
- }
-
- return this.Numerator == BigInteger.MinusOne;
- }
+ return !Equals(left, right);
}
///
- /// Gets a value indicating whether this instance is equal to positive infinity (1, 0)
+ /// Gets the numerator of a number.
///
- public bool IsPositiveInfinity
+ public uint Numerator
{
- get
- {
- if (this.Denominator != BigInteger.Zero)
- {
- return false;
- }
-
- return this.Numerator == BigInteger.One;
- }
+ get;
+ private set;
}
///
- /// Converts a rational number to the nearest double.
+ /// Gets the denominator of a number.
///
- ///
- /// The .
- ///
- public double ToDouble()
+ public uint Denominator
{
- // Shortcut return values
- if (this.IsIndeterminate)
- {
- return double.NaN;
- }
-
- if (this.IsPositiveInfinity)
- {
- return double.PositiveInfinity;
- }
-
- if (this.IsNegativeInfinity)
- {
- return double.NegativeInfinity;
- }
-
- if (this.IsInteger)
- {
- return (double)this.Numerator;
- }
-
- // The Double value type represents a double-precision 64-bit number with
- // values ranging from -1.79769313486232e308 to +1.79769313486232e308
- // values that do not fit into this range are returned as +/-Infinity
- if (SafeCastToDouble(this.Numerator) && SafeCastToDouble(this.Denominator))
- {
- return (double)this.Numerator / (double)this.Denominator;
- }
-
- // Scale the numerator to preserve the fraction part through the integer division
- // We could probably adjust this to make it less precise if need be.
- BigInteger denormalized = (this.Numerator * DoublePrecision) / this.Denominator;
- if (denormalized.IsZero)
- {
- // underflow to -+0
- return (this.Numerator.Sign < 0) ? BitConverter.Int64BitsToDouble(unchecked((long)0x8000000000000000)) : 0d;
- }
-
- double result = 0;
- bool isDouble = false;
- int scale = DoubleMaxScale;
-
- while (scale > 0)
- {
- if (!isDouble)
- {
- if (SafeCastToDouble(denormalized))
- {
- result = (double)denormalized;
- isDouble = true;
- }
- else
- {
- denormalized = denormalized / 10;
- }
- }
-
- result = result / 10;
- scale--;
- }
-
- if (!isDouble)
- {
- return (this.Numerator.Sign < 0) ? double.NegativeInfinity : double.PositiveInfinity;
- }
- else
- {
- return result;
- }
+ get;
+ private set;
}
- ///
+ ///
+ /// Determines whether the specified is equal to this .
+ ///
+ ///The to compare this with.
public override bool Equals(object obj)
{
if (obj is Rational)
- {
- return this.Equals((Rational)obj);
- }
+ return Equals((Rational)obj);
return false;
}
- ///
+ ///
+ /// Determines whether the specified is equal to this .
+ ///
+ ///The to compare this with.
public bool Equals(Rational other)
{
- // Standard: a/b = c/d
- if (this.Denominator == other.Denominator)
- {
- return this.Numerator == other.Numerator;
- }
-
- // Indeterminate
- if (this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero)
- {
- return other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero;
- }
-
- if (other.Numerator == BigInteger.Zero && other.Denominator == BigInteger.Zero)
- {
- return this.Numerator == BigInteger.Zero && this.Denominator == BigInteger.Zero;
- }
+ BigRational left = new BigRational(Numerator, Denominator);
+ BigRational right = new BigRational(other.Numerator, other.Denominator);
- // ad = bc
- return (this.Numerator * other.Denominator) == (this.Denominator * other.Numerator);
- }
-
- ///
- public override int GetHashCode()
- {
- return this.GetHashCode(this);
+ return left.Equals(right);
}
- ///
- public override string ToString()
+ ///
+ /// Converts the specified to an instance of this type.
+ ///
+ ///The to convert to an instance of this type.
+ public static Rational FromDouble(double value)
{
- return this.ToString(CultureInfo.InvariantCulture);
+ return new Rational(value, false);
}
- ///
- /// Converts the numeric value of this instance to its equivalent string representation using
- /// the specified culture-specific format information.
- ///
- ///
- /// An object that supplies culture-specific formatting information.
- ///
- ///
- public string ToString(IFormatProvider provider)
+ ///
+ /// Converts the specified to an instance of this type.
+ ///
+ ///The to convert to an instance of this type.
+ ///Specifies if the instance should be created with the best precision possible.
+ public static Rational FromDouble(double value, bool bestPrecision)
{
- if (this.IsIndeterminate)
- {
- return "[ Indeterminate ]";
- }
-
- if (this.IsPositiveInfinity)
- {
- return "[ PositiveInfinity ]";
- }
-
- if (this.IsNegativeInfinity)
- {
- return "[ NegativeInfinity ]";
- }
-
- if (this.IsZero)
- {
- return "0";
- }
-
- if (this.IsInteger)
- {
- return this.Numerator.ToString(provider);
- }
-
- StringBuilder sb = new StringBuilder();
- sb.Append(this.Numerator.ToString("R", provider));
- sb.Append("/");
- sb.Append(this.Denominator.ToString("R", provider));
- return sb.ToString();
+ return new Rational(value, bestPrecision);
}
- ///
- /// Simplifies the rational.
- ///
- private void Simplify()
+ ///
+ /// Serves as a hash of this type.
+ ///
+ public override int GetHashCode()
{
- if (this.IsIndeterminate)
- {
- return;
- }
-
- if (this.IsNegativeInfinity)
- {
- return;
- }
-
- if (this.IsPositiveInfinity)
- {
- return;
- }
-
- if (this.IsInteger)
- {
- return;
- }
-
- if (this.Numerator == BigInteger.Zero)
- {
- this.Denominator = BigInteger.One;
- return;
- }
-
- if (this.Numerator == this.Denominator)
- {
- this.Numerator = BigInteger.One;
- this.Denominator = BigInteger.One;
- return;
- }
-
- BigInteger gcd = BigInteger.GreatestCommonDivisor(this.Numerator, this.Denominator);
- if (gcd > BigInteger.One)
- {
- this.Numerator = this.Numerator / gcd;
- this.Denominator = this.Denominator / gcd;
- }
+ BigRational self = new BigRational(Numerator, Denominator);
+ return self.GetHashCode();
}
///
- /// Converts the string representation of a number into its rational value
+ /// Converts a rational number to the nearest .
///
- /// A string that contains a number to convert.
- /// The
- internal static Rational Parse(string value)
+ ///
+ /// The .
+ ///
+ public double ToDouble()
{
- int periodIndex = value.IndexOf(".", StringComparison.Ordinal);
- int eIndex = value.IndexOf("E", StringComparison.Ordinal);
- int slashIndex = value.IndexOf("/", StringComparison.Ordinal);
-
- // An integer such as 3
- if (periodIndex == -1 && eIndex == -1 && slashIndex == -1)
- {
- return new Rational(BigInteger.Parse(value));
- }
-
- // A fraction such as 22/7
- if (periodIndex == -1 && eIndex == -1 && slashIndex != -1)
- {
- return new Rational(BigInteger.Parse(value.Substring(0, slashIndex)),
- BigInteger.Parse(value.Substring(slashIndex + 1)));
- }
-
- // No scientific Notation such as 3.14159
- if (eIndex == -1)
- {
- BigInteger n = BigInteger.Parse(value.Replace(".", string.Empty));
- BigInteger d = (BigInteger)Math.Pow(10, value.Length - periodIndex - 1);
- return new Rational(n, d);
- }
-
- // Scientific notation such as 3.14159E-2
- int characteristic = int.Parse(value.Substring(eIndex + 1));
- BigInteger ten = 10;
- BigInteger numerator = BigInteger.Parse(value.Substring(0, eIndex).Replace(".", string.Empty));
- BigInteger denominator = new BigInteger(Math.Pow(10, eIndex - periodIndex - 1));
- BigInteger charPower = BigInteger.Pow(ten, Math.Abs(characteristic));
-
- if (characteristic > 0)
- {
- numerator = numerator * charPower;
- }
- else
- {
- denominator = denominator * charPower;
- }
-
- return new Rational(numerator, denominator);
+ return Numerator / (double)Denominator;
}
///
- /// Returns a value indicating whether the given big integer can be
- /// safely cast to a double.
+ /// Converts the numeric value of this instance to its equivalent string representation.
///
- /// The value to test.
- /// true if the value can be safely cast
- private static bool SafeCastToDouble(BigInteger value)
+ public override string ToString()
{
- return DoubleMinValue <= value && value <= DoubleMaxValue;
+ return ToString(CultureInfo.InvariantCulture);
}
///
- /// Returns the hash code for this instance.
+ /// Converts the numeric value of this instance to its equivalent string representation using
+ /// the specified culture-specific format information.
///
- ///
- /// The instance of to return the hash code for.
+ ///
+ /// An object that supplies culture-specific formatting information.
///
- ///
- /// A 32-bit signed integer that is the hash code for this instance.
- ///
- private int GetHashCode(Rational rational)
+ ///
+ public string ToString(IFormatProvider provider)
{
- return ((rational.Numerator * 397) ^ rational.Denominator).GetHashCode();
+ BigRational rational = new BigRational(Numerator, Denominator);
+ return rational.ToString(provider);
}
}
}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Numerics/SignedRational.cs b/src/ImageProcessorCore/Numerics/SignedRational.cs
new file mode 100644
index 0000000000..d216b30d4e
--- /dev/null
+++ b/src/ImageProcessorCore/Numerics/SignedRational.cs
@@ -0,0 +1,199 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore
+{
+ using System;
+ using System.Globalization;
+
+ ///
+ /// Represents a number that can be expressed as a fraction.
+ ///
+ ///
+ /// This is a very simplified implimentation of a rational number designed for use with metadata only.
+ ///
+ public struct SignedRational : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///The to convert to an instance of this type.
+ public SignedRational(double value)
+ : this(value, false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///The to convert to an instance of this type.
+ ///Specifies if the instance should be created with the best precision possible.
+ public SignedRational(double value, bool bestPrecision)
+ {
+ BigRational rational = new BigRational(value, bestPrecision);
+
+ Numerator = (int)rational.Numerator;
+ Denominator = (int)rational.Denominator;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The integer to create the rational from.
+ public SignedRational(int value)
+ : this(value, 1)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.
+ /// The number below the line in a vulgar fraction; a divisor.
+ public SignedRational(int numerator, int denominator)
+ : this(numerator, denominator, true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The number above the line in a vulgar fraction showing how many of the parts indicated by the denominator are taken.
+ /// The number below the line in a vulgar fraction; a divisor.
+ /// Specified if the rational should be simplified.
+ public SignedRational(int numerator, int denominator, bool simplify)
+ {
+ BigRational rational = new BigRational(numerator, denominator, simplify);
+
+ Numerator = (int)rational.Numerator;
+ Denominator = (int)rational.Denominator;
+ }
+
+ ///
+ /// Determines whether the specified instances are considered equal.
+ ///
+ /// The first to compare.
+ /// The second to compare.
+ ///
+ public static bool operator ==(SignedRational left, SignedRational right)
+ {
+ return Equals(left, right);
+ }
+
+ ///
+ /// Determines whether the specified instances are not considered equal.
+ ///
+ /// The first to compare.
+ /// The second to compare.
+ ///
+ public static bool operator !=(SignedRational left, SignedRational right)
+ {
+ return !Equals(left, right);
+ }
+
+ ///
+ /// Gets the numerator of a number.
+ ///
+ public int Numerator
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the denominator of a number.
+ ///
+ public int Denominator
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Determines whether the specified is equal to this .
+ ///
+ ///The to compare this with.
+ public override bool Equals(object obj)
+ {
+ if (obj is SignedRational)
+ return Equals((SignedRational)obj);
+
+ return false;
+ }
+
+ ///
+ /// Determines whether the specified is equal to this .
+ ///
+ ///The to compare this with.
+ public bool Equals(SignedRational other)
+ {
+ BigRational left = new BigRational(Numerator, Denominator);
+ BigRational right = new BigRational(other.Numerator, other.Denominator);
+
+ return left.Equals(right);
+ }
+
+ ///
+ /// Converts the specified to an instance of this type.
+ ///
+ ///The to convert to an instance of this type.
+ public static SignedRational FromDouble(double value)
+ {
+ return new SignedRational(value, false);
+ }
+
+ ///
+ /// Converts the specified to an instance of this type.
+ ///
+ ///The to convert to an instance of this type.
+ ///Specifies if the instance should be created with the best precision possible.
+ public static SignedRational FromDouble(double value, bool bestPrecision)
+ {
+ return new SignedRational(value, bestPrecision);
+ }
+
+ ///
+ /// Serves as a hash of this type.
+ ///
+ public override int GetHashCode()
+ {
+ BigRational self = new BigRational(Numerator, Denominator);
+ return self.GetHashCode();
+ }
+
+ ///
+ /// Converts a rational number to the nearest .
+ ///
+ ///
+ /// The .
+ ///
+ public double ToDouble()
+ {
+ return Numerator / (double)Denominator;
+ }
+
+ ///
+ /// Converts the numeric value of this instance to its equivalent string representation.
+ ///
+ public override string ToString()
+ {
+ return ToString(CultureInfo.InvariantCulture);
+ }
+
+ ///
+ /// Converts the numeric value of this instance to its equivalent string representation using
+ /// the specified culture-specific format information.
+ ///
+ ///
+ /// An object that supplies culture-specific formatting information.
+ ///
+ ///
+ public string ToString(IFormatProvider provider)
+ {
+ BigRational rational = new BigRational(Numerator, Denominator);
+ return rational.ToString(provider);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
index 333a00d0a7..546d5b846b 100644
--- a/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifReader.cs
@@ -442,13 +442,13 @@ namespace ImageProcessorCore
{
if (!this.ValidateArray(data, 8, 4))
{
- return Rational.Zero;
+ return new Rational();
}
uint numerator = BitConverter.ToUInt32(data, 0);
uint denominator = BitConverter.ToUInt32(data, 4);
- return new Rational(numerator, denominator);
+ return new Rational(numerator, denominator, false);
}
private sbyte ToSignedByte(byte[] data)
@@ -466,17 +466,17 @@ namespace ImageProcessorCore
return BitConverter.ToInt32(data, 0);
}
- private Rational ToSignedRational(byte[] data)
+ private SignedRational ToSignedRational(byte[] data)
{
if (!this.ValidateArray(data, 8, 4))
{
- return Rational.Zero;
+ return new SignedRational();
}
int numerator = BitConverter.ToInt32(data, 0);
int denominator = BitConverter.ToInt32(data, 4);
- return new Rational(numerator, denominator);
+ return new SignedRational(numerator, denominator, false);
}
private short ToSignedShort(byte[] data)
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs b/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
index 3fd195b4ea..a79bc85efc 100644
--- a/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifValue.cs
@@ -539,13 +539,12 @@ namespace ImageProcessorCore
case ExifDataType.DoubleFloat:
Guard.IsTrue(type == typeof(double), nameof(value), $"Value should be a double{(this.IsArray ? " array." : ".")}");
break;
- case ExifDataType.Rational:
- case ExifDataType.SignedRational:
- Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}");
- break;
case ExifDataType.Long:
Guard.IsTrue(type == typeof(uint), nameof(value), $"Value should be an unsigned int{(this.IsArray ? " array." : ".")}");
break;
+ case ExifDataType.Rational:
+ Guard.IsTrue(type == typeof(Rational), nameof(value), $"Value should be a Rational{(this.IsArray ? " array." : ".")}");
+ break;
case ExifDataType.Short:
Guard.IsTrue(type == typeof(ushort), nameof(value), $"Value should be an unsigned short{(this.IsArray ? " array." : ".")}");
break;
@@ -555,6 +554,9 @@ namespace ImageProcessorCore
case ExifDataType.SignedLong:
Guard.IsTrue(type == typeof(int), nameof(value), $"Value should be an int{(this.IsArray ? " array." : ".")}");
break;
+ case ExifDataType.SignedRational:
+ Guard.IsTrue(type == typeof(SignedRational), nameof(value), $"Value should be a SignedRational{(this.IsArray ? " array." : ".")}");
+ break;
case ExifDataType.SignedShort:
Guard.IsTrue(type == typeof(short), nameof(value), $"Value should be a short{(this.IsArray ? " array." : ".")}");
break;
diff --git a/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs b/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
index 269ce52c4b..e89b0d1a91 100644
--- a/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
+++ b/src/ImageProcessorCore/Profiles/Exif/ExifWriter.cs
@@ -329,17 +329,16 @@ namespace ImageProcessorCore
private int WriteRational(Rational value, byte[] destination, int offset)
{
- // Ensure no overflow
- Write(BitConverter.GetBytes((uint)(value.Numerator * (value.ToDouble() < 0.0 ? -1 : 1))), destination, offset);
- Write(BitConverter.GetBytes((uint)value.Denominator), destination, offset + 4);
+ Write(BitConverter.GetBytes(value.Numerator), destination, offset);
+ Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4);
return offset + 8;
}
- private int WriteSignedRational(Rational value, byte[] destination, int offset)
+ private int WriteSignedRational(SignedRational value, byte[] destination, int offset)
{
- Write(BitConverter.GetBytes((int)value.Numerator), destination, offset);
- Write(BitConverter.GetBytes((int)value.Denominator), destination, offset + 4);
+ Write(BitConverter.GetBytes(value.Numerator), destination, offset);
+ Write(BitConverter.GetBytes(value.Denominator), destination, offset + 4);
return offset + 8;
}
@@ -370,7 +369,7 @@ namespace ImageProcessorCore
case ExifDataType.SignedShort:
return Write(BitConverter.GetBytes((short)value), destination, offset);
case ExifDataType.SignedRational:
- return this.WriteSignedRational((Rational)value, destination, offset);
+ return this.WriteSignedRational((SignedRational)value, destination, offset);
case ExifDataType.SingleFloat:
return Write(BitConverter.GetBytes((float)value), destination, offset);
default:
diff --git a/tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs b/tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs
index 246349f82b..e83ec5118e 100644
--- a/tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs
+++ b/tests/ImageProcessorCore.Tests/Numerics/RationalTests.cs
@@ -22,6 +22,7 @@ namespace ImageProcessorCore.Tests
Rational r2 = new Rational(3, 2);
Assert.Equal(r1, r2);
+ Assert.True(r1 == r2);
Rational r3 = new Rational(7.55);
Rational r4 = new Rational(755, 100);
@@ -41,6 +42,7 @@ namespace ImageProcessorCore.Tests
Rational second = new Rational(100, 100);
Assert.NotEqual(first, second);
+ Assert.True(first != second);
}
///
@@ -49,9 +51,65 @@ namespace ImageProcessorCore.Tests
[Fact]
public void ConstructorAssignsProperties()
{
- Rational first = new Rational(4, 5);
- Assert.Equal(4, first.Numerator);
- Assert.Equal(5, first.Denominator);
+ Rational rational = new Rational(7, 55);
+ Assert.Equal(7U, rational.Numerator);
+ Assert.Equal(55U, rational.Denominator);
+
+ rational = new Rational(755, 100);
+ Assert.Equal(151U, rational.Numerator);
+ Assert.Equal(20U, rational.Denominator);
+
+ rational = new Rational(755, 100, false);
+ Assert.Equal(755U, rational.Numerator);
+ Assert.Equal(100U, rational.Denominator);
+
+ rational = new Rational(-7.55);
+ Assert.Equal(151U, rational.Numerator);
+ Assert.Equal(20U, rational.Denominator);
+
+ rational = new Rational(7);
+ Assert.Equal(7U, rational.Numerator);
+ Assert.Equal(1U, rational.Denominator);
+ }
+
+ [Fact]
+ public void Fraction()
+ {
+ Rational first = new Rational(1.0 / 1600);
+ Rational second = new Rational(1.0 / 1600, true);
+ Assert.False(first.Equals(second));
+ }
+
+ [Fact]
+ public void ToDouble()
+ {
+ Rational rational = new Rational(0, 0);
+ Assert.Equal(double.NaN, rational.ToDouble());
+
+ rational = new Rational(2, 0);
+ Assert.Equal(double.PositiveInfinity, rational.ToDouble());
+ }
+
+ [Fact]
+ public void ToStringRepresention()
+ {
+ Rational rational = new Rational(0, 0);
+ Assert.Equal("[ Indeterminate ]", rational.ToString());
+
+ rational = new Rational(double.PositiveInfinity);
+ Assert.Equal("[ PositiveInfinity ]", rational.ToString());
+
+ rational = new Rational(double.NegativeInfinity);
+ Assert.Equal("[ PositiveInfinity ]", rational.ToString());
+
+ rational = new Rational(0, 1);
+ Assert.Equal("0", rational.ToString());
+
+ rational = new Rational(2, 1);
+ Assert.Equal("2", rational.ToString());
+
+ rational = new Rational(1, 2);
+ Assert.Equal("1/2", rational.ToString());
}
}
}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Numerics/SignedRationalTests.cs b/tests/ImageProcessorCore.Tests/Numerics/SignedRationalTests.cs
new file mode 100644
index 0000000000..fff7f0fd34
--- /dev/null
+++ b/tests/ImageProcessorCore.Tests/Numerics/SignedRationalTests.cs
@@ -0,0 +1,122 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessorCore.Tests
+{
+ using Xunit;
+
+ ///
+ /// Tests the struct.
+ ///
+ public class SignedRationalTests
+ {
+ ///
+ /// Tests the equality operators for equality.
+ ///
+ [Fact]
+ public void AreEqual()
+ {
+ SignedRational r1 = new SignedRational(3, 2);
+ SignedRational r2 = new SignedRational(3, 2);
+
+ Assert.Equal(r1, r2);
+ Assert.True(r1 == r2);
+
+ SignedRational r3 = new SignedRational(7.55);
+ SignedRational r4 = new SignedRational(755, 100);
+ SignedRational r5 = new SignedRational(151, 20);
+
+ Assert.Equal(r3, r4);
+ Assert.Equal(r4, r5);
+ }
+
+ ///
+ /// Tests the equality operators for inequality.
+ ///
+ [Fact]
+ public void AreNotEqual()
+ {
+ SignedRational first = new SignedRational(0, 100);
+ SignedRational second = new SignedRational(100, 100);
+
+ Assert.NotEqual(first, second);
+ Assert.True(first != second);
+ }
+
+ ///
+ /// Tests whether the Rational constructor correctly assign properties.
+ ///
+ [Fact]
+ public void ConstructorAssignsProperties()
+ {
+ SignedRational rational = new SignedRational(7, -55);
+ Assert.Equal(7, rational.Numerator);
+ Assert.Equal(-55, rational.Denominator);
+
+ rational = new SignedRational(-755, 100);
+ Assert.Equal(-151, rational.Numerator);
+ Assert.Equal(20, rational.Denominator);
+
+ rational = new SignedRational(-755, -100, false);
+ Assert.Equal(-755, rational.Numerator);
+ Assert.Equal(-100, rational.Denominator);
+
+ rational = new SignedRational(-151, -20);
+ Assert.Equal(-151, rational.Numerator);
+ Assert.Equal(-20, rational.Denominator);
+
+ rational = new SignedRational(-7.55);
+ Assert.Equal(-151, rational.Numerator);
+ Assert.Equal(20, rational.Denominator);
+
+ rational = new SignedRational(7);
+ Assert.Equal(7, rational.Numerator);
+ Assert.Equal(1, rational.Denominator);
+ }
+
+ [Fact]
+ public void Fraction()
+ {
+ SignedRational first = new SignedRational(1.0 / 1600);
+ SignedRational second = new SignedRational(1.0 / 1600, true);
+ Assert.False(first.Equals(second));
+ }
+
+ [Fact]
+ public void ToDouble()
+ {
+ SignedRational rational = new SignedRational(0, 0);
+ Assert.Equal(double.NaN, rational.ToDouble());
+
+ rational = new SignedRational(2, 0);
+ Assert.Equal(double.PositiveInfinity, rational.ToDouble());
+
+ rational = new SignedRational(-2, 0);
+ Assert.Equal(double.NegativeInfinity, rational.ToDouble());
+ }
+
+ [Fact]
+ public void ToStringRepresention()
+ {
+ SignedRational rational = new SignedRational(0, 0);
+ Assert.Equal("[ Indeterminate ]", rational.ToString());
+
+ rational = new SignedRational(double.PositiveInfinity);
+ Assert.Equal("[ PositiveInfinity ]", rational.ToString());
+
+ rational = new SignedRational(double.NegativeInfinity);
+ Assert.Equal("[ NegativeInfinity ]", rational.ToString());
+
+ rational = new SignedRational(0, 1);
+ Assert.Equal("0", rational.ToString());
+
+ rational = new SignedRational(2, 1);
+ Assert.Equal("2", rational.ToString());
+
+ rational = new SignedRational(1, 2);
+ Assert.Equal("1/2", rational.ToString());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
index 438e903363..988c7747df 100644
--- a/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
+++ b/tests/ImageProcessorCore.Tests/Profiles/Exif/ExifProfileTests.cs
@@ -64,12 +64,11 @@ namespace ImageProcessorCore.Tests
{
using (MemoryStream memStream = new MemoryStream())
{
- // double exposureTime = 1.0 / 1600;
- Rational exposureTime = new Rational(1, 1600);
+ double exposureTime = 1.0 / 1600;
ExifProfile profile = GetExifProfile();
- profile.SetValue(ExifTag.ExposureTime, exposureTime);
+ profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime));
Image image = new Image(1, 1);
image.ExifProfile = profile;
@@ -84,12 +83,12 @@ namespace ImageProcessorCore.Tests
ExifValue value = profile.GetValue(ExifTag.ExposureTime);
Assert.NotNull(value);
- Assert.Equal(exposureTime, value.Value);
+ Assert.NotEqual(exposureTime, ((Rational)value.Value).ToDouble());
memStream.Position = 0;
profile = GetExifProfile();
- profile.SetValue(ExifTag.ExposureTime, exposureTime);
+ profile.SetValue(ExifTag.ExposureTime, new Rational(exposureTime, true));
image.ExifProfile = profile;
image.SaveAsJpeg(memStream);
@@ -101,7 +100,7 @@ namespace ImageProcessorCore.Tests
Assert.NotNull(profile);
value = profile.GetValue(ExifTag.ExposureTime);
- TestValue(value, exposureTime);
+ Assert.Equal(exposureTime, ((Rational)value.Value).ToDouble());
}
}
@@ -111,104 +110,29 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan))
{
Image image = new Image(stream);
- image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, Rational.PositiveInfinity);
+ image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.PositiveInfinity));
image = WriteAndRead(image);
ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
- Assert.Equal(Rational.PositiveInfinity, value.Value);
+ Assert.Equal(new SignedRational(double.PositiveInfinity), value.Value);
- image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, Rational.NegativeInfinity);
+ image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, new SignedRational(double.NegativeInfinity));
image = WriteAndRead(image);
value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
Assert.NotNull(value);
- Assert.Equal(Rational.NegativeInfinity, value.Value);
+ Assert.Equal(new SignedRational(double.NegativeInfinity), value.Value);
- image.ExifProfile.SetValue(ExifTag.FlashEnergy, Rational.NegativeInfinity);
+ image.ExifProfile.SetValue(ExifTag.FlashEnergy, new Rational(double.NegativeInfinity));
image = WriteAndRead(image);
value = image.ExifProfile.GetValue(ExifTag.FlashEnergy);
Assert.NotNull(value);
- Assert.Equal(Rational.PositiveInfinity, value.Value);
+ Assert.Equal(new Rational(double.PositiveInfinity), value.Value);
}
}
- //[Fact]
- //public void WriteFraction()
- //{
- // using (MemoryStream memStream = new MemoryStream())
- // {
- // double exposureTime = 1.0 / 1600;
-
- // ExifProfile profile = GetExifProfile();
-
- // profile.SetValue(ExifTag.ExposureTime, exposureTime);
-
- // Image image = new Image(1, 1);
- // image.ExifProfile = profile;
-
- // image.SaveAsJpeg(memStream);
-
- // memStream.Position = 0;
- // image = new Image(memStream);
-
- // profile = image.ExifProfile;
- // Assert.NotNull(profile);
-
- // ExifValue value = profile.GetValue(ExifTag.ExposureTime);
- // Assert.NotNull(value);
- // Assert.NotEqual(exposureTime, value.Value);
-
- // memStream.Position = 0;
- // profile = GetExifProfile();
-
- // profile.SetValue(ExifTag.ExposureTime, exposureTime);
- // profile.BestPrecision = true;
- // image.ExifProfile = profile;
-
- // image.SaveAsJpeg(memStream);
-
- // memStream.Position = 0;
- // image = new Image(memStream);
-
- // profile = image.ExifProfile;
- // Assert.NotNull(profile);
-
- // value = profile.GetValue(ExifTag.ExposureTime);
- // TestValue(value, exposureTime);
- // }
- //}
-
- //[Fact]
- //public void ReadWriteInfinity()
- //{
- // using (FileStream stream = File.OpenRead(TestImages.Jpg.Floorplan))
- // {
- // Image image = new Image(stream);
- // image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.PositiveInfinity);
-
- // image = WriteAndRead(image);
- // ExifValue value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
- // Assert.NotNull(value);
- // Assert.Equal(double.PositiveInfinity, value.Value);
-
- // image.ExifProfile.SetValue(ExifTag.ExposureBiasValue, double.NegativeInfinity);
-
- // image = WriteAndRead(image);
- // value = image.ExifProfile.GetValue(ExifTag.ExposureBiasValue);
- // Assert.NotNull(value);
- // Assert.Equal(double.NegativeInfinity, value.Value);
-
- // image.ExifProfile.SetValue(ExifTag.FlashEnergy, double.NegativeInfinity);
-
- // image = WriteAndRead(image);
- // value = image.ExifProfile.GetValue(ExifTag.FlashEnergy);
- // Assert.NotNull(value);
- // Assert.Equal(double.PositiveInfinity, value.Value);
- // }
- //}
-
[Fact]
public void SetValue()
{
@@ -224,11 +148,11 @@ namespace ImageProcessorCore.Tests
Assert.Throws(() => { value.Value = 15; });
- image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new Rational(75.55));
+ image.ExifProfile.SetValue(ExifTag.ShutterSpeedValue, new SignedRational(75.55));
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
- TestValue(value, new Rational(7555, 100));
+ TestValue(value, new SignedRational(7555, 100));
Assert.Throws(() => { value.Value = 75; });
@@ -258,7 +182,7 @@ namespace ImageProcessorCore.Tests
TestValue(value, "ImageProcessorCore");
value = image.ExifProfile.GetValue(ExifTag.ShutterSpeedValue);
- TestValue(value, new Rational(75.55));
+ TestValue(value, new SignedRational(75.55));
value = image.ExifProfile.GetValue(ExifTag.XResolution);
TestValue(value, new Rational(150.0));
@@ -372,6 +296,12 @@ namespace ImageProcessorCore.Tests
Assert.Equal(expected, value.Value);
}
+ private static void TestValue(ExifValue value, SignedRational expected)
+ {
+ Assert.NotNull(value);
+ Assert.Equal(expected, value.Value);
+ }
+
private static void TestValue(ExifValue value, Rational[] expected)
{
Assert.NotNull(value);