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, so 2/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);        }    }}
Formattable Fraction
c#;formatting;rational numbers
Immutable structsYay! Format providerIt seems odd to see a format provider in a constructor, and stranger still to store a format provider for every fraction instance. Think of the memory overhead -- imagine a use case where I perform calculations on millions of fractions, but only want to print the end result.WhoopsThis will not terminate:Console.WriteLine(new Fraction(1, 1, CultureInfo.InvariantCulture));Edge casesAs @nhgrif hinted at, you should throw an ArgumentOutOfRange exception in the constructor for a zero denominator.As @nhgrif mentioned, you need to be careful handling zero denominators. For example,Console.WriteLine(new Fraction(1) / new Fraction(1, 0));0/1RecursionGetGreatestCommonDenominator can be written iteratively, instead of recursively.CultureWhy are you getting the CultureInfo from the assembly? I'm not sure, but I think it would be better to use CultureInfo.CurrentCulture.APIAdd a ToString(IFormatProvider) method so you don't have to call it with a first argument of null.You might also consider providing MaxValue, MinValue, Zero, and One fields.Comparing fractionsThough I can't say it's wrong, it does feel weird to use floating-point values to compare fractions:public int CompareTo(Fraction other){    return ToDecimal().CompareTo(other.ToDecimal());}I would expect something like this:public int CompareTo(Fraction other){    // TODO: handle case where this and/or other have zero denominator.    long lhs = _numerator * other._denominator;    long rhs = other._numerator * _denominator;    return lhs.CompareTo(rhs);}Then,public bool Equals(Fraction other){    return ToDecimal().Equals(other.ToDecimal());}could bepublic bool Equals(Fraction other){    return this.CompareTo(other) == 0;}Nit-pickingFinally, though it's a matter of preference, I would remove the redundant elses, e.g.if (obj is int){    return CompareTo(new Fraction((int)obj, 1));}else if (obj is string){...