Click here to Skip to main content
15,904,153 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.8K   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

 
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 
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 
GeneralRe: Some improvements for this class. Pin
dynamichael31-Jul-20 19:27
dynamichael31-Jul-20 19:27 
GeneralA few suggestions Pin
Jeffrey Sax16-Dec-04 16:19
Jeffrey Sax16-Dec-04 16:19 
Overall, this is a nice implementation of basic fraction functionality. There is still room for improvement, however:
  1. Conversion from Double should use base 2 rather than base 10. The 'ToString()' causes loss of precision in several ways, most notably due to the fact that it uses only 15 digits of precision.

    Every double value within the range of Fraction is exactly representable by a Fraction, and so you should make this conversion exact. A round-trip cast expression like (double)(Fraction)x should return the original value x, or throw an OverflowException if x is out of range.

    Ideally, you would also offer to find the fraction closest to the number, using Euclid's GCD algorithm backwards. You could put an upper limit on the size of the denominator, so that, for example, 0.333333334 gets converted to 1/3 rather than 166666667/500000000.
  2. Your addition method can cause overflow when that is not necessary. You should divide out any common factors of the denominators. This common factor will only be divided out in the normalization. You could do something like:
    long gcd = GCD(frac1.Denominator, frac2.Denominator);
    long denominator1 = frac1.Denominator / gcd;
    long denominator2 = frac2.Denominator / gcd;
    long iNumerator=frac1.Numerator*denominator2 + frac2.Numerator*denominator1;
    long iDenominator=denominator1*denominator2*gcd;
    return ( new Fraction(iNumerator, iDenominator) );
    Similar improvements are possible for the other operators.
  3. You may want to consider making this a value type (struct). The reasoning? We definitely have an object with value semantics here - it is just an alternate representation of a number. Moreover, it is small enough (16 bytes) so the by-value passing of value types shouldn't hinder performance.

    It would also eliminate the need for methods like Duplicate and properties like Value, since you can use direct assignments.
  4. Your implementation of Equals does not comply with the guidelines[^]. For example, your code throws an exception if the argument isn't of type Fraction. Equals should never throw an exception.
There may well be more. Improving code is like ironing out all the wrinkles: you get some of the bigger ones first, and then take care of the progressively smaller ones.

Nice work!

Jeffrey

Everything should be as simple as possible, but not simpler.
    -- Albert Einstein

Numerical components for C# and VB.NET
GeneralRe: A few suggestions Pin
Syed Mehroz Alam20-Dec-04 19:54
Syed Mehroz Alam20-Dec-04 19:54 
GeneralRe: A few suggestions Pin
Jeffrey Sax20-Dec-04 21:45
Jeffrey Sax20-Dec-04 21:45 

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.