Click here to Skip to main content
15,895,871 members
Articles / Programming Languages / C#
Article

Fraction class in C#

Rate me:
Please Sign up or sign in to vote.
4.63/5 (22 votes)
14 Feb 2005CPOL4 min read 222.5K   8.1K   50   29
An article representing floating point numbers as fractions.

Introduction

This article demonstrates how to remove the problem of limited precision when numbers such as 1/10, 1/7, 1/3, etc. are represented in a floating point variable type in C#. Yes, you guessed it right, this problem can be solved by storing such numbers in a fraction format. As a fraction object stores its numerator and denominator as integer variables, there is no loss of accuracy. For this purpose, I have written a simple C# class representing fractions. It can be used in various applications, e.g., equation solving, matrix transformations, etc.

Features of class

The class contains a variety of overloaded constructors and operators for certain situations. It also throws certain exceptions, e.g., when denominator is tried to assign a 0 value, when the result of an arithmetic exceeds the limit (range), etc. One more feature is the automatic normalization (simplification) of fractions. The class uses data type 'long integer' for storing numerator and denominator, hence its range is upper bounded by the range of Int64 in .NET framework.

Using the code

The class includes a variety of constructors, e.g., one that takes an integer like 123, one that takes a double like 156.25, one that takes a string that has all the above qualities (e.g., string can be "32/77" or "213" or "321.44"), one that takes values of numerator and denominator like 231 and 101, and, of course, a parameter-less constructor for a "zero" fraction stored as 0/1.

C#
Fraction frac=new Fraction(); // we'll get 0/1
frac=new Fraction(1,5);       // we'll get 1/5
frac=new Fraction(25);        // we'll get 25/1
frac=new Fraction(9.25);      // we'll get 37/4
frac=new Fraction("6.25");    // we'll get 25/4

frac=new Fraction( System.Console.ReadLine() );
     // we can enter anything like "213" or 
     // "23/3" or "4.27"

Console.WriteLine( frac );
// displays the current value of frac1 object;

Operators overloaded (overloaded for fractions, integers and doubles) for Fraction object include:

  • Unary: - (Negation)
  • Binary +, -, *, /
  • Relational operators such as ==, !=, <, >, <=, >=.
C#
Fraction frac=new Fraction("1/2"); // initialize a fraction with 1/2
Console.WriteLine( frac+2.5 );     // will display 3

Overloaded conversion further enhances the capabilities of the class. See how simple it is to work with fractions:

C#
Fraction frac="1/2" // implicit cast from string to 
frac="22.5"         // implicit cast from string to fraction
frac=10.25          // implicit cast from double to fraction
frac=15             // implicit cast from integer/long to fraction

Finally, as an exercise, guess the output of the following code:

C#
Fraction f=0.5;                 // initialize frac=1/2
Console.WriteLine( f-0.25 );    // Yes, you are right. "1/4" is displayed
Console.WriteLine( f+"1/4" );
   // not sure??? It will display "3/4" because "1/4" has 
   // been converted to fraction and then added to our frac object

Implementation details

The class uses simple mathematics to do all the work. Let us see some of these simple techniques:

  • To convert a double to a fraction, we keep multiplying the given double number with 10 until it is converted to an integer number.
  • To convert a given string to a fraction, we treat all the value before "/" as numerator and after "/" as denominator.
  • To normalize a fraction, we divide its numerator and denominator by their GCD (found by famous Euler's formula).
  • To add two fractions, we use simple school formula to get numerator and denominator of the resultant fraction and then normalize it:
    C#
    Numerator = frac1.Numerator*frac2.Denominator 
                + frac2.Numerator*frac1.Denominator;
    Denominator = frac1.Denominator*frac2.Denominator;
  • To overload arithmetic operators for integers and doubles, we first convert them to fractions and then perform the operation.

Applications

There are a lot of applications of Fraction class. An example is a matrix class, see my article on Matrix class in C#.

History

Version 2.0

  • Changed Numerator and Denominator from Int32 (integer) to Int64 (long) for increased range.
  • Renamed ConvertToString() to 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.
  • Fixed other minor bugs.

Version 2.1

  • Overloaded conversions from/to fractions.

Version 2.2 (changes by Marc Brooks and Jeffrey Sax).

  • 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]
  • Reorganized code, added 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]

