815 lines
33 KiB
C#
Raw Normal View History

//
// Rational.cs
//
// Authors:
// Alexander Chebaturkin (chebaturkin@gmail.com)
//
// Copyright (C) 2012 Alexander Chebaturkin
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using Mono.CodeContracts.Static.DataStructures;
namespace Mono.CodeContracts.Static.Analysis.Numerical {
/// <summary>
/// Represents a rational number.
/// </summary>
public struct Rational : IEquatable<Rational> {
public static readonly Rational Zero = new Rational (0L);
public static readonly Rational One = new Rational (1L);
public static readonly Rational MinusOne = new Rational (-1L);
public static Rational PlusInfinity = new Rational (Kind.PlusInfinity);
public static Rational MinusInfinity = new Rational (Kind.MinusInfinity);
public static Rational MaxValue = new Rational (long.MaxValue);
public static Rational MinValue = new Rational (long.MinValue);
readonly long down;
readonly Kind kind;
readonly long up;
Rational (Kind kind)
{
if (kind == Kind.Normal)
throw new ArgumentException (
"Kind should be equal to Kind.PlusInfinity or Kind.MinusInfinity", "kind");
this.kind = kind;
this.up = 0L;
this.down = 0L;
}
Rational (long number)
: this (number, 1L)
{
}
Rational (long nominator, long denominator)
{
if (denominator == 0L)
throw new ArgumentException ("Denominator should not be equal to 0");
if (nominator == 0L) {
this.kind = Kind.Normal;
this.up = 0L;
this.down = 1L;
return;
}
this.kind = Kind.Normal;
int sign = System.Math.Sign (nominator) * System.Math.Sign (denominator);
if (nominator == long.MinValue)
nominator = sign >= 0 ? long.MaxValue : long.MinValue;
else
nominator = sign * System.Math.Abs (nominator);
if (denominator == long.MinValue)
denominator = long.MaxValue;
else
denominator = System.Math.Abs (denominator);
if (nominator % denominator == 0) {
this.up = nominator / denominator;
this.down = 1L;
return;
}
long gcd = (nominator == 0L || nominator == long.MaxValue ||
denominator == 0L || denominator == long.MaxValue)
? 1L
: GCD (System.Math.Abs (nominator), System.Math.Abs (denominator));
this.up = nominator / gcd;
this.down = denominator / gcd;
}
public bool IsInteger { get { return !this.IsInfinity && (this.up % this.down == 0L); } }
public bool IsInt32 { get { return this.IsInteger && this.up >= int.MinValue && this.up <= int.MaxValue; } }
public bool IsInfinity { get { return this.kind == Kind.PlusInfinity || this.kind == Kind.MinusInfinity; } }
public bool IsPlusInfinity { get { return this.kind == Kind.PlusInfinity; } }
public bool IsMinusInfinity { get { return this.kind == Kind.MinusInfinity; } }
public bool IsZero { get { return this.kind == Kind.Normal && this.up == 0L; } }
public bool IsMaxValue { get { return this.kind == Kind.Normal && this.up == long.MaxValue && this.down == 1L; } }
public bool IsMinValue { get { return this.kind == Kind.Normal && this.up == -long.MaxValue && this.down == 1L; } }
public int Sign { get { return GetSign (this); } }
public Rational NextInt32
{
get
{
if (this.IsInfinity)
return this;
var next = (long) System.Math.Ceiling ((double) this);
return For (next >= int.MaxValue ? int.MaxValue : next);
}
}
public Rational PreviousInt32
{
get
{
if (this.IsInfinity)
return this;
var prev = (long) System.Math.Floor ((double) this);
return For (prev <= int.MinValue ? int.MinValue : prev);
}
}
public Rational NextInt64
{
get
{
if (this.IsInfinity)
return this;
double next = System.Math.Ceiling ((double) this);
return For (next >= (double) long.MaxValue ? long.MaxValue : (long) System.Math.Truncate (next));
}
}
public long Up { get { return up; } }
public long Down { get { return down; } }
public bool IsInRange (long min, long max)
{
return min <= this && this <= max;
}
public static Rational For (long number)
{
return new Rational (number);
}
public static Rational For (long nominator, long denominator)
{
switch (denominator) {
case 0L:
return new Rational (nominator >= 0 ? Kind.PlusInfinity : Kind.MinusInfinity);
default:
return new Rational (nominator, denominator);
}
}
public static bool operator == (Rational l, Rational r)
{
if (l.kind != r.kind)
return false;
if (l.kind != Kind.Normal)
return true;
return l.up == r.up && l.down == r.down;
}
public static bool operator != (Rational l, Rational r)
{
return !(l == r);
}
public static bool operator < (Rational l, Rational r)
{
if (l.IsMinusInfinity && !r.IsMinusInfinity
|| r.IsPlusInfinity && !l.IsPlusInfinity)
return true;
if (l.IsPlusInfinity || r.IsMinusInfinity)
return false;
if (l.down == r.down)
return l.up < r.up;
if (l.up <= 0L && r.up > 0L)
return true;
if (l.up < 0L && r.up == 0L)
return true;
try {
return checked(l.up * r.down) < checked(r.up * l.down);
}
catch (ArithmeticException) {
return (decimal) l.up / l.down < (decimal) r.up / r.down;
}
}
public static bool operator <= (Rational l, Rational r)
{
if (l.IsMinusInfinity || r.IsPlusInfinity)
return true;
if (l.IsPlusInfinity || r.IsMinusInfinity)
return false;
if (l.down == r.down)
return l.up <= r.up;
try {
return checked(l.up * r.down) <= checked(r.up * l.down);
}
catch (ArithmeticException) {
return (decimal) l.up / l.down <= (decimal) r.up / r.down;
}
}
public static bool operator >= (Rational l, Rational r)
{
return r <= l;
}
public static bool operator <= (Rational l, long r)
{
switch (l.kind) {
case Kind.PlusInfinity:
return false;
case Kind.MinusInfinity:
return true;
default:
try {
return l.up <= checked(l.down * r);
}
catch (ArithmeticException) {
return (decimal) l.up / l.down <= r;
}
}
}
public static bool operator <= (long l, Rational r)
{
switch (r.kind) {
case Kind.PlusInfinity:
return true;
case Kind.MinusInfinity:
return false;
default:
try {
return r.up >= checked(r.down * l);
}
catch (ArithmeticException) {
return (decimal) r.up / r.down >= l;
}
}
}
public static bool operator >= (long l, Rational r)
{
return r <= l;
}
public static bool operator >= (Rational l, long r)
{
return r <= l;
}
public static bool operator > (Rational l, Rational r)
{
return r < l;
}
public static bool operator < (Rational l, long r)
{
switch (l.kind) {
case Kind.PlusInfinity:
return false;
case Kind.MinusInfinity:
return true;
default:
try {
return l.up < checked(r * l.down);
}
catch {
return (decimal) l.up / l.down < r;
}
}
}
public static bool operator > (Rational l, long r)
{
return r < l;
}
public static bool operator < (long l, Rational r)
{
switch (r.kind) {
case Kind.PlusInfinity:
return true;
case Kind.MinusInfinity:
return false;
default:
try {
return checked(l * r.down) < r.up;
}
catch {
return l < (decimal) r.up / r.down;
}
}
}
public static bool operator > (long l, Rational r)
{
return r < l;
}
public static Rational operator + (Rational l, Rational r)
{
Rational result;
if (TryAdd (l, r, out result))
return result;
throw new ArithmeticException ();
}
public static Rational operator - (Rational l, Rational r)
{
Rational result;
if (TrySubtract (l, r, out result))
return result;
throw new ArithmeticException ();
}
public static Rational operator * (Rational l, Rational r)
{
Rational result;
if (TryMultiply (l, r, out result))
return result;
throw new ArithmeticException ();
}
public static Rational operator / (Rational l, Rational r)
{
Rational result;
if (TryDivide (l, r, out result))
return result;
throw new ArithmeticException ();
}
public static Rational operator - (Rational l, long i)
{
if (l.kind == Kind.Normal && l.down == 1L)
try {
return For (checked(l.up - i));
}
catch (ArithmeticException) {
}
return l - For (i);
}
public static Rational operator + (Rational l, long i)
{
if (l.kind == Kind.Normal && l.down == 1L)
try {
return For (checked(l.up + i));
}
catch (ArithmeticException) {
}
return l + For (i);
}
public static Rational operator - (Rational value)
{
Rational result;
if (TryUnaryMinus (value, out result))
return result;
throw new ArithmeticException ();
}
public static explicit operator double (Rational r)
{
switch (r.kind) {
case Kind.PlusInfinity:
return double.PositiveInfinity;
case Kind.MinusInfinity:
return double.NegativeInfinity;
default:
return (double) r.up / r.down;
}
}
public static explicit operator long (Rational r)
{
if (r.down == 0L)
return r.up >= 0L ? long.MaxValue : long.MinValue;
if (!r.IsInteger)
return (long) System.Math.Round ((double) r.up / r.down);
return r.up;
}
public static explicit operator int (Rational r)
{
if (r.down != 0L)
return (int) System.Math.Round ((double) r.up / r.down);
return r.up >= 0L ? int.MaxValue : int.MinValue;
}
public static implicit operator Rational (long l)
{
return For (l);
}
public static Rational Abs (Rational a)
{
switch (a.kind) {
case Kind.PlusInfinity:
case Kind.MinusInfinity:
return PlusInfinity;
default:
return a.IsZero || a > 0L ? a : -a;
}
}
public static Rational Max (Rational a, Rational b)
{
return a < b ? b : a;
}
public static Rational Min (Rational a, Rational b)
{
return a < b ? a : b;
}
public static bool TryAdd (Rational l, Rational r, out Rational result)
{
if (l.IsZero)
return true.With (r, out result);
if (r.IsZero || l.IsInfinity)
return true.With (l, out result);
if (r.IsInfinity)
return true.With (r, out result);
if (l.IsMaxValue && r > 0L || r.IsMaxValue && l > 0L)
return true.With (PlusInfinity, out result);
long nom;
long denom;
try {
if (l.down == r.down) {
if (l.up == r.up && (r.down & 1L) == 0L) {
nom = l.up;
denom = l.down >> 1;
}
else {
nom = checked (l.up + r.up);
denom = l.down;
}
}
else {
nom = checked (l.up * r.down + r.up * l.down);
denom = checked (l.down * r.down);
}
}
catch (ArithmeticException) {
try {
long gcd = GCD (l.down, r.down);
nom =
checked (
l.up * unchecked (r.down / gcd) +
r.up * unchecked (l.down / gcd));
denom = checked ((l.down / gcd) * r.down);
}
catch (ArithmeticException) {
return false.Without (out result);
}
}
return true.With (denom == 1L ? For (nom) : For (nom, denom), out result);
}
public static bool TrySubtract (Rational l, Rational r, out Rational result)
{
if (r.IsZero)
return true.With (l, out result);
if (l.IsZero)
return true.With (-r, out result);
if (l == r)
return true.With (Zero, out result);
if (r < 0L && !r.IsMinValue)
return TryAdd (l, Abs (r), out result);
if (l.IsInfinity)
return true.With (l, out result);
if (r.IsInfinity)
return true.With (-r, out result);
if (l.IsMinValue && r > 0L)
return true.With (MinusInfinity, out result);
long nom;
long denom;
try {
if (l.down == r.down) {
nom = checked (l.up - r.up);
denom = l.down;
}
else {
nom = checked (l.up * r.down - r.up * l.down);
denom = checked (l.down * r.down);
}
}
catch (ArithmeticException) {
return false.Without (out result);
}
return true.With (For (nom, denom), out result);
}
public static bool TryDivide (Rational l, Rational r, out Rational result)
{
if (r == One)
return true.With (l, out result);
if (r.IsZero)
return false.Without (out result);
if (l.IsZero || r.IsInfinity)
return true.With (Zero, out result);
if (l.IsPlusInfinity)
return true.With (r.Sign > 0 ? PlusInfinity : MinusInfinity, out result);
if (l.IsMinusInfinity)
return true.With (r.Sign > 0 ? MinusInfinity : PlusInfinity, out result);
long nom;
long denom;
if (l.up == r.up) {
// (a/b)/(a/c) = (c/b)
nom = r.down;
denom = l.down;
}
else if (l.down == r.down) {
// (a/c)/(b/c) = (a/b)
nom = l.up;
denom = r.up;
}
else {
// (x/y) / (e/f) == (x/e) * (f/y)
Rational a = For (l.up, r.up);
Rational b = For (r.down, l.down);
try {
return TryMultiply (a, b, out result);
}
catch (ArithmeticException) {
return false.Without (out result);
}
}
return true.With (For (nom, denom), out result);
}
public static bool TryMultiply (Rational l, Rational r, out Rational result)
{
if (l.IsZero || r.IsZero)
return true.With (Zero, out result);
if (l == One)
return true.With (r, out result);
if (r == One)
return true.With (l, out result);
if (l.IsPlusInfinity) {
if (r.IsPlusInfinity)
result = PlusInfinity;
else if (r.IsMinusInfinity)
result = MinusInfinity;
else if (r.IsZero)
result = Zero;
else
result = r.Sign > 0 ? PlusInfinity : MinusInfinity;
return true;
}
if (l.IsMinusInfinity) {
if (r.IsPlusInfinity)
result = MinusInfinity;
else if (r.IsMinusInfinity)
result = PlusInfinity;
else if (r.IsZero)
result = Zero;
else
result = r.Sign > 0 ? MinusInfinity : PlusInfinity;
return true;
}
if (r.IsPlusInfinity) {
if (l.IsZero)
result = Zero;
else
result = l.Sign > 0 ? PlusInfinity : MinusInfinity;
return true;
}
if (r.IsMinusInfinity) {
if (l.IsZero)
result = Zero;
else
result = l.Sign > 0 ? MinusInfinity : PlusInfinity;
return true;
}
long nom;
long denom;
try {
Rational a = For (l.up, r.down);
Rational b = For (r.up, l.down);
nom = checked(a.up * b.up);
denom = checked(a.down * b.down);
}
catch (ArithmeticException) {
return false.Without (out result);
}
return true.With (For (nom, denom), out result);
}
public static bool TryUnaryMinus (Rational value, out Rational result)
{
if (value.IsZero)
return true.With (value, out result);
switch (value.kind) {
case Kind.PlusInfinity:
return true.With (MinusInfinity, out result);
case Kind.MinusInfinity:
return true.With (PlusInfinity, out result);
}
if (value.IsMinValue)
return true.With (MaxValue, out result);
if (value.IsMaxValue)
return true.With (MinValue, out result);
return true.With (For (-value.up, value.down), out result);
}
static int GetSign (Rational r)
{
switch (r.kind) {
case Kind.PlusInfinity:
return 1;
case Kind.MinusInfinity:
return -1;
default:
return System.Math.Sign (r.up) * System.Math.Sign (r.down);
}
}
static long GCD (long a, long b)
{
var aa = (ulong) a;
var bb = (ulong) b;
int pow = 0;
while (((aa | bb) & 1L) == 0L) //while both divide by 2
{
aa >>= 1;
bb >>= 1;
++pow;
}
while ((aa & 1L) == 0L) //get rid of other 2's factors
aa >>= 1;
do {
while ((bb & 1L) == 0L)
bb >>= 1;
ulong cc;
if (aa < bb)
cc = bb - aa;
else {
ulong tmp = aa - bb;
aa = bb;
cc = tmp;
}
bb = cc >> 1;
} while (bb != 0L);
return (long) aa << pow;
}
public override bool Equals (object obj)
{
if (ReferenceEquals (null, obj))
return false;
if (ReferenceEquals (this, obj))
return true;
return obj is Rational && this.Equals ((Rational) obj);
}
public bool Equals (Rational other)
{
return this == other;
}
public override int GetHashCode ()
{
unchecked {
int hashCode = this.kind.GetHashCode ();
hashCode = (hashCode * 397) ^ this.up.GetHashCode ();
hashCode = (hashCode * 1001) ^ this.down.GetHashCode ();
return hashCode;
}
}
public override string ToString ()
{
switch (this.kind) {
case Kind.MinusInfinity:
return "-oo" +
(this.up == -1L || this.down == 0L
? ""
: string.Format ("({0} / {1})", this.up, this.down));
case Kind.PlusInfinity:
return "+oo" +
(this.up == 1L || this.down == 0L
? ""
: string.Format ("({0} / {1})", this.up, this.down));
default:
return this.IsInteger
? this.up.ToString ()
: string.Format ("({0} / {1})", this.up, this.down);
}
}
enum Kind {
Normal,
PlusInfinity,
MinusInfinity,
}
}
static class RationalExtensions {
public static TExpr ToExpression<TVar, TExpr>(this Rational value, IExpressionEncoder<TVar, TExpr> encoder )
{
if (value.IsInteger)
return encoder.ConstantFor ((long) value);
if (value.IsPlusInfinity)
return encoder.CompoundFor (ExpressionType.Int32, ExpressionOperator.Div,
encoder.ConstantFor (1L), encoder.ConstantFor (0L));
if (value.IsMinusInfinity)
return encoder.CompoundFor (ExpressionType.Int32, ExpressionOperator.Div,
encoder.ConstantFor (-1L), encoder.ConstantFor (0L));
TExpr l = encoder.ConstantFor (value.Up);
TExpr r = encoder.ConstantFor (value.Down);
return encoder.CompoundFor (ExpressionType.Int32, ExpressionOperator.Div, l, r);
}
}
}