/*
* Author: Syed Mehroz Alam
* Email: smehrozalam@yahoo.com
* URL: Programming Home "http://www.geocities.com/smehrozalam/"
*
*/
using System;
using System.Runtime.InteropServices;
using System.Globalization;
namespace Mehroz
{
///
/// Classes Contained:
/// Fraction
/// FractionException
///
/// Class name: Fraction
/// Developed by: Syed Mehroz Alam
/// Email: mailto:smehrozalam@yahoo.com
/// URL: http://www.geocities.com/smehrozalam/
/// Changes: Marc C. Brooks mailto:IDisposable@gmail.com
/// Jeffery Sax http://www.extremeoptimization.com
/// Version: 2.2
///
/// What's new in version 2.0:
/// * Changed Numerator and Denominator from Int32(integer) to Int64(long) for increased range
/// * renamed ConvertToString() to (overloaded) ToString()
/// * added the capability of detecting/raising overflow exceptions
/// * Fixed the bug that very small numbers e.g. 0.00000001 could not be converted to fraction
/// * Other minor bugs fixed
///
/// What's new in version 2.1
/// * overloaded user-defined conversions to/from Fractions
///
/// What's new in version 2.2 - Marc C. Brooks mailto:IDisposable@gmail.com
/// * less overflows by finding the GCD for Add [Jeffery Sax] and Multiply [Marc C. Brooks]
/// * understands and handles NaN, PositiveInfinity, NegativeInfinity just like double [Marc C. Brooks]
/// * fixed several uses of int where long was correct [Marc C. Brooks]
/// * made value-type (struct) [Jeffery Sax]
/// * added ToInt32(), ToInt64() which throw for invalid (NaN, PositiveInfinity, NegativeInfinity) [Marc C. Brooks]
/// * removed redundant Value property [Jeffery Sax]
/// * added explicit conversion to Int32 and Int64 [Marc C. Brooks]
/// * better handling of exceptions [Marc C. Brooks]
/// * reorganize code, add XML doc and regions [Marc C. Brooks]
/// * proper implementations of Equals [Marc C. Brooks, Jeffery Sax]
/// * uses Math.Log(xx,2) and Math.Pow(xx,2) to get the best accuracy possible when converting doubles [Marc C. Brooks, Jeffery Sax]
///
/// What's new in version 2.3 - Marc C. Brooks mailto:IDisposable@gmail.com 01/10/2005
/// * fixed double-to-fraction logic to use continued fraction rules to get best possible precistion [bug fix for Syed Mehroz Alam, idea from Jeffery Sax]
/// * added static readonly values for NaN, PositiveInfinity, NegativeInfinity [idea from Jeffery Sax]
/// * moved comparisons into an implementation of IComparer [idea from Jeffery Sax]
/// * no longer throws for NaN(s) involved in Add, Subtract, Multiply, Divide operations [idea from Jeffery Sax]
/// * added static readonly values for Zero, MinValue, MaxValue, Epsilon to better mirror double
/// * added IsInfinity to better mirror double.
/// * added Modulus and % operators
///
/// Properties:
/// Numerator: Set/Get value for Numerator
/// Denominator: Set/Get value for Numerator
/// [Note: If you Set either Property, the Fraction should be passed to ReduceFraction at some point.]
///
/// Constructors:
/// no arguments: initializes fraction as 0/0 = NaN, so don't do that!
/// (Numerator, Denominator): initializes fraction with the given numerator and denominator
/// values and reduces
/// (long): initializes fraction with the given long value
/// (double): initializes fraction with the given double value
/// (string): initializes fraction with the given string value
/// the string can be an in the form of and integer, double or fraction.
/// e.g it can be like "123" or "123.321" or "123/456"
///
/// Public Methods (Description is given with respective methods' definitions)
/// Fraction ToFraction(long)
/// Fraction ToFraction(double)
/// Fraction ToFraction(string)
/// Int32 ToInt32()
/// Int64 ToInt64()
/// double ToDouble()
/// (override) string ToString()
/// Fraction Inverse()
/// Fraction Inverted(long)
/// Fraction Inverted(double)
/// ReduceFraction(ref Fraction)
/// CrossReducePair(ref Fraction, ref Fraction)
/// (override) Equals(object)
/// (override) GetHashCode()
///
/// Overloaded Operators (overloaded for Fractions, long and double)
/// Unary: -
/// Binary: +,-,*,/
/// Relational and Logical Operators: ==,!=,<,>,<=,>= (only == and != for long and doubles)
///
/// Overloaded user-defined conversions
/// Implicit: From long/double/string to Fraction
/// Explicit: From Fraction to long/double/string
///
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Fraction : IComparable, IFormattable
{
#region Constructors
///
/// Construct a Fraction from an integral value
///
/// The value (eventual numerator)
/// The denominator will be 1
public Fraction(long wholeNumber)
{
if (wholeNumber == long.MinValue)
wholeNumber++; // prevent serious issues later..
m_Numerator = wholeNumber;
m_Denominator = 1;
// no reducing required, we're a whole number
}
///
/// Construct a Fraction from a floating-point value
///
/// The value
public Fraction(double floatingPointNumber)
{
this = ToFraction(floatingPointNumber);
}
///
/// Construct a Fraction from a string in any legal format
///
/// A string with a legal fraction input format
/// Will reduce the fraction to smallest possible denominator
/// ToFraction(string strValue)
public Fraction(string inValue)
{
this = ToFraction(inValue);
}
///
/// Construct a Fraction from a numerator, denominator pair
///
/// The numerator (top number)
/// The denominator (bottom number)
/// Will reduce the fraction to smallest possible denominator
public Fraction(long numerator, long denominator)
{
if (numerator == long.MinValue)
numerator++; // prevent serious issues later..
if (denominator == long.MinValue)
denominator++; // prevent serious issues later..
m_Numerator = numerator;
m_Denominator = denominator;
ReduceFraction(ref this);
}
///
/// Private constructor to synthesize a Fraction for indeterminates (NaN and infinites)
///
/// Kind of inderterminate
private Fraction(Indeterminates type)
{
m_Numerator = (long)type;
m_Denominator = 0;
// do NOT reduce, we're clean as can be!
}
#endregion
#region Properties
///
/// The 'top' part of the fraction
///
/// For 3/4ths, this is the 3
public long Numerator
{
get
{
return m_Numerator;
}
set
{
m_Numerator = value;
}
}
///
/// The 'bottom' part of the fraction
///
/// For 3/4ths, this is the 4
public long Denominator
{
get
{
return m_Denominator;
}
set
{
m_Denominator = value;
}
}
#endregion
#region Expose constants
public static readonly Fraction NaN = new Fraction(Indeterminates.NaN);
public static readonly Fraction PositiveInfinity = new Fraction(Indeterminates.PositiveInfinity);
public static readonly Fraction NegativeInfinity = new Fraction(Indeterminates.NegativeInfinity);
public static readonly Fraction Zero = new Fraction(0,1);
public static readonly Fraction Epsilon = new Fraction(1, Int64.MaxValue);
private static readonly double EpsilonDouble = 1.0 / Int64.MaxValue;
public static readonly Fraction MaxValue = new Fraction(Int64.MaxValue, 1);
public static readonly Fraction MinValue = new Fraction(Int64.MinValue, 1);
#endregion
#region Explicit conversions
#region To primitives
///
/// Get the integral value of the Fraction object as int/Int32
///
/// The (approximate) integer value
/// If the value is not a true integer, the fractional part is discarded
/// (truncated toward zero). If the valid exceeds the range of an Int32 and exception is thrown.
/// Will throw a FractionException for NaN, PositiveInfinity
/// or NegativeInfinity with the InnerException set to a System.NotFiniteNumberException.
///
public Int32 ToInt32()
{
if (this.m_Denominator == 0)
{
throw new FractionException(string.Format("Cannot convert {0} to Int32", IndeterminateTypeName(this.m_Numerator)), new System.NotFiniteNumberException());
}
long bestGuess = this.m_Numerator / this.m_Denominator;
if (bestGuess > Int32.MaxValue || bestGuess < Int32.MinValue)
{
throw new FractionException("Cannot convert to Int32", new System.OverflowException());
}
return (Int32)bestGuess;
}
///
/// Get the integral value of the Fraction object as long/Int64
///
/// The (approximate) integer value
/// If the value is not a true integer, the fractional part is discarded
/// (truncated toward zero). If the valid exceeds the range of an Int32, no special
/// handling is guaranteed.
/// Will throw a FractionException for NaN, PositiveInfinity
/// or NegativeInfinity with the InnerException set to a System.NotFiniteNumberException.
public Int64 ToInt64()
{
if (this.m_Denominator == 0)
{
throw new FractionException(string.Format("Cannot convert {0} to Int64", IndeterminateTypeName(this.m_Numerator)), new System.NotFiniteNumberException());
}
return this.m_Numerator / this.m_Denominator;
}
///
/// Get the value of the Fraction object as double with full support for NaNs and infinities
///
/// The decimal representation of the Fraction, or double.NaN, double.NegativeInfinity
/// or double.PositiveInfinity
public double ToDouble()
{
if (this.m_Denominator == 1)
return this.m_Numerator;
else if (this.m_Denominator == 0)
{
switch (NormalizeIndeterminate(this.m_Numerator))
{
case Indeterminates.NegativeInfinity:
return double.NegativeInfinity;
case Indeterminates.PositiveInfinity:
return double.PositiveInfinity;
case Indeterminates.NaN:
default: // this can't happen
return double.NaN;
}
}
else
{
return (double)this.m_Numerator / (double)this.m_Denominator;
}
}
///
/// Get the value of the Fraction as a string, with proper representation for NaNs and infinites
///
/// The string representation of the Fraction, or the culture-specific representations of
/// NaN, PositiveInfinity or NegativeInfinity.
/// The current culture determines the textual representation the Indeterminates
public override string ToString()
{
if (this.m_Denominator == 1)
{
return this.m_Numerator.ToString();
}
else if (this.m_Denominator == 0)
{
return IndeterminateTypeName(this.m_Numerator);
}
else
{
return this.m_Numerator.ToString() + "/" + this.m_Denominator.ToString();
}
}
#endregion
#region From primitives
///
/// Converts a long value to the exact Fraction
///
/// The long to convert
/// An exact representation of the value
public static Fraction ToFraction(long inValue)
{
return new Fraction(inValue);
}
///
/// Converts a double value to the approximate Fraction
///
/// The double to convert
/// A best-fit representation of the value
/// Supports double.NaN, double.PositiveInfinity and double.NegativeInfinity
public static Fraction ToFraction(double inValue)
{
// it's one of the indeterminates... which?
if (double.IsNaN(inValue))
return NaN;
else if (double.IsNegativeInfinity(inValue))
return NegativeInfinity;
else if (double.IsPositiveInfinity(inValue))
return PositiveInfinity;
else if (inValue == 0.0d)
return Zero;
if (inValue > Int64.MaxValue)
throw new OverflowException(string.Format("Double {0} too large", inValue));
if (inValue < -Int64.MaxValue)
throw new OverflowException(string.Format("Double {0} too small", inValue));
if (-EpsilonDouble < inValue && inValue < EpsilonDouble)
throw new ArithmeticException(string.Format("Double {0} cannot be represented", inValue));
int sign = Math.Sign(inValue);
inValue = Math.Abs(inValue);
return ConvertPositiveDouble(sign, inValue);
}
///
/// Converts a string to the corresponding reduced fraction
///
/// The string representation of a fractional value
/// The Fraction that represents the string
/// Four forms are supported, as a plain integer, as a double, or as Numerator/Denominator
/// and the representations for NaN and the infinites
/// "123" = 123/1 and "1.25" = 5/4 and "10/36" = 5/13 and NaN = 0/0 and
/// PositiveInfinity = 1/0 and NegativeInfinity = -1/0
public static Fraction ToFraction(string inValue)
{
if (inValue == null || inValue == string.Empty)
throw new ArgumentNullException("inValue");
// could also be NumberFormatInfo.InvariantInfo
NumberFormatInfo info = NumberFormatInfo.CurrentInfo;
// Is it one of the special symbols for NaN and such...
string trimmedValue = inValue.Trim();
if (trimmedValue == info.NaNSymbol)
return NaN;
else if (trimmedValue == info.PositiveInfinitySymbol)
return PositiveInfinity;
else if (trimmedValue == info.NegativeInfinitySymbol)
return NegativeInfinity;
else
{
// Not special, is it a Fraction?
int slashPos = inValue.IndexOf('/');
if (slashPos > -1)
{
// string is in the form of Numerator/Denominator
long numerator = Convert.ToInt64(inValue.Substring(0, slashPos));
long denominator = Convert.ToInt64(inValue.Substring(slashPos + 1));
return new Fraction(numerator, denominator);
}
else
{
// the string is not in the form of a fraction
// hopefully it is double or integer, do we see a decimal point?
int decimalPos = inValue.IndexOf(info.CurrencyDecimalSeparator);
if (decimalPos > -1)
return new Fraction(Convert.ToDouble(inValue));
else
return new Fraction(Convert.ToInt64(inValue));
}
}
}
#endregion
#endregion
#region Indeterminate classifications
///
/// Determines if a Fraction represents a Not-a-Number
///
/// True if the Fraction is a NaN
public bool IsNaN()
{
if (this.m_Denominator == 0
&& NormalizeIndeterminate(this.m_Numerator) == Indeterminates.NaN)
return true;
else
return false;
}
///
/// Determines if a Fraction represents Any Infinity
///
/// True if the Fraction is Positive Infinity or Negative Infinity
public bool IsInfinity()
{
if (this.m_Denominator == 0
&& NormalizeIndeterminate(this.m_Numerator) != Indeterminates.NaN)
return true;
else
return false;
}
///
/// Determines if a Fraction represents Positive Infinity
///
/// True if the Fraction is Positive Infinity
public bool IsPositiveInfinity()
{
if (this.m_Denominator == 0
&& NormalizeIndeterminate(this.m_Numerator) == Indeterminates.PositiveInfinity)
return true;
else
return false;
}
///
/// Determines if a Fraction represents Negative Infinity
///
/// True if the Fraction is Negative Infinity
public bool IsNegativeInfinity()
{
if (this.m_Denominator == 0
&& NormalizeIndeterminate(this.m_Numerator) == Indeterminates.NegativeInfinity)
return true;
else
return false;
}
#endregion
#region Inversion
///
/// Inverts a Fraction
///
/// The inverted Fraction (with Denominator over Numerator)
/// Does NOT throw for zero Numerators as later use of the fraction will catch the error.
public Fraction Inverse()
{
// don't use the obvious constructor because we do not want it normalized at this time
Fraction frac = new Fraction();
frac.m_Numerator = this.m_Denominator;
frac.m_Denominator = this.m_Numerator;
return frac;
}
///
/// Creates an inverted Fraction
///
/// The inverted Fraction (with Denominator over Numerator)
/// Does NOT throw for zero Numerators as later use of the fraction will catch the error.
public static Fraction Inverted(long value)
{
Fraction frac = new Fraction(value);
return frac.Inverse();
}
///
/// Creates an inverted Fraction
///
/// The inverted Fraction (with Denominator over Numerator)
/// Does NOT throw for zero Numerators as later use of the fraction will catch the error.
public static Fraction Inverted(double value)
{
Fraction frac = new Fraction(value);
return frac.Inverse();
}
#endregion
#region Operators
#region Unary Negation operator
///
/// Negates the Fraction
///
/// The Fraction to negate
/// The negative version of the Fraction
public static Fraction operator -(Fraction left)
{
return Negate(left);
}
#endregion
#region Addition operators
public static Fraction operator +(Fraction left, Fraction right)
{
return Add(left, right);
}
public static Fraction operator +(long left, Fraction right)
{
return Add(new Fraction(left), right);
}
public static Fraction operator +(Fraction left, long right)
{
return Add(left, new Fraction(right));
}
public static Fraction operator +(double left, Fraction right)
{
return Add(ToFraction(left), right);
}
public static Fraction operator +(Fraction left, double right)
{
return Add(left, ToFraction(right));
}
#endregion
#region Subtraction operators
public static Fraction operator -(Fraction left, Fraction right)
{
return Add(left, - right);
}
public static Fraction operator -(long left, Fraction right)
{
return Add(new Fraction(left), - right);
}
public static Fraction operator -(Fraction left, long right)
{
return Add(left, new Fraction(- right));
}
public static Fraction operator -(double left, Fraction right)
{
return Add(ToFraction(left), - right);
}
public static Fraction operator -(Fraction left, double right)
{
return Add(left, ToFraction(- right));
}
#endregion
#region Multiplication operators
public static Fraction operator *(Fraction left, Fraction right)
{
return Multiply(left, right);
}
public static Fraction operator *(long left, Fraction right)
{
return Multiply(new Fraction(left), right);
}
public static Fraction operator *(Fraction left, long right)
{
return Multiply(left, new Fraction(right));
}
public static Fraction operator *(double left, Fraction right)
{
return Multiply(ToFraction(left), right);
}
public static Fraction operator *(Fraction left, double right)
{
return Multiply(left, ToFraction(right));
}
#endregion
#region Division operators
public static Fraction operator /(Fraction left, Fraction right)
{
return Multiply(left, right.Inverse());
}
public static Fraction operator /(long left, Fraction right)
{
return Multiply(new Fraction(left), right.Inverse());
}
public static Fraction operator /(Fraction left, long right)
{
return Multiply(left, Inverted(right));
}
public static Fraction operator /(double left, Fraction right)
{
return Multiply(ToFraction(left), right.Inverse());
}
public static Fraction operator /(Fraction left, double right)
{
return Multiply(left, Inverted(right));
}
#endregion
#region Modulus operators
public static Fraction operator %(Fraction left, Fraction right)
{
return Modulus(left, right);
}
public static Fraction operator %(long left, Fraction right)
{
return Modulus(new Fraction(left), right);
}
public static Fraction operator %(Fraction left, long right)
{
return Modulus(left, right);
}
public static Fraction operator %(double left, Fraction right)
{
return Modulus(ToFraction(left), right);
}
public static Fraction operator %(Fraction left, double right)
{
return Modulus(left, right);
}
#endregion
#region Equal operators
public static bool operator ==(Fraction left, Fraction right)
{
return left.CompareEquality(right, false);
}
public static bool operator ==(Fraction left, long right)
{
return left.CompareEquality(new Fraction(right), false);
}
public static bool operator ==(Fraction left, double right)
{
return left.CompareEquality(new Fraction(right), false);
}
#endregion
#region Not-equal operators
public static bool operator !=(Fraction left, Fraction right)
{
return left.CompareEquality(right, true);
}
public static bool operator !=(Fraction left, long right)
{
return left.CompareEquality(new Fraction(right), true);
}
public static bool operator !=(Fraction left, double right)
{
return left.CompareEquality(new Fraction(right), true);
}
#endregion
#region Inequality operators
///
/// Compares two Fractions to see if left is less than right
///
/// The first Fraction
/// The second Fraction
/// True if left is less
/// than right
/// Special handling for indeterminates exists. IndeterminateLess
/// Throws an error if overflows occur while computing the
/// difference with an InnerException of OverflowException
public static bool operator <(Fraction left, Fraction right)
{
return left.CompareTo(right) < 0;
}
///
/// Compares two Fractions to see if left is greater than right
///
/// The first Fraction
/// The second Fraction
/// True if left is greater
/// than right
/// Special handling for indeterminates exists. IndeterminateLess
/// Throws an error if overflows occur while computing the
/// difference with an InnerException of OverflowException
public static bool operator >(Fraction left, Fraction right)
{
return left.CompareTo(right) > 0;
}
///
/// Compares two Fractions to see if left is less than or equal to right
///
/// The first Fraction
/// The second Fraction
/// True if left is less than or
/// equal to right
/// Special handling for indeterminates exists. IndeterminateLessEqual
/// Throws an error if overflows occur while computing the
/// difference with an InnerException of OverflowException
public static bool operator <=(Fraction left, Fraction right)
{
return left.CompareTo(right) <= 0;
}
///
/// Compares two Fractions to see if left is greater than or equal to right
///
/// The first Fraction
/// The second Fraction
/// True if left is greater than or
/// equal to right
/// Special handling for indeterminates exists. IndeterminateLessEqual
/// Throws an error if overflows occur while computing the
/// difference with an InnerException of OverflowException
public static bool operator >=(Fraction left, Fraction right)
{
return left.CompareTo(right) >= 0;
}
#endregion
#region Implict conversion from primitive operators
///
/// Implicit conversion of a long integral value to a Fraction
///
/// The long integral value to convert
/// A Fraction whose denominator is 1
public static implicit operator Fraction(long value)
{
return new Fraction(value);
}
///
/// Implicit conversion of a double floating point value to a Fraction
///
/// The double value to convert
/// A reduced Fraction
public static implicit operator Fraction(double value)
{
return new Fraction(value);
}
///
/// Implicit conversion of a string to a Fraction
///
/// The string to convert
/// A reduced Fraction
public static implicit operator Fraction(string value)
{
return new Fraction(value);
}
#endregion
#region Explicit converstion to primitive operators
///
/// Explicit conversion from a Fraction to an integer
///
/// the Fraction to convert
/// The integral representation of the Fraction
public static explicit operator int(Fraction frac)
{
return frac.ToInt32();
}
///
/// Explicit conversion from a Fraction to an integer
///
/// The Fraction to convert
/// The integral representation of the Fraction
public static explicit operator long(Fraction frac)
{
return frac.ToInt64();
}
///
/// Explicit conversion from a Fraction to a double floating-point value
///
/// The Fraction to convert
/// The double representation of the Fraction
public static explicit operator double(Fraction frac)
{
return frac.ToDouble();
}
///
/// Explicit conversion from a Fraction to a string
///
/// the Fraction to convert
/// The string representation of the Fraction
public static implicit operator string(Fraction frac)
{
return frac.ToString();
}
#endregion
#endregion
#region Equals and GetHashCode overrides
///
/// Compares for equality the current Fraction to the value passed.
///
/// A Fraction,
/// True if the value equals the current fraction, false otherwise (including for
/// non-Fraction types or null object.
public override bool Equals(object obj)
{
if (obj == null || ! (obj is Fraction))
return false;
try
{
Fraction right = (Fraction)obj;
return this.CompareEquality(right, false);
}
catch
{
// can't throw in an Equals!
return false;
}
}
///
/// Returns a hash code generated from the current Fraction
///
/// The hash code
/// Reduces (in-place) the Fraction first.
public override int GetHashCode()
{
// insure we're as close to normalized as possible first
ReduceFraction(ref this);
int numeratorHash = this.m_Numerator.GetHashCode();
int denominatorHash = this.m_Denominator.GetHashCode();
return (numeratorHash ^ denominatorHash);
}
#endregion
#region IComparable member and type-specific version
///
/// Compares an object to this Fraction
///
/// The object to compare against (null is less than everything)
/// -1 if this is less than ,
/// 0 if they are equal,
/// 1 if this is greater than
/// Will convert an object from longs, doubles, and strings as this is a value-type.
public int CompareTo(object obj)
{
if (obj == null)
return 1; // null is less than anything
Fraction right;
if (obj is Fraction)
right = (Fraction)obj;
else if (obj is long)
right = (long)obj;
else if (obj is double)
right = (double)obj;
else if (obj is string)
right = (string)obj;
else
throw new ArgumentException("Must be convertible to Fraction", "obj");
return this.CompareTo(right);
}
///
/// Compares this Fraction to another Fraction
///
/// The Fraction to compare against
/// -1 if this is less than ,
/// 0 if they are equal,
/// 1 if this is greater than
public int CompareTo(Fraction right)
{
// if left is an indeterminate, punt to the helper...
if (this.m_Denominator == 0)
{
return IndeterminantCompare(NormalizeIndeterminate(this.m_Numerator), right);
}
// if right is an indeterminate, punt to the helper...
if (right.m_Denominator == 0)
{
// note sign-flip...
return - IndeterminantCompare(NormalizeIndeterminate(right.m_Numerator), this);
}
// they're both normal Fractions
CrossReducePair(ref this, ref right);
try
{
checked
{
long leftScale = this.m_Numerator * right.m_Denominator;
long rightScale = this.m_Denominator * right.m_Numerator;
if (leftScale < rightScale)
return -1;
else if (leftScale > rightScale)
return 1;
else
return 0;
}
}
catch (Exception e)
{
throw new FractionException(string.Format("CompareTo({0}, {1}) error", this, right), e);
}
}
#endregion
#region IFormattable Members
string System.IFormattable.ToString(string format, IFormatProvider formatProvider)
{
return this.m_Numerator.ToString(format, formatProvider) + "/" + this.m_Denominator.ToString(format, formatProvider);
}
#endregion
#region Reduction
///
/// Reduces (simplifies) a Fraction by dividing down to lowest possible denominator (via GCD)
///
/// The Fraction to be reduced [WILL BE MODIFIED IN PLACE]
/// Modifies the input arguments in-place! Will normalize the NaN and infinites
/// representation. Will set Denominator to 1 for any zero numerator. Moves sign to the
/// Numerator.
/// 2/4 will be reduced to 1/2
public static void ReduceFraction(ref Fraction frac)
{
// clean up the NaNs and infinites
if (frac.m_Denominator == 0)
{
frac.m_Numerator = (long)NormalizeIndeterminate(frac.m_Numerator);
return;
}
// all forms of zero are alike.
if (frac.m_Numerator == 0)
{
frac.m_Denominator = 1;
return;
}
long iGCD = GCD(frac.m_Numerator, frac.m_Denominator);
frac.m_Numerator /= iGCD;
frac.m_Denominator /= iGCD;
// if negative sign in denominator
if ( frac.m_Denominator < 0 )
{
//move negative sign to numerator
frac.m_Numerator = - frac.m_Numerator;
frac.m_Denominator = - frac.m_Denominator;
}
}
///
/// Cross-reduces a pair of Fractions so that we have the best GCD-reduced values for multiplication
///
/// The first Fraction [WILL BE MODIFIED IN PLACE]
/// The second Fraction [WILL BE MODIFIED IN PLACE]
/// Modifies the input arguments in-place!
/// (3/4, 5/9) = (1/4, 5/3)
public static void CrossReducePair(ref Fraction frac1, ref Fraction frac2)
{
// leave the indeterminates alone!
if (frac1.m_Denominator == 0 || frac2.m_Denominator == 0)
return;
long gcdTop = GCD(frac1.m_Numerator, frac2.m_Denominator);
frac1.m_Numerator = frac1.m_Numerator / gcdTop;
frac2.m_Denominator = frac2.m_Denominator / gcdTop;
long gcdBottom = GCD(frac1.m_Denominator, frac2.m_Numerator);
frac2.m_Numerator = frac2.m_Numerator / gcdBottom;
frac1.m_Denominator = frac1.m_Denominator / gcdBottom;
}
#endregion
#region Implementation
#region Convert a double to a fraction
private static Fraction ConvertPositiveDouble(int sign, double inValue)
{
// Shamelessly stolen from http://homepage.smc.edu/kennedy_john/CONFRAC.PDF
// with AccuracyFactor == double.Episilon
long fractionNumerator = (long)inValue;
double fractionDenominator = 1;
double previousDenominator = 0;
double remainingDigits = inValue;
int maxIterations = 594; // found at http://www.ozgrid.com/forum/archive/index.php/t-22530.html
while (remainingDigits != Math.Floor(remainingDigits)
&& Math.Abs(inValue - (fractionNumerator / fractionDenominator)) > double.Epsilon)
{
remainingDigits = 1.0 / (remainingDigits - Math.Floor(remainingDigits));
double scratch = fractionDenominator;
fractionDenominator =(Math.Floor(remainingDigits) * fractionDenominator) + previousDenominator;
fractionNumerator = (long)(inValue * fractionDenominator + 0.5);
previousDenominator = scratch;
if (maxIterations-- < 0)
break;
}
return new Fraction(fractionNumerator * sign, (long)fractionDenominator);
}
#endregion
#region Equality helper
///
/// Compares for equality the current Fraction to the value passed.
///
/// A Fraction to compare against
/// If true, we're looking for not-equal
/// True if the equals the current
/// fraction, false otherwise. If comparing two NaNs, they are always equal AND
/// not-equal.
private bool CompareEquality(Fraction right, bool notEqualCheck)
{
// insure we're normalized first
ReduceFraction(ref this);
// now normalize the comperand
ReduceFraction(ref right);
if (this.m_Numerator == right.m_Numerator && this.m_Denominator == right.m_Denominator)
{
// special-case rule, two NaNs are always both equal
if (notEqualCheck && this.IsNaN())
return true;
else
return ! notEqualCheck;
}
else
{
return notEqualCheck;
}
}
#endregion
#region Comparison helper
///
/// Determines how this Fraction, of an indeterminate type, compares to another Fraction
///
/// What kind of indeterminate
/// The other Fraction to compare against
/// -1 if this is less than ,
/// 0 if they are equal,
/// 1 if this is greater than
/// NaN is less than anything except NaN and Negative Infinity. Negative Infinity is less
/// than anything except Negative Infinity. Positive Infinity is greater than anything except
/// Positive Infinity.
private static int IndeterminantCompare(Indeterminates leftType, Fraction right)
{
switch (leftType)
{
case Indeterminates.NaN:
// A NaN is...
if (right.IsNaN())
return 0; // equal to a NaN
else if (right.IsNegativeInfinity())
return 1; // great than Negative Infinity
else
return -1; // less than anything else
case Indeterminates.NegativeInfinity:
// Negative Infinity is...
if (right.IsNegativeInfinity())
return 0; // equal to Negative Infinity
else
return -1; // less than anything else
case Indeterminates.PositiveInfinity:
if (right.IsPositiveInfinity())
return 0; // equal to Positive Infinity
else
return 1; // greater than anything else
default:
// this CAN'T happen, something VERY wrong is going on...
return 0;
}
}
#endregion
#region Math helpers
///
/// Negates the Fraction
///
/// Value to negate
/// A new Fraction that is sign-flipped from the input
private static Fraction Negate(Fraction frac)
{
// for a NaN, it's still a NaN
return new Fraction( - frac.m_Numerator, frac.m_Denominator);
}
///
/// Adds two Fractions
///
/// A Fraction
/// Another Fraction
/// Sum of the Fractions. Returns NaN if either Fraction is a NaN.
/// Will throw if an overflow occurs when computing the
/// GCD-normalized values.
private static Fraction Add(Fraction left, Fraction right)
{
if (left.IsNaN() || right.IsNaN())
return NaN;
long gcd = GCD(left.m_Denominator, right.m_Denominator); // cannot return less than 1
long leftDenominator = left.m_Denominator / gcd;
long rightDenominator = right.m_Denominator / gcd;
try
{
checked
{
long numerator = left.m_Numerator * rightDenominator + right.m_Numerator * leftDenominator;
long denominator = leftDenominator * rightDenominator * gcd;
return new Fraction(numerator, denominator);
}
}
catch (Exception e)
{
throw new FractionException("Add error", e);
}
}
///
/// Multiplies two Fractions
///
/// A Fraction
/// Another Fraction
/// Product of the Fractions. Returns NaN if either Fraction is a NaN.
/// Will throw if an overflow occurs. Does a cross-reduce to
/// insure only the unavoidable overflows occur.
private static Fraction Multiply(Fraction left, Fraction right)
{
if (left.IsNaN() || right.IsNaN())
return NaN;
// this would be unsafe if we were not a ValueType, because we would be changing the
// caller's values. If we change back to a class, must use temporaries
CrossReducePair(ref left, ref right);
try
{
checked
{
long numerator = left.m_Numerator * right.m_Numerator;
long denominator = left.m_Denominator * right.m_Denominator;
return new Fraction(numerator, denominator);
}
}
catch (Exception e)
{
throw new FractionException("Multiply error", e);
}
}
///
/// Returns the modulus (remainder after dividing) two Fractions
///
/// A Fraction
/// Another Fraction
/// Modulus of the Fractions. Returns NaN if either Fraction is a NaN.
/// Will throw if an overflow occurs. Does a cross-reduce to
/// insure only the unavoidable overflows occur.
private static Fraction Modulus(Fraction left, Fraction right)
{
if (left.IsNaN() || right.IsNaN())
return NaN;
try
{
checked
{
// this will discard any fractional places...
Int64 quotient = (Int64)(left / right);
Fraction whole = new Fraction(quotient * right.m_Numerator, right.m_Denominator);
return left - whole;
}
}
catch (Exception e)
{
throw new FractionException("Modulus error", e);
}
}
///
/// Computes the greatest common divisor for two values
///
/// One value
/// Another value
/// The greatest common divisor of the two values
/// (6, 9) returns 3 and (11, 4) returns 1
private static long GCD(long left, long right)
{
// take absolute values
if (left < 0)
left = - left;
if (right < 0)
right = - right;
// if we're dealing with any zero or one, the GCD is 1
if (left < 2 || right < 2)
return 1;
do
{
if (left < right)
{
long temp = left; // swap the two operands
left = right;
right = temp;
}
left %= right;
} while (left != 0);
return right;
}
#endregion
#region Indeterminate helpers
///
/// Gives the culture-related representation of the indeterminate types NaN, PositiveInfinity
/// and NegativeInfinity
///
/// The value in the numerator
/// The culture-specific string representation of the implied value
/// Only the sign and zero/non-zero information is relevant.
private static string IndeterminateTypeName(long numerator)
{
// could also be NumberFormatInfo.InvariantInfo
System.Globalization.NumberFormatInfo info = NumberFormatInfo.CurrentInfo;
switch (NormalizeIndeterminate(numerator))
{
case Indeterminates.PositiveInfinity:
return info.PositiveInfinitySymbol;
case Indeterminates.NegativeInfinity:
return info.NegativeInfinitySymbol;
default: // if this happens, something VERY wrong is going on...
case Indeterminates.NaN:
return info.NaNSymbol;
}
}
///
/// Gives the normalize representation of the indeterminate types NaN, PositiveInfinity
/// and NegativeInfinity
///
/// The value in the numerator
/// The normalized version of the indeterminate type
/// Only the sign and zero/non-zero information is relevant.
private static Indeterminates NormalizeIndeterminate(long numerator)
{
switch (Math.Sign(numerator))
{
case 1:
return Indeterminates.PositiveInfinity;
case -1:
return Indeterminates.NegativeInfinity;
default: // if this happens, your Math.Sign function is BROKEN!
case 0:
return Indeterminates.NaN;
}
}
// These are used to represent the indeterminate with a Denominator of zero
private enum Indeterminates
{
NaN = 0
, PositiveInfinity = 1
, NegativeInfinity = -1
}
#endregion
#region Member variables
private long m_Numerator;
private long m_Denominator;
#endregion
#endregion
} //end class Fraction
///
/// Exception class for Fraction, derived from System.Exception
///
public class FractionException : Exception
{
///
/// Constructs a FractionException
///
/// String associated with the error message
/// Actual inner exception caught
public FractionException(string Message, Exception InnerException) : base(Message, InnerException)
{
}
} //end class FractionException
} //end namespace Mehroz