PowerShellで分数を扱う

PowerShellで分数が使いたくなった。
なので、C#で書いてみた。
演算子オーバーロードが意外に書くのが面倒。
ジェネリクスでなんとかできないか?、、、。



スクリプト、、、が長いので先に解説・使い方

June.Fractionクラスにコンパイルします。
基本的に扱うのはintですが、キャストだけはdecimalに対応しています。
.ToString()すると、約分して"n/m"表記にします。
四則演算・比較が使えます。

PS > $a = New-Object June.Fraction 1,2
PS > $a.ToString()
1/2
PS > $b = [June.Fraction]1/3
PS > $b.ToString()
1/3
PS > ([June.Fraction]0.25).ToString()
1/4
PS > ($a + $b).ToString()
5/6
PS > ($a - $b).ToString()
1/6
PS > ($a * $b).ToString()
1/6
PS > ($a / $b).ToString()
3/2
PS > $a -lt $b
False
PS > $a -gt $b
True

なお、PowerShellの比較演算子はIComparable依存の模様。
演算子オーバーロードだけでは怒られた。


どうでも良いけど「[June.Fraction]1/3」の意味は、「intの1をFractionにキャストして3で割る」であって「1/3をFractionにキャストする」ではないけど、後者に見えるのがお気に入り。

スクリプト

