//----------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All Rights Reserved. // //----------------------------------------------------------------------------- using System; using System.Diagnostics.Contracts; namespace Microsoft.Basetypes { using BNM = Microsoft.FSharp.Math.BigRational; /// /// The representation of a rational number. /// public struct Rational { public static readonly Rational ZERO = new Rational(cce.NonNull(BNM.Zero)); public static readonly Rational ONE = new Rational(cce.NonNull(BNM.One)); public static readonly Rational MINUS_ONE = new Rational(cce.NonNull(-BNM.One)); private readonly Microsoft.FSharp.Math.BigRational _val; private Microsoft.FSharp.Math.BigRational val { get { Contract.Ensures(Contract.Result() != null); if ((object)_val == null) return cce.NonNull(BNM.Zero); else return _val; } } // int numerator; // int denominator; // invariant: 0 < denominator || (numerator == 0 && denominator == 0); // invariant: numerator != 0 ==> gcd(abs(numerator),denominator) == 1; // invariant: numerator == 0 ==> denominator == 1 || denominator == 0; private Rational(int x) { this._val = BNM.FromInt(x); } public static Rational FromInt(int x) { return new Rational(x); } private Rational(Microsoft.FSharp.Math.BigRational/*!*/ val) { Contract.Requires(val != null); this._val = val; } public Rational(BigNum num, BigNum den) { System.Diagnostics.Debug.Assert(!den.IsZero); this._val = BNM.FromBigInt(num.val) / BNM.FromBigInt(den.val); } public static Rational FromInts(int num, int den) { return new Rational(BigNum.FromInt(num), BigNum.FromInt(den)); } /// /// Returns the absolute value of the rational. /// public Rational Abs() { Contract.Ensures(Contract.Result().IsNonNegative); if (IsNonNegative) { return this; } else { return -this; } } /// /// Returns a rational whose numerator and denominator, resepctively, are the Gcd /// of the numerators and denominators of r and s. If one of r and s is 0, the absolute /// value of the other is returned. If both are 0, 1 is returned. /// public static Rational Gcd(Rational r, Rational s) { Contract.Ensures(Contract.Result().IsPositive); if (r.IsZero) { if (s.IsZero) { return ONE; } else { return s.Abs(); } } else if (s.IsZero) { return r.Abs(); } else { return new Rational(r.Numerator.Gcd(s.Numerator), r.Denominator.Gcd(s.Denominator)); } } public BigNum Numerator { get { // Better way to obtain the numerator? // The FSharp library does not seem to offer any methods for this string/*!*/ lin = cce.NonNull(val.ToString()); int i = lin.IndexOf('/'); if (i == -1) return new BigNum(BNM.ToBigInt(this.val)); else return BigNum.FromString(lin.Substring(0, i)); } } public BigNum Denominator { get { // Better way to obtain the numerator? // The FSharp library does not seem to offer any methods for this string/*!*/ lin = cce.NonNull(val.ToString()); int i = lin.IndexOf('/'); if (i == -1) return BigNum.ONE; else return BigNum.FromString(lin.Substring(i + 1)); } } public override string/*!*/ ToString() { Contract.Ensures(Contract.Result() != null); return String.Format("{0}/{1}", Numerator, Denominator); } public static bool operator ==(Rational r, Rational s) { return (r.val == s.val); } public static bool operator !=(Rational r, Rational s) { return !(r.val == s.val); } public override bool Equals(object obj) { if (obj == null) return false; return obj is Rational && (Rational)obj == this; } public override int GetHashCode() { return this.val.GetHashCode(); } public int Signum { get { return this.val.Sign; } } public bool IsZero { get { return Signum == 0; } } public bool IsNonZero { get { return Signum != 0; } } public bool IsIntegral { get { // better way? return !cce.NonNull(this.val.ToString()).Contains("/"); } } [Pure] [Reads(ReadsAttribute.Reads.Nothing)] public bool HasValue(int n) { return this == new Rational(n); } /// /// Returns the rational as an integer. Requires the rational to be integral. /// public int AsInteger { get { System.Diagnostics.Debug.Assert(this.IsIntegral); return Numerator.ToIntSafe; } } public BigNum AsBigNum { get { System.Diagnostics.Debug.Assert(this.IsIntegral); return new BigNum(BNM.ToBigInt(this.val)); } } public double AsDouble { [Pure] get { if (this.IsZero) { return 0.0; } else { return (double)Numerator.ToIntSafe / (double)Denominator.ToIntSafe; } } } public bool IsNegative { [Pure] get { return Signum < 0; } } public bool IsPositive { [Pure] get { return 0 < Signum; } } public bool IsNonNegative { [Pure] get { return 0 <= Signum; } } public static Rational operator -(Rational r) { return new Rational(cce.NonNull(BNM.Zero - r.val)); // for whatever reason, the Spec# compiler refuses to accept -r.val (unary negation) } public static Rational operator +(Rational r, Rational s) { return new Rational(cce.NonNull(r.val + s.val)); } public static Rational operator -(Rational r, Rational s) { return new Rational(cce.NonNull((r.val - s.val))); } public static Rational operator *(Rational r, Rational s) { return new Rational(cce.NonNull(r.val * s.val)); } public static Rational operator /(Rational r, Rational s) { return new Rational(cce.NonNull(r.val / s.val)); } public static bool operator <=(Rational r, Rational s) { return (r.val <= s.val); } public static bool operator >=(Rational r, Rational s) { return (r.val >= s.val); } public static bool operator <(Rational r, Rational s) { return (r.val < s.val); } public static bool operator >(Rational r, Rational s) { return (r.val > s.val); } } }