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.

Share

About the Author

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

Just tried the class and it works better than any other fraction class I have tried. One, maybe very specific for me, problem is if you read a decimal number (from a file) that is written in an another culture than what the program is run under. E.g. Living in Sweden we have comma as the decimal separator. Using the testFraction.cs with it's test data it will crash...

So I added a regex statement that replaces all different separators for the current one. Just one line, but it makes the class more resilient.

// Anders

publicstatic Fraction ToFraction(string inValue)
{
if (inValue == null || inValue == string.Empty)
thrownew 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;
elseif (trimmedValue == info.PositiveInfinitySymbol)
return PositiveInfinity;
elseif (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));
returnnew 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?
// make sure that we use CurrencyDecimalSeparator as decimal point
inValue = Regex.Replace(inValue, @"[\.\?,;/-]", CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator);
int decimalPos = inValue.IndexOf(info.CurrencyDecimalSeparator);
if (decimalPos > -1)
returnnew Fraction(Convert.ToDouble(inValue));
elsereturnnew Fraction(Convert.ToInt64(inValue));
}
}
}

Apart from being an excellent class I'd like to note that the GCD method is done in base 10 arithmatic. It would benefit greatly if done in binary using binary shifts doing the division. I've located a C++ algorithm on wikipedia to do exactly that http://en.wikipedia.org/wiki/Binary_GCD_algorithm[^]