Version 2.3 (changes by Marc Brooks and Jeffrey Sax)

  • Fixed double-to-fraction logic to use continued fraction rules to get best possible precision [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 [Marc C. Brooks]
  • Added IsInfinity to better mirror double [Marc C. Brooks]
  • Added modulus and % operators [Marc C. Brooks]

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Pakistan Pakistan

Syed Mehroz Alam, living in Karachi, Pakistan, is a developer focusing Microsoft technologies. He has completed his bachelors as a Computer Systems Engineer in 2006 and is currently pursuing a Masters degree in Computer Science. He loves to learn, discover and master all aspects of .NET and SQL Server. Mehroz has developed rich internet enterprise applications using Silverlight in addition to the traditional ASP.NET and Windows Forms applications. He has worked with all three components of SQL Business Intelligence Studio: SSIS, SSRS and SSAS for developing BI Solutions and Data warehouse. He loves to write complex TSQL queries and evaluate his skills by participating in various TSQL Challenges. His blog can be viewed at http://smehrozalam.wordpress.com.


Comments and Discussions

 
GeneralMy vote of 5 Pin
Member 1351769713-Feb-24 1:49
Member 1351769713-Feb-24 1:49 
QuestionBug Pin
H. L. McMaken14-Nov-23 15:33
H. L. McMaken14-Nov-23 15:33 
QuestionPretty Fractions Pin
dynamichael1-Aug-20 6:26
dynamichael1-Aug-20 6:26 
BugSome issues with comparison Pin
tharhtwe1-Apr-19 17:15
tharhtwe1-Apr-19 17:15 
BugClass doesnt support big numbers Pin
Member 1179687521-Jul-16 4:04
Member 1179687521-Jul-16 4:04 
GeneralRe: Class doesnt support big numbers Pin
Member 1116400319-Mar-17 5:35
Member 1116400319-Mar-17 5:35 
QuestionSigned Number for Both Numerator and Denominator? Pin
Member 1215322924-Mar-16 8:06
Member 1215322924-Mar-16 8:06 
QuestionGlobalization issue Pin
Anders Eriksson17-Feb-14 2:58
Anders Eriksson17-Feb-14 2:58 
SuggestionHandling Radios & Percents Pin
Duane McKinney19-Jun-13 6:46
Duane McKinney19-Jun-13 6:46 
SuggestionRe: Square roots Pin
mla15425-Jul-12 5:13
mla15425-Jul-12 5:13 
GeneralUnit tests for this class using NUnit Pin
Syed Mehroz Alam16-Feb-10 2:07
Syed Mehroz Alam16-Feb-10 2:07 
GeneralThanks a million! Pin
Le Duc Anh27-Oct-09 19:59
professionalLe Duc Anh27-Oct-09 19:59 
GeneralAlso check out the Fraction class from Apache Pin
Sire40427-Mar-09 3:03
Sire40427-Mar-09 3:03 
Generalgcd in binary Pin
TheKing28-Mar-07 10:35
TheKing28-Mar-07 10:35 
GeneralSimple change Pin
Daenris24-Sep-06 13:26
Daenris24-Sep-06 13:26 
GeneralRe: Simple change Pin
Daenris24-Sep-06 13:33
Daenris24-Sep-06 13:33 
GeneralRe: Simple change Pin
dodiggitydag12-Oct-06 15:10
dodiggitydag12-Oct-06 15:10 
GeneralRe: Simple change Pin
Nick Alexeev12-Oct-14 9:40
professionalNick Alexeev12-Oct-14 9:40 
GeneralA very good class Pin
Mina Momtaz13-May-06 22:33
Mina Momtaz13-May-06 22:33 
GeneralBug in CompareTo(Fraction right) method. Pin
Marc Brooks9-Nov-05 7:25
Marc Brooks9-Nov-05 7:25 
GeneralSome improvements for this class. Pin
Marc Brooks20-Dec-04 13:22
Marc Brooks20-Dec-04 13:22 
Cool | :cool: Thanks for this class! I needed something like this just the other day. I incorporated my needs and the changes suggested by Jeffery Sax.

It avoids most 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]

Due to limitations of the CodeProject message system, the XML docs have been stripped. If you want a copy of the revised class, just e-mail me

Here's the revised Fraction.cs class.
/*
 * Author: Syed Mehroz Alam
 * Email: smehrozalam@yahoo.com
 * URL: Programming Home "http://www.geocities.com/smehrozalam/" 
 * Date: 12/20/2004
 * Time: 06:30 PM
 *
 */

using System;
using System.Globalization;

namespace Mehroz
{
    /// <summary>
    /// Classes Contained:
    ///     Fraction
    ///     FractionException
    /// </summary>
    