if(!('June.Fraction' -as [type])) {
	Add-Type -TypeDefinition @"
using System;

namespace June
{
    public struct Fraction : IComparable
    {
        #region property
        public int Numerator { get; private set; }
        public int Denominator { get; private set; }
        public int DisplayMultiplier { get; set; }
        #endregion

        #region public static method
        public static int GetGreatestCommonDivisor(int a, int b)
        {
            // Euclidean Algorithm
            int t = a;
            if (Math.Abs(b) > Math.Abs(a))
            {
                a = b;
                b = t;
            }
            while (b != 0)
            {
                t = a % b;
                a = b;
                b = t;
            }
            if (a == 0) a = 1;
            return a;
        }
        public static bool TryParse(string a, out Fraction b)
        {
            bool ret = false;
            decimal d;
            string[] c = a.Split('/');
            int nu, de;
            if (decimal.TryParse(a, out d))
            {
                b = new Fraction(d, 10);
                ret = true;
            }
            else if (c.Length == 2 && int.TryParse(c[0], out nu) && int.TryParse(c[1], out de))
            {
                b = new Fraction(nu, de);
                ret = true;
            }
            else
            {
                b = new Fraction();
            }
            return ret;
        }
        public static Fraction Parse(string a)
        {
            Fraction ret;
            if (!Fraction.TryParse(a, out ret))
            {
                throw new FormatException();
            }
            return ret;
        }

        #region operator +
        public static Fraction operator +(Fraction a, Fraction b)
        {
            int gcd = GetGreatestCommonDivisor(a.Denominator, b.Denominator);
            return new Fraction(a.Numerator * (b.Denominator / gcd) + b.Numerator * (a.Denominator / gcd), a.Denominator * (b.Denominator / gcd));
        }
        public static Fraction operator +(Fraction a, int b)
        {
            return new Fraction(a.Numerator + b * a.Denominator, a.Denominator);
        }
        public static Fraction operator +(int a, Fraction b)
        {
            return (b + a);
        }
        public static Fraction operator +(Fraction a)
        {
            return a;
        }
        #endregion
        #region operator -
        public static Fraction operator -(Fraction a, Fraction b)
        {
            int gcd = GetGreatestCommonDivisor(a.Denominator, b.Denominator);
            return new Fraction(a.Numerator * (b.Denominator / gcd) - b.Numerator * (a.Denominator / gcd), a.Denominator * (b.Denominator / gcd));
        }
        public static Fraction operator -(Fraction a, int b)
        {
            return new Fraction(a.Numerator - b * a.Denominator, a.Denominator);
        }
        public static Fraction operator -(int b, Fraction a)
        {
            return new Fraction(b * a.Denominator - a.Numerator, a.Denominator);
        }
        public static Fraction operator -(Fraction a)
        {
            return new Fraction(-a.Numerator, a.Denominator);
        }
        #endregion
        #region operator *
        public static Fraction operator *(Fraction a, Fraction b)
        {
            return new Fraction(a.Numerator * b.Numerator, a.Denominator * b.Denominator);
        }
        public static Fraction operator *(Fraction a, int b)
        {
            return (new Fraction(a.Numerator * b, a.Denominator));
        }
        public static Fraction operator *(int a, Fraction b)
        {
            return (b * a);
        }
        #endregion
        #region operator /
        public static Fraction operator /(Fraction a, Fraction b)
        {
            return new Fraction(a.Numerator * b.Denominator, a.Denominator * b.Numerator);
        }
        public static Fraction operator /(Fraction a, int b)
        {
            return new Fraction(a.Numerator, a.Denominator * b);
        }
        public static Fraction operator /(int a, Fraction b)
        {
            return new Fraction(a * b.Denominator, b.Numerator);
        }
        #endregion
        #region operator ++, --
        public static Fraction operator ++(Fraction a)
        {
            return (a + 1);
        }
        public static Fraction operator --(Fraction a)
        {
            return (a - 1);
        }
        #endregion
        #region operator ==
        public static bool operator ==(Fraction a, Fraction b)
        {
            bool ret = (a.Numerator == b.Numerator);
            if (a.Numerator != 0) ret &= (a.Denominator == b.Denominator);
            if (ret && a.Denominator == 0) ret = false;
            return ret;
        }
        public static bool operator ==(Fraction a, int b)
        {
            return (a.Denominator == 1 && (a.Numerator == b));
        }
        public static bool operator ==(int a, Fraction b)
        {
            return (b == a);
        }
        #endregion
        #region operator !=
        public static bool operator !=(Fraction a, Fraction b)
        {
            return !(a == b);
        }
        public static bool operator !=(Fraction a, int b)
        {
            return !(a == b);
        }
        public static bool operator !=(int a, Fraction b)
        {
            return !(b == a);
        }
        #endregion
        #region operator <
        public static bool operator <(Fraction a, Fraction b)
        {
            return (a.Numerator * b.Denominator < b.Numerator * a.Denominator);
        }
        public static bool operator <(Fraction a, int b)
        {
            return (a.Numerator < b * a.Denominator);
        }
        public static bool operator <(int a, Fraction b)
        {
            return (b.Numerator > a * b.Denominator);
        }
        #endregion
        #region operator >
        public static bool operator >(Fraction a, Fraction b)
        {
            return (a.Numerator * b.Denominator > b.Numerator * a.Denominator);
        }
        public static bool operator >(Fraction a, int b)
        {
            return (a.Numerator > b * a.Denominator);
        }
        public static bool operator >(int a, Fraction b)
        {
            return (b.Numerator < a * b.Denominator);
        }
        #endregion
        #region operator <=
        public static bool operator <=(Fraction a, Fraction b)
        {
            return (a.Numerator * b.Denominator <= b.Numerator * a.Denominator);
        }
        public static bool operator <=(Fraction a, int b)
        {
            return (a.Numerator <= b * a.Denominator);
        }
        public static bool operator <=(int a, Fraction b)
        {
            return (b.Numerator >= a * b.Denominator);
        }
        #endregion
        #region operator >=
        public static bool operator >=(Fraction a, Fraction b)
        {
            return (a.Numerator * b.Denominator >= b.Numerator * a.Denominator);
        }
        public static bool operator >=(Fraction a, int b)
        {
            return (a.Numerator >= b * a.Denominator);
        }
        public static bool operator >=(int a, Fraction b)
        {
            return (b.Numerator <= a * b.Denominator);
        }
        #endregion
        #region operator cast
        public static explicit operator Fraction(int a) { return new Fraction(a, 1); }
        public static explicit operator int(Fraction a) { return (int)(a.Numerator / a.Denominator); }
        public static explicit operator Fraction(decimal a) { return new Fraction(a, 10); }
        public static explicit operator decimal(Fraction a) { return (a.Numerator / (decimal)a.Denominator); }
        #endregion
        #endregion

        #region constructor
        public Fraction(int numerator, int denominator) : this()
        {
            int gcd = GetGreatestCommonDivisor(numerator, denominator);
            if (gcd < 0)
            {
                gcd = Math.Abs(gcd);
                numerator = -Math.Abs(numerator);
                denominator = Math.Abs(denominator);
            }
            Numerator = numerator / gcd;
            Denominator = denominator / gcd;
            DisplayMultiplier = gcd;
        }
        public Fraction(decimal num, int ba) : this()
        {
            int de = 1;
            while (Math.Floor(num) != num && num <= int.MaxValue / ba && num >= int.MinValue / ba && de <= int.MaxValue / ba)
            {
                num *= ba;
                de *= ba;
            }
            Numerator = (int)Math.Floor(num);
            int gcd = GetGreatestCommonDivisor(Numerator, de);
            if (gcd < 0)
            {
                gcd = Math.Abs(gcd);
                Numerator = -Math.Abs(Numerator);
                de = Math.Abs(de);
            }
            Numerator = Numerator / gcd;
            Denominator = de / gcd;
            DisplayMultiplier = gcd;
        }
        #endregion

        #region public method
        public string ToString(bool bReduce)
        {
            int gcd = DisplayMultiplier;
            if (bReduce || DisplayMultiplier == 0) gcd = 1;
            string ret;
            if (Denominator * gcd == 1)
            {
                ret = string.Format("{0}", Numerator * gcd);
            }
            else
            {
                ret = string.Format("{0}/{1}", Numerator * gcd, Denominator * gcd);
            }
            return ret;
        }

        public override string ToString()
        {
            return ToString(true);
        }
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            bool ret = false;
            if (obj is Fraction || obj is decimal || obj is int)
            {
                ret = (this == (Fraction)obj);
            }
            return ret;
        }

        public int CompareTo(object obj)
        {
            decimal ret;
            if (obj is Fraction || obj is decimal || obj is int)
            {
                ret = (decimal)(this - (Fraction)obj);
            }
            else
            {
                throw new ArgumentException();
            }
            if (ret < 0) ret--;
            return (int)Math.Ceiling(ret);
        }
        #endregion
    }
}
"@}