I have written this small little program, to test how my Fraction
type behaves when used with and without a custom FractionFormatter
- I've implemented two custom formatters:
FractionFormatter
, which simply puts the numerator at the left of a slash, and the denominator at the right ("{0}/{1}", fraction.Numerator, fraction.Denominator
),MathJaxFractionFormatter
, pushes the customization a bit further by formatting the fraction as MathJax, so2/5
could be rendered as\$\frac{2}{5}\$
which produces \$\frac{2}{5}\$ in CR posts.
static void Main(string[] args) { var fraction1 = new Fraction(2); var fraction2 = new Fraction(2, 4); Console.WriteLine("Fraction1: {0} (decimal: {1})", fraction1, fraction1.ToDecimal()); Console.WriteLine("Fraction2: {0} (decimal: {1})", fraction2, fraction2.ToDecimal()); Console.WriteLine("{0} + {1} = {2} (decimal: {3})", fraction1, fraction2, fraction1 + fraction2, (fraction1 + fraction2).ToDecimal()); Console.WriteLine("{0} - {1} = {2} (decimal: {3})", fraction1, fraction2, fraction1 - fraction2, (fraction1 - fraction2).ToDecimal()); Console.WriteLine("{0} * {1} = {2} (decimal: {3})", fraction1, fraction2, fraction1 * fraction2, (fraction1 * fraction2).ToDecimal()); Console.WriteLine("{0} / {1} = {2} (decimal: {3})", fraction1, fraction2, fraction1 / fraction2, (fraction1 / fraction2).ToDecimal()); var jaxFormatter = new MathJaxFractionFormatter(); var fraction3 = new Fraction(2, jaxFormatter); var fraction4 = new Fraction(2, 4, jaxFormatter); Console.WriteLine("Fraction3: {0} ({1})", fraction3, fraction3.ToString(null, new FractionFormatter())); Console.WriteLine("Fraction4: {0} ({1})", fraction4, fraction4.ToString(null, new FractionFormatter())); var crJaxFormatter = new MathJaxFractionFormatter("\\$", MathJaxFractionFormatter.MathJaxFractionSize.Large); var fraction5 = new Fraction(2, crJaxFormatter); var fraction6 = new Fraction(2, 4, crJaxFormatter); Console.WriteLine("Fraction5: {0}", fraction5); Console.WriteLine("Fraction6: {0}", fraction6); Console.ReadLine(); }
The sandbox program above outputs this:
Fraction1: 2/1 (decimal: 2) Fraction2: 2/4 (decimal: 0.5) 2/1 + 2/4 = 5/2 (decimal: 2.5) 2/1 - 2/4 = 3/2 (decimal: 1.5) 2/1 * 2/4 = 1/1 (decimal: 1) 2/1 / 2/4 = 4/1 (decimal: 4) Fraction3: $\frac{2}{1}$ (2/1) Fraction4: $\frac{2}{4}$ (2/4) Fraction5: \$\dfrac{2}{1}\$ Fraction6: \$\dfrac{2}{4}\$
The Fraction
type was largely inspired (ok, stolen) from this Objective-C question - my implementation is an immutable struct
though:
[Serializable]
public struct Fraction : IFormattable,
IComparable,
IComparable<Fraction>,
IEquatable<Fraction>
{
private readonly IFormatProvider _formatProvider;
private readonly int _numerator;
private readonly int _denominator;
public Fraction(int numerator)
: this(numerator, 1)
{
}
public Fraction(int numerator, int denominator)
: this(numerator, denominator, null)
{
}
public Fraction(int numerator, IFormatProvider formatProvider)
: this(numerator, 1, formatProvider)
{
}
public Fraction(int numerator, int denominator, IFormatProvider formatProvider)
{
_numerator = numerator;
_denominator = denominator;
_formatProvider = formatProvider ?? new FractionFormatter();
}
public int Numerator { get { return _numerator; } }
public int Denominator { get { return _denominator; } }
public Fraction Simplify()
{
var greatestCommonDenominator = GetGreatestCommonDenominator(_numerator, _denominator);
var numerator = _numerator / greatestCommonDenominator;
var denominator = _denominator / greatestCommonDenominator;
return new Fraction(numerator, denominator);
}
public decimal ToDecimal()
{
return (decimal)_numerator / (decimal)_denominator;
}
private int GetGreatestCommonDenominator(int numerator, int denominator)
{
return denominator == 0 ? numerator
: GetGreatestCommonDenominator(denominator, numerator % denominator);
}
public static explicit operator decimal (Fraction fraction)
{
return fraction.ToDecimal();
}
public static Fraction operator +(Fraction fraction, int integer)
{
return fraction + new Fraction(integer);
}
public static Fraction operator +(Fraction fraction1, Fraction fraction2)
{
int numerator = (fraction1.Numerator * fraction2.Denominator) + (fraction1.Denominator * fraction2.Numerator);
int denominator = (fraction1.Denominator * fraction2.Denominator);
var result = new Fraction(numerator, denominator).Simplify();
return result;
}
public static Fraction operator -(Fraction fraction, int integer)
{
return fraction - new Fraction(integer);
}
public static Fraction operator -(Fraction fraction1, Fraction fraction2)
{
var subtrator = new Fraction(fraction2.Numerator*-1, fraction2.Denominator);
return fraction1 + subtrator;
}
public static Fraction operator /(Fraction fraction, int integer)
{
return fraction / new Fraction(integer);
}
public static Fraction operator /(Fraction fraction1, Fraction fraction2)
{
var divisor = new Fraction(fraction2.Denominator, fraction2.Numerator);
return fraction1 * divisor;
}
public static Fraction operator *(Fraction fraction, int integer)
{
return fraction * new Fraction(integer);
}
public static Fraction operator *(Fraction fraction1, Fraction fraction2)
{
var numerator = fraction1.Numerator * fraction2.Numerator;
var denominator = fraction1.Denominator * fraction2.Denominator;
var result = new Fraction(numerator, denominator).Simplify();
return result;
}
public override bool Equals(object obj)
{
return ToDecimal().Equals((decimal)obj);
}
public override int GetHashCode()
{
return ToDecimal().GetHashCode();
}
public override string ToString()
{
return ToString(null, _formatProvider);
}
public string ToString(string format, IFormatProvider formatProvider)
{
if (formatProvider is ICustomFormatter)
{
return ((ICustomFormatter)formatProvider).Format(format, this, formatProvider);
}
else
{
return ToString();
}
}
public int CompareTo(object obj)
{
if (obj is int)
{
return CompareTo(new Fraction((int)obj, 1));
}
else if (obj is string)
{
int intValue;
if (int.TryParse(obj as string, out intValue))
{
return CompareTo(new Fraction(intValue));
}
}
// will throw an InvalidCastException when obj cannot be cast to a Fraction:
return CompareTo((Fraction)obj);
}
public int CompareTo(Fraction other)
{
return ToDecimal().CompareTo(other.ToDecimal());
}
public bool Equals(Fraction other)
{
return ToDecimal().Equals(other.ToDecimal());
}
}
The FractionFormatter
:
public class FractionFormatter : IFormatProvider, ICustomFormatter
{
private static readonly CultureInfo _culture = typeof(FractionFormatter).Assembly.GetName().CultureInfo;
public object GetFormat(Type formatType)
{
return (formatType == typeof(ICustomFormatter)) ? this : null;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
var fraction = (Fraction)arg;
if (string.IsNullOrEmpty(format))
{
return string.Format(_culture, "{0}/{1}", fraction.Numerator, fraction.Denominator);
}
else
{
return fraction.ToString(format, _culture);
}
}
}
And the MathJaxFractionFormatter
:
public class MathJaxFractionFormatter : IFormatProvider, ICustomFormatter
{
public enum MathJaxFractionSize
{
Normal,
Large
}
private static readonly CultureInfo _culture = typeof(FractionFormatter).Assembly.GetName().CultureInfo;
private readonly string _delimiter;
private readonly MathJaxFractionSize _size;
public MathJaxFractionFormatter()
: this("$", MathJaxFractionSize.Normal) { }
public MathJaxFractionFormatter(string delimiter, MathJaxFractionSize size)
{
_delimiter = delimiter;
_size = size;
}
public object GetFormat(Type formatType)
{
return (formatType == typeof(ICustomFormatter)) ? this : null;
}
public string Format(string format, object arg, IFormatProvider formatProvider)
{
var fraction = (Fraction)arg;
if (string.IsNullOrEmpty(format))
{
var keyword = _size == MathJaxFractionSize.Normal ? "\\frac" : "\\dfrac";
return string.Format(_culture, "{2}{3}{{{0}}}{{{1}}}{2}", fraction.Numerator, fraction.Denominator, _delimiter, keyword);
}
else
{
return fraction.ToString(format, _culture);
}
}
}
DivideByZeroException
wheneverToDecimal
gets called, ...which meansGetHashCode
can't run for such an instance! \$\endgroup\$