    /// 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]
    /// 
    /// 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
    /// </summary>
    public struct Fraction
    {
        #region Constructors
        /// <summary>
        /// Construct a Fraction from an integral value
        /// </summary>
        /// <param name="wholeNumber">The value (eventual numerator)</param>
        /// <remarks>The denominator will be 1</remarks>
        public Fraction(long wholeNumber)
        {
            m_Numerator = wholeNumber;
            m_Denominator = 1;
            // no reducing required, we're a whole number
        }
    
        /// <summary>
        /// Construct a Fraction from a floating-point value
        /// </summary>
        /// <param name="floatingPointNumber">The value</param>
        public Fraction(double floatingPointNumber)
        {
            this = ToFraction(floatingPointNumber);
            // the best representation was built already, no reducing required
        }
        
        /// <summary>
        /// Construct a Fraction from a string in any legal format
        /// </summary>
        /// <param name="inValue">A string with a legal fraction input format</param>
        /// <remarks>Will reduce the fraction to smallest possible denominator</remarks>
        /// <see>ToFraction(string strValue)</see>
        public Fraction(string inValue)
        {
            this = ToFraction(inValue);
        }
        
        /// <summary>
        /// Construct a Fraction from a numerator, denominator pair
        /// </summary>
        /// <param name="numerator">The numerator (top number)</param>
        /// <param name="denominator">The denominator (bottom number)</param>
        /// <remarks>Will reduce the fraction to smallest possible denominator</remarks>
        public Fraction(long numerator, long denominator)
        {
            m_Numerator = numerator;
            m_Denominator = denominator;
            ReduceFraction(ref this);
        }

        /// <summary>
        /// Private constructor to synthesize a Fraction for indeterminates (NaN and infinites)
        /// </summary>
        /// <param name="type">Kind of inderterminate</param>
        private Fraction(Indeterminates type)
        {
            m_Denominator = 0;
            m_Numerator = (long)type;
            // do NOT reduce, we're clean as can be!
        }
        #endregion

        #region Properties
        /// <summary>
        /// The 'top' part of the fraction
        /// </summary>
        /// <example>For 3/4ths, this is the 3</example>
        public long Numerator
        {
            get 
            {
                return m_Numerator;
            }
            set
            {
                m_Numerator = value;
            }
        }

        /// <summary>
        /// The 'bottom' part of the fraction
        /// </summary>
        /// <example>For 3/4ths, this is the 4</example>
        public long Denominator
        {
            get
            {
                return m_Denominator;
            }
            set
            {
                m_Denominator = value;
            }
        }
        #endregion

        #region Explicit conversions
        #region To primitives
        /// <summary>
        /// Get the integral value of the Fraction object as int/Int32
        /// </summary>
        /// <returns>The (approximate) integer value</returns>
        /// <remarks>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.</remarks>
        /// <exception cref="FractionException">Will throw a FractionException for NaN, PositiveInfinity
        /// or NegativeInfinity with the InnerException set to a System.NotFiniteNumberException.</exception>
        /// <exception cref="OverflowException" Will throw a System.OverflowException if the value is too
        /// large or small to be represented as an Int32.</exception>
        public Int32 ToInt32()
        {
            if (this.Denominator == 0)
            {
                throw new FractionException(string.Format("Cannot convert {0} to Int32", IndeterminateTypeName(this.Numerator)), new System.NotFiniteNumberException());
            }

            long bestGuess = this.Numerator / this.Denominator;

            if (bestGuess > Int32.MaxValue || bestGuess < Int32.MinValue)
            {
                throw new FractionException("Cannot convert to Int32", new System.OverflowException());
            }

            return (Int32)bestGuess;
        }

        /// <summary>
        /// Get the integral value of the Fraction object as long/Int64
        /// </summary>
        /// <returns>The (approximate) integer value</returns>
        /// <remarks>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.</remarks>
        /// <exception cref="FractionException">Will throw a FractionException for NaN, PositiveInfinity
        /// or NegativeInfinity with the InnerException set to a System.NotFiniteNumberException.</exception>
        public Int64 ToInt64()
        {
            if (this.Denominator == 0)
            {
                throw new FractionException(string.Format("Cannot convert {0} to Int64", IndeterminateTypeName(this.Numerator)), new System.NotFiniteNumberException());
            }

            return this.Numerator / this.Denominator;
        }

