// Description: Rational number
// Tags: C++11
#ifndef PASTELSYS_RATIONAL_H
#define PASTELSYS_RATIONAL_H
#include "pastel/sys/integer/integer_concept.h"
#include "pastel/sys/type_traits/and.h"
#include "pastel/sys/type_traits/or.h"
#include "pastel/sys/type_traits/not.h"
#include <boost/operators.hpp>
namespace Pastel
{
namespace Rational_
{
template <
typename Integer,
typename That_Integer>
using IsInteger =
And<
Not<std::is_integral<That_Integer>>,
std::is_same<That_Integer, Integer>
>;
template <
typename Integer,
typename That_Integer>
using IsNativeOrInteger =
Or<
std::is_integral<That_Integer>,
IsInteger<Integer, That_Integer>
>;
}
enum class Rounding : integer
{
Truncate,
RoundUp
};
}
namespace Pastel
{
//! A rational number.
template <typename Integer_>
class Rational
: boost::totally_ordered<Rational<Integer_>
, boost::field_operators1<Rational<Integer_>
, boost::unit_steppable<Rational<Integer_>
> > >
{
public:
using Integer = Integer_;
PASTEL_CONCEPT_CHECK(Integer, Integer_Concept);
//! Constructs with the value (0 / 1).
Rational();
//! Copy-constructs with another number.
Rational(const Rational& that);
//! Move-constructs with another number.
Rational(Rational&& that);
//! Constructs with infinity.
template <bool Positive>
Rational(Infinity_<Positive>)
: m_(Positive ? 1 : -1)
, n_(0)
{
}
//! Constructs with Not-a-Number.
Rational(Nan)
: m_(0)
, n_(0)
{
}
// Note in the following the different
// kinds of constructors. The intent is
// to identify literals such as 1, 2.3f,
// 2.3 with Rational numbers. One has to
// be careful to not create ambiguities
// when creating these overloads.
//! Constructs with the value (wholes / 1).
/*!
Implicit conversion allowed.
*/
template <
typename That_Integer,
Requires<Rational_::IsNativeOrInteger<Integer, That_Integer>> = 0>
Rational(That_Integer wholes);
//! Constructs with the value (m / n).
template <
typename M_Integer,
typename N_Integer,
Requires<
Rational_::IsNativeOrInteger<Integer, M_Integer>,
Rational_::IsNativeOrInteger<Integer, N_Integer>
> = 0
>
Rational(
M_Integer m,
N_Integer n);
//! Constructs with a native floating point number.
/*!
If possible, constructs the rational
number m / n subject to
1) |that - (m / n)| <= maxError,
2) |n| <= |nMax|,
3) |n| is minimal.
If not possible, constructs the rational
number m / n subject to
1) |that - (m / n)| is minimal,
2) |n| <= |nMax|
Best rational approximation:
maxError = 0
Simplest rational approximation:
nMax = (Integer)Infinity()
Preconditions:
maxError >= 0
nMax >= 1
nMax < (Integer)Infinity()
maxError (Rational : 0):
Maximum allowed absolute error below which to
stop searching.
nMax (Integer : (Integer)Infinity() - 1):
Maximum allowed divisor.
returns:
On normal numbers, as given above.
On positive overflow, (Rational)Infinity().
On negative overflow, -(Rational)Infinity().
On underflow, 0.
*/
template <
typename Real,
typename... ArgumentSet,
Requires<std::is_floating_point<Real>> = 0>
Rational(
Real that,
ArgumentSet&&... argumentSet);
//! Assigns another rational number.
Rational<Integer>& operator=(Rational that);
//! Swaps two rational numbers.
void swap(Rational& that);
//! Sets the value to (m / n).
void set(
Integer m,
Integer n);
//! Returns the m.
const Integer& m() const;
//! Returns the n.
const Integer& n() const;
//! Returns an approximating real number.
template <
typename Real,
Requires<std::is_floating_point<Real>> = 0>
Real asReal() const;
//! Returns the number as a ratio of integers.
std::string asStringRatio() const;
//! Returns a b-base expansion of the number.
/*!
Preconditions:
base >= 2
digits >= 0
Optional arguments
------------------
base (integer : 10):
The base to use.
showBase (bool : false):
Whether to show the base.
digits (integer : 3):
The digits to show for the fractional part.
shortenExact (bool : true):
Whether to cut off trailing digits in case
of an exact number.
rounding (Rounding : Rouding::RoundUp):
How to handle rounding.
*/
template <typename... ArgumentSet>
std::string asString(
ArgumentSet&&... argumentSet) const;
//! Returns whether the number is infinity.
bool isInfinity() const;
//! Returns whether the number is not-a-number.
bool isNan() const;
//! Returns whether the number is an integer.
bool isInteger() const;
//! Returns whether the number is == 0.
bool isZero() const
{
return zero(*this);
}
//! Returns whether the number is > 0.
bool isPositive() const
{
return positive(*this);
}
//! Returns whether the number is < 0.
bool isNegative() const
{
return negative(*this);
}
//! Adds the given number to this number.
Rational<Integer>& operator+=(Rational that);
//! Subtracts the given number from this number.
Rational<Integer>& operator-=(Rational that);
//! Multiplies this number with the given number.
Rational<Integer>& operator*=(Rational that);
//! Divides this number with the given number.
Rational<Integer>& operator/=(Rational that);
//! Adds 1 to this number.
Rational<Integer>& operator++()
{
*this += 1;
return *this;
}
//! Subtracts 1 from this number.
Rational<Integer>& operator--()
{
*this -= 1;
return *this;
}
//! Returns the negation of this number.
Rational<Integer> operator-() const;
//! Returns this number.
Rational<Integer> operator+() const;
// The following operators must be defined as global friends.
// Note for boost::operators to work the operators must
// be in this class scope, thus you can't turn them
// into template functions.
// If you make these operators member functions,
// the combination of implicit conversions from integer
// and Integer and the boost::operators does not work.
// I suspect it is because the member function
// operator== and operator< hide the same-named
// functions in the base class.
//! Returns if the given number equals this number.
friend bool operator==(
const Rational& left,
const Rational& right)
{
return left.equal(right);
}
//! Returns if this number is less than the given number.
friend bool operator<(
const Rational& left,
const Rational& right)
{
return left.lessThan(right);
}
//! Invert the number.
Rational<Integer>& invert()
{
using std::swap;
swap(m_, n_);
if (negative(n_))
{
// As an invariant, the divisor
// is always positive. Make it so.
m_ = -m_;
n_ = -n_;
}
return *this;
}
//! Negate the number.
Rational<Integer>& negate()
{
m_ = -m_;
return *this;
}
private:
enum class NumberType : integer
{
Normal,
Nan,
Infinity,
MinusInfinity,
Zero
};
class SkipSimplify {};
//! Constructs with the value (m / n).
Rational(Integer m,
Integer n,
SkipSimplify);
//! Sets the value to (m / n).
void set(
Integer m,
Integer n,
SkipSimplify);
//! Brings the rational number to a normal form.
/*!
If the number is not NaN (0, 0), then the normal
form is where gcd(m, n) = 1
and m >= 0. The normal form for NaN is
NaN itself.
*/
void simplify();
NumberType classify() const;
bool lessThan(const Rational& that) const;
bool equal(const Rational& that) const;
Integer m_;
Integer n_;
};
}
#include "pastel/sys/rational/rational.hpp"
#include "pastel/sys/rational/rational_add.hpp"
#include "pastel/sys/rational/rational_as_real.hpp"
#include "pastel/sys/rational/rational_as_string.hpp"
#include "pastel/sys/rational/rational_classify.hpp"
#include "pastel/sys/rational/rational_compare.hpp"
#include "pastel/sys/rational/rational_construct.hpp"
#include "pastel/sys/rational/rational_multiply.hpp"
#include "pastel/sys/rational/rational_real.hpp"
#include "pastel/sys/rational/rational_simplify.hpp"
#include "pastel/sys/rational/rational_stream.h"
#endif