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.
Fraction frac=new Fraction();
frac=new Fraction(1,5);
frac=new Fraction(25);
frac=new Fraction(9.25);
frac=new Fraction("6.25");
frac=new Fraction( System.Console.ReadLine() );
Console.WriteLine( frac );
Operators overloaded (overloaded for fractions, integers and doubles) for Fraction
object include:
- Unary:
-
(Negation)
- Binary
+
, -
, *
, /
- Relational operators such as
==
, !=
, <
, >
, <=
, >=
.
Fraction frac=new Fraction("1/2");
Console.WriteLine( frac+2.5 );
Overloaded conversion further enhances the capabilities of the class. See how simple it is to work with fractions:
Fraction frac="1/2"
frac="22.5"
frac=10.25
frac=15
Finally, as an exercise, guess the output of the following code:
Fraction f=0.5;
Console.WriteLine( f-0.25 );
Console.WriteLine( f+"1/4" );
Implementation details
The class uses simple mathematics to do all the work. Let us see some of these simple techniques:
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 double
s [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]