        /// <summary>
        /// Get the value of the Fraction object as double with full support for NaNs and infinities
        /// </summary>
        /// <returns>The decimal representation of the Fraction, or double.NaN, double.NegativeInfinity
        /// or double.PositiveInfinity</returns>
        public double ToDouble()
        {
            if (this.Denominator == 1)
                return this.Numerator;
            else if (this.Denominator == 0)
            {
                switch (NormalizeIndeterminate(this.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.Numerator / (double)this.Denominator;
            }
        }

        /// <summary>
        /// Get the value of the Fraction as a string, with proper representation for NaNs and infinites
        /// </summary>
        /// <returns>The string representation of the Fraction, or the culture-specific representations of
        /// NaN, PositiveInfinity or NegativeInfinity.</returns>
        /// <remarks>The current culture determines the textual representation the Indeterminates</remarks>
        public override string ToString()
        {
            if (this.Denominator == 1)
            {
                return this.Numerator.ToString();
            }
            else if (this.Denominator == 0)
            {
                return IndeterminateTypeName(this.Numerator);
            }
            else
            {
                return this.Numerator.ToString() + "/" + this.Denominator.ToString();
            }
        }
        #endregion

        #region From primitives
        /// <summary>
        /// Converts a long value to the exact Fraction
        /// </summary>
        /// <param name="inValue">The long to convert</param>
        /// <returns>An exact representation of the value</returns>
        public static Fraction ToFraction(long inValue)
        {
            return new Fraction(inValue);
        }

        /// <summary>
        /// Converts a double value to the approximate Fraction
        /// </summary>
        /// <param name="inValue">The double to convert</param>
        /// <returns>A best-fit representation of the value</returns>
        /// <remarks>Supports double.NaN, double.PositiveInfinity and double.NegativeInfinity</remarks>
        public static Fraction ToFraction(double inValue)
        {
            if (double.IsNegativeInfinity(inValue))
            {
                return new Fraction(Indeterminates.NegativeInfinity);
            }
            else if (double.IsPositiveInfinity(inValue))
            {
                return new Fraction(Indeterminates.PositiveInfinity);
            }
            else if (double.IsNaN(inValue))
            {
                return new Fraction(Indeterminates.NaN);
            }
            else if (inValue % 1 == 0)  // if whole number
            {
                return new Fraction((Int64) inValue);
            }
            else
            {
                try
                {
                    checked
                    {
                        int sign = Math.Sign(inValue);
                        double numerator = Math.Abs(inValue);
                        long inScale = (long)Math.Log(numerator, 2);            // base two give "as exact as possible"
                        double denominator = Math.Pow(2, Math.Abs(inScale));    // start with at least enough...

                        return new Fraction((long)Math.Round(numerator * denominator * sign), (long)Math.Floor(denominator));
                    }
                }
                catch (Exception e)
                {
                    throw new FractionException("Assigned from double", e);
                }
            }
        }

        /// <summary>
        /// Converts a string to the corresponding reduced fraction
        /// </summary>
        /// <param name="inValue">The string representation of a fractional value</param>
        /// <returns>The Fraction that represents the string</returns>
        /// <remarks>Four forms are supported, as a plain integer, as a double, or as Numerator/Denominator
        /// and the representations for NaN and the infinites</remarks>
        /// <example>"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</example>
        public static Fraction ToFraction(string inValue)
        {
            if (inValue == null)
                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 new Fraction(Indeterminates.NaN);
            else if (trimmedValue == info.PositiveInfinitySymbol)
                return new Fraction(Indeterminates.PositiveInfinity);
            else if (trimmedValue == info.NegativeInfinitySymbol)
                return new Fraction(Indeterminates.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
        /// <summary>
        /// Determines if a Fraction represents a Not-a-Number
        /// </summary>
        /// <returns>True if the Fraction is a NaN</returns>
        public bool IsNaN()
        {
            if (this.Denominator == 0 
                && NormalizeIndeterminate(this.Numerator) == Indeterminates.NaN)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Determines if a Fraction represents Positive Infinity
        /// </summary>
        /// <returns>True if the Fraction is Positive Infinity</returns>
        public bool IsPositiveInfinity()
        {
            if (this.Denominator == 0
                && NormalizeIndeterminate(this.Numerator) == Indeterminates.PositiveInfinity)
                return true;
            else
                return false;
        }

        /// <summary>
        /// Determines if a Fraction represents Negative Infinity
        /// </summary>
        /// <returns>True if the Fraction is Negative Infinity</returns>
        public bool IsNegativeInfinity()
        {
            if (this.Denominator == 0
                && NormalizeIndeterminate(this.Numerator) == Indeterminates.NegativeInfinity)
                return true;
            else
                return false;
        }
        #endregion

        #region Inversion
        /// <summary>
        /// Inverts a Fraction
        /// </summary>
        /// <returns>The inverted Fraction (with Denominator over Numerator)</returns>
        /// <remarks>Does NOT throw for zero Numerators as later use of the fraction will catch the error.</remarks>
        public Fraction Inverse()
        {
            // don't use the obvious constructor because we do not want it normalized at this time
            Fraction frac = new Fraction();

            frac.Numerator = this.Denominator;
            frac.Denominator = this.Numerator;
            return frac;
        }

        /// <summary>
        /// Creates an inverted Fraction
        /// </summary>
        /// <returns>The inverted Fraction (with Denominator over Numerator)</returns>
        /// <remarks>Does NOT throw for zero Numerators as later use of the fraction will catch the error.</remarks>
        public static Fraction Inverted(long value)
        {
            Fraction frac = new Fraction(value);
            return frac.Inverse();
        }

        /// <summary>
        /// Creates an inverted Fraction
        /// </summary>
        /// <returns>The inverted Fraction (with Denominator over Numerator)</returns>
        /// <remarks>Does NOT throw for zero Numerators as later use of the fraction will catch the error.</remarks>
        public static Fraction Inverted(double value)
        {
            Fraction frac = new Fraction(value);
            return frac.Inverse();
        }
        #endregion

        #region Operators
        #region Unary Negation operator
        /// <summary>
        /// Negates the Fraction
        /// </summary>
        /// <param name="left">The Fraction to negate</param>
        /// <returns>The negative version of the Fraction</returns>
        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 Equal operators
        public static bool operator ==(Fraction left, Fraction right)
        {
            return left.Equals(right);
        }

        public static bool operator ==(Fraction left, long right)
        {
            return left.Equals(new Fraction(right));
        }

        public static bool operator ==(Fraction left, double right)
        {
            return left.Equals(new Fraction(right));
        }
        #endregion

        #region Not-equal operators
        public static bool operator !=(Fraction left, Fraction right)
        {
            return ! left.Equals(right);
        }

        public static bool operator !=(Fraction left, long right)
        {
            return ! left.Equals(new Fraction(right));
        }
        
        public static bool operator !=(Fraction left, double right)
        {
            return ! left.Equals(new Fraction(right));
        }
        #endregion

        #region Inequality operators
        /// <summary>
        /// Compares two Fractions to see if left is less than right
        /// </summary>
        /// <param name="left">The first Fraction</param>
        /// <param name="right">The second Fraction</param>
        /// <returns>True if <paramref name="left">left</paramref> is less
        /// than <paramref name="right">right</paramref></returns>
        /// <remarks>Special handling for indeterminates exists. <see>IndeterminateLess</see></remarks>
        /// <exception cref="FractionException">Throws an error if overflows occur while computing the 
        /// difference with an InnerException of OverflowException</exception>
        public static bool operator <(Fraction left, Fraction right)
        {
            // if left is an indeterminate, punt to the helper...
            if (left.Denominator == 0)
            {
                return IndeterminantLess(NormalizeIndeterminate(left.Numerator), right);
            }

            if (right.Denominator == 0)
            {
                // Only positive infinity compares favorably to real values
                return (NormalizeIndeterminate(right.Numerator) == Indeterminates.PositiveInfinity);
            }

            CrossReducePair(ref left, ref right);

            try
            {
                checked
                {
                    long leftScale = left.Numerator * right.Denominator;
                    long rightScale = left.Denominator * right.Numerator;

                    return leftScale < rightScale;
                }
            }
            catch (Exception e)
            {
                throw new FractionException("Compare < error", e);
            }
        }

        /// <summary>
        /// Compares two Fractions to see if left is greater than right
        /// </summary>
        /// <param name="left">The first Fraction</param>
        /// <param name="right">The second Fraction</param>
        /// <returns>True if <paramref name="left">left</paramref> is greater
        /// than <paramref name="right">right</paramref></returns>
        /// <remarks>Special handling for indeterminates exists. <see>IndeterminateLess</see></remarks>
        /// <exception cref="FractionException">Throws an error if overflows occur while computing the 
        /// difference with an InnerException of OverflowException</exception>
        public static bool operator >(Fraction left, Fraction right)
        {
            // if right is an indeterminate, punt to the helper...
            if (right.Denominator == 0)
            {
                // Note: swapped arguments because we want greater.
                return IndeterminantLess(NormalizeIndeterminate(right.Numerator), left);
            }

            if (left.Denominator == 0)
            {
                // Only positive infinity compares favorably to real values
                return (NormalizeIndeterminate(left.Numerator) == Indeterminates.PositiveInfinity);
            }

            CrossReducePair(ref left, ref right);

            try
            {
                checked
                {
                    long leftScale = left.Numerator * right.Denominator;
                    long rightScale = left.Denominator * right.Numerator;

                    return leftScale > rightScale;
                }
            }
            catch (Exception e)
            {
                throw new FractionException("Compare > error", e);
            }
        }

        /// <summary>
        /// Compares two Fractions to see if left is less than or equal to right
        /// </summary>
        /// <param name="left">The first Fraction</param>
        /// <param name="right">The second Fraction</param>
        /// <returns>True if <paramref name="left">left</paramref> is less than or 
        /// equal to <paramref name="right">right</paramref></returns>
        /// <remarks>Special handling for indeterminates exists. <see>IndeterminateLessEqual</see></remarks>
        /// <exception cref="FractionException">Throws an error if overflows occur while computing the 
        /// difference with an InnerException of OverflowException</exception>
        public static bool operator <=(Fraction left, Fraction right)
        {
            return ! (right > left);
        }
        
        /// <summary>
        /// Compares two Fractions to see if left is greater than or equal to right
        /// </summary>
        /// <param name="left">The first Fraction</param>
        /// <param name="right">The second Fraction</param>
        /// <returns>True if <paramref name="left">left</paramref> is greater than or 
        /// equal to <paramref name="right">right</paramref></returns>
        /// <remarks>Special handling for indeterminates exists. <see>IndeterminateLessEqual</see></remarks>
        /// <exception cref="FractionException">Throws an error if overflows occur while computing the 
        /// difference with an InnerException of OverflowException</exception>
        public static bool operator >=(Fraction left, Fraction right)
        {
            return ! (right < left);
        }
        #endregion

        #region Implict conversion from primitive operators
        /// <summary>
        /// Implicit conversion of a long integral value to a Fraction
        /// </summary>
        /// <param name="value">The long integral value to convert</param>
        /// <returns>A Fraction whose denominator is 1</returns>
        public static implicit operator Fraction(long value)
        {
            return new Fraction(value);
        }

        /// <summary>
        /// Implicit conversion of a double floating point value to a Fraction
        /// </summary>
        /// <param name="value">The double value to convert</param>
        /// <returns>A reduced Fraction</returns>
        public static implicit operator Fraction(double value)
        {
            return new Fraction(value);
        }

        /// <summary>
        /// Implicit conversion of a string to a Fraction
        /// </summary>
        /// <param name="value">The string to convert</param>
        /// <returns>A reduced Fraction</returns>
        public static implicit operator Fraction(string value)
        {
            return new Fraction(value);
        }
        #endregion

        #region Explicit converstion to primitive operators
        /// <summary>
        /// Explicit conversion from a Fraction to an integer
        /// </summary>
        /// <param name="frac">the Fraction to convert</param>
        /// <returns>The integral representation of the Fraction</returns>
        public static explicit operator int(Fraction frac)
        {
            return frac.ToInt32();
        }

        /// <summary>
        /// Explicit conversion from a Fraction to an integer
        /// </summary>
        /// <param name="frac">The Fraction to convert</param>
        /// <returns>The integral representation of the Fraction</returns>
        public static explicit operator long(Fraction frac)
        {
            return frac.ToInt64();
        }

        /// <summary>
        /// Explicit conversion from a Fraction to a double floating-point value
        /// </summary>
        /// <param name="frac">The Fraction to convert</param>
        /// <returns>The double representation of the Fraction</returns>
        public static explicit operator double(Fraction frac)
        {
            return frac.ToDouble();
        }

        /// <summary>
        /// Explicit conversion from a Fraction to a string
        /// </summary>
        /// <param name="frac">the Fraction to convert</param>
        /// <returns>The string representation of the Fraction</returns>
        public static implicit operator string(Fraction frac)
        {
            return frac.ToString();
        }
        #endregion
        #endregion

        #region Equals and GetHashCode overrides
        /// <summary>
        /// Compares for equality the current Fraction to the value passed.
        /// </summary>
        /// <param name="obj">A comparable object, a Fraction, a int/long, a float/double or a string</param>
        /// <returns>True if the value equals the current fraction, false otherwise (including for
        /// unsupported types or null object.</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            try 
            {
                Fraction frac;

                if (obj is Fraction)
                    frac = (Fraction)obj;
                else if (obj is long)
                    frac = new Fraction((long)obj);
                else if (obj is int)
                    frac = new Fraction((int)obj);
                else if (obj is double)
                    frac = new Fraction((double)obj);
                else if (obj is float)
                    frac = new Fraction((float)obj);
                else if (obj is string)
                    frac = new Fraction((string)obj);
                else 
                    return false;

                // insure we're as close to normalized as possible first
                ReduceFraction(ref this);

                // now normalize the comperand
                ReduceFraction(ref frac);

                return (this.Numerator == frac.Numerator && this.Denominator == frac.Denominator);
            }
            catch
            {
                // can't throw in an Equals!
                return false;
            }
        }
        
        /// <summary>
        /// Returns a hash code generated from the current Fraction
        /// </summary>
        /// <returns>The hash code</returns>
        /// <remarks>Reduces (in-place) the Fraction first.</remarks>
        public override int GetHashCode()
        {
            // insure we're as close to normalized as possible first
            ReduceFraction(ref this);
            return Convert.ToInt32((Numerator ^ Denominator) & 0xFFFFFFFF);
        }
        #endregion

        #region Reduction
        /// <summary>
        /// Reduces (simplifies) a Fraction by dividing down to lowest possible denominator (via GCD)
        /// </summary>
        /// <param name="frac">The Fraction to be reduced [WILL BE MODIFIED IN PLACE]</param>
        /// <remarks>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.</remarks>
        /// <example>2/4 will be reduced to 1/2</example>
        public static void ReduceFraction(ref Fraction frac)
        {
            // clean up the NaNs and infinites
            if (frac.Denominator == 0)
            {
                frac.Numerator = (long)NormalizeIndeterminate(frac.Numerator);
                return;
            }

            // all forms of zero are alike.
            if (frac.Numerator == 0)
            {
                frac.Denominator = 1;
                return;
            }
            
            long iGCD = GCD(frac.Numerator, frac.Denominator);
            frac.Numerator /= iGCD;
            frac.Denominator /= iGCD;
        
            // if negative sign in denominator
            if ( frac.Denominator < 0 )
            {
                //move negative sign to numerator
                frac.Numerator = - frac.Numerator;
                frac.Denominator = - frac.Denominator;  
            }
        }

        /// <summary>
        /// Cross-reduces a pair of Fractions so that we have the best GCD-reduced values for multiplication
        /// </summary>
        /// <param name="frac1">The first Fraction [WILL BE MODIFIED IN PLACE]</param>
        /// <param name="frac2">The second Fraction [WILL BE MODIFIED IN PLACE]</param>
        /// <remarks>Modifies the input arguments in-place!</remarks>
        /// <example>(3/4, 5/9) = (1/4, 5/3)</example>
        public static void CrossReducePair(ref Fraction frac1, ref Fraction frac2)
        {
            // leave the indeterminates alone!
            if (frac1.Denominator == 0 || frac2.Denominator == 0)
                return;

            long gcdTop = GCD(frac1.Numerator, frac2.Denominator);
            frac1.Numerator = frac1.Numerator / gcdTop;
            frac2.Denominator = frac2.Denominator / gcdTop;

            long gcdBottom = GCD(frac1.Denominator, frac2.Numerator);
            frac2.Numerator = frac2.Numerator / gcdBottom;
            frac1.Denominator = frac1.Denominator / gcdBottom;
        }
        #endregion

        #region Implementation
        #region Inequality helper
        /// <summary>
        /// Determines if the Fraction is less than the indeterminate type.
        /// </summary>
        /// <param name="frac1Type">What kind of indeterminate</param>
        /// <param name="frac2">The Fraction to compare</param>
        /// <returns>Returns true if indeterminate is less than the Fraction</returns>
        /// <remarks>NaN is less than anything except NaN and Negative Infinity. Negative Infinity is less
        /// than anything except Negative Infinity. Positive Infinity is not less than anything.</remarks>
        private static bool IndeterminantLess(Indeterminates frac1Type, Fraction frac2)
        {
            switch (frac1Type)
            {
                case Indeterminates.NaN:
                    // A NaN is less than any non-NaN except Negative Infinity
                    return ! (frac2.IsNaN() || frac2.IsNegativeInfinity());

                case Indeterminates.NegativeInfinity:
                    // Negative Infinity is less than anything except Negative Infinity
                    return ! frac2.IsNegativeInfinity();

                case Indeterminates.PositiveInfinity:
                    // Positive Infinity is NOT less than anything
                    return false;

                default:    // if this happens, something VERY wrong is going on...
                    return false;
            }
        }
        #endregion

        #region Math helpers
        /// <summary>
        /// Negates the Fraction
        /// </summary>
        /// <param name="frac">Value to negate</param>
        /// <returns>A new Fraction that is sign-flipped from the input</returns>
        private static Fraction Negate(Fraction frac)
        {
            return new Fraction( - frac.Numerator, frac.Denominator);
        }

        /// <summary>
        /// Adds two Fractions
        /// </summary>
        /// <param name="left">A Fraction</param>
        /// <param name="right">Another Fraction</param>
        /// <returns>Sum of the Fractions</returns>
        /// <exception cref="FractionException">Will throw if an overflow occurs when computing the
        /// GCD-normalized values.</exception>
        private static Fraction Add(Fraction left, Fraction right)
        {
            long gcd = GCD(left.Denominator, right.Denominator); // cannot return less than 1
            long leftDenominator = left.Denominator / gcd;
            long rightDenominator = right.Denominator / gcd;

            try
            {
                checked
                {
                    if (left.IsNaN() || right.IsNaN())
                        throw new ArithmeticException("Not-a-Number");

                    long numerator = left.Numerator * rightDenominator + right.Numerator * leftDenominator;
                    long denominator = leftDenominator * rightDenominator * gcd;

                    return new Fraction(numerator, denominator);
                }
            }
            catch (Exception e)
            {
                throw new FractionException("Add error", e);
            }
        }
    
        /// <summary>
        /// Multiplies two Fractions
        /// </summary>
        /// <param name="left">A Fraction</param>
        /// <param name="right">Another Fraction</param>
        /// <returns>Product of the Fractions</returns>
        /// <exception cref="FractionException">Will throw if an overflow occurs. Does a cross-reduce to 
        /// insure only the unavoidable overflows occur.</exception>
        private static Fraction Multiply(Fraction left, Fraction right)
        {
            // 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
                {
                    if (left.IsNaN() || right.IsNaN())
                        throw new ArithmeticException("Not-a-Number");

                    long numerator = left.Numerator * right.Numerator;
                    long denominator = left.Denominator * right.Denominator;

                    return new Fraction(numerator, denominator);
                }
            }
            catch (Exception e)
            {
                throw new FractionException("Multiply error", e);
            }
        }

        /// <summary>
        /// Computes the greatest common divisor for two values
        /// </summary>
        /// <param name="left">One value</param>
        /// <param name="right">Another value</param>
        /// <returns>The greatest common divisor of the two values</returns>
        /// <example>(6, 9) returns 3 and (11, 4) returns 1</example>
        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
        /// <summary>
        /// Gives the culture-related representation of the indeterminate types NaN, PositiveInfinity
        /// and NegativeInfinity
        /// </summary>
        /// <param name="numerator">The value in the numerator</param>
        /// <returns>The culture-specific string representation of the implied value</returns>
        /// <remarks>Only the sign and zero/non-zero information is relevant.</remarks>
        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;
            }
        }

        /// <summary>
        /// Gives the normalize representation of the indeterminate types NaN, PositiveInfinity
        /// and NegativeInfinity
        /// </summary>
        /// <param name="numerator">The value in the numerator</param>
        /// <returns>The normalized version of the indeterminate type</returns>
        /// <remarks>Only the sign and zero/non-zero information is relevant.</remarks>
        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;
            }
        }
        #endregion

        /// <summary>
        /// The actual members
        /// </summary>
        private long m_Numerator;
        private long m_Denominator;
        
        // These are used to represent the indeterminate with a Denominator of zero
        private enum Indeterminates
        {
            NaN = 0
            , PositiveInfinity = 1
            , NegativeInfinity = -1
        }
        #endregion
    }   //end class Fraction

    /// <summary>
    /// Exception class for Fraction, derived from System.Exception
    /// </summary>
    public class FractionException : Exception
    {
        /// <summary>
        /// Constructs a FractionException
        /// </summary>
        /// <param name="Message">String associated with the error message</param>
        /// <param name="InnerException">Actual inner exception caught</param>
        public FractionException(string Message, Exception InnerException) : base(Message, InnerException)
        {
        }
    }   //end class FractionException
}   //end namespace Mehroz

GeneralRe: Some improvements for this class. Pin
Syed Mehroz Alam20-Dec-04 20:06
Syed Mehroz Alam20-Dec-04 20:06 
GeneralRe: Some improvements for this class. Pin
Jeffrey Sax20-Dec-04 21:28
Jeffrey Sax20-Dec-04 21:28 
GeneralRe: Some improvements for this class. Pin
Marc Brooks11-Jan-05 10:26
Marc Brooks11-Jan-05 10:26 
GeneralRe: Some improvements for this class. Pin
Hardy Wang7-Mar-09 7:01
Hardy Wang7-Mar-09 7:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.