mirror of
https://github.com/Gnucash/gnucash.git
synced 2026-09-03 20:53:02 -05:00
Make the GncRational and GncNumeric APIs mostly identical.
Leaving string conversion and stream operators out of GncRational.
This commit is contained in:
@@ -64,17 +64,18 @@ powten (unsigned int exp)
|
||||
|
||||
GncNumeric::GncNumeric(GncRational rr)
|
||||
{
|
||||
if (rr.m_num.isNan() || rr.m_den.isNan())
|
||||
/* Can't use isValid here because we want to throw different exceptions. */
|
||||
if (rr.num().isNan() || rr.denom().isNan())
|
||||
throw std::underflow_error("Operation resulted in NaN.");
|
||||
if (rr.m_num.isOverflow() || rr.m_den.isOverflow())
|
||||
if (rr.num().isOverflow() || rr.denom().isOverflow())
|
||||
throw std::overflow_error("Operation overflowed a 128-bit int.");
|
||||
if (rr.m_num.isBig() || rr.m_den.isBig())
|
||||
if (rr.num().isBig() || rr.denom().isBig())
|
||||
{
|
||||
GncRational reduced(rr.reduce());
|
||||
rr = reduced.round_to_numeric(); // A no-op if it's already small.
|
||||
}
|
||||
m_num = static_cast<int64_t>(rr.m_num);
|
||||
m_den = static_cast<int64_t>(rr.m_den);
|
||||
m_num = static_cast<int64_t>(rr.num());
|
||||
m_den = static_cast<int64_t>(rr.denom());
|
||||
}
|
||||
|
||||
GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
|
||||
@@ -261,18 +262,18 @@ GncNumeric::prepare_conversion(int64_t new_denom) const
|
||||
GncRational conversion(new_denom, m_den);
|
||||
auto red_conv = conversion.reduce();
|
||||
GncInt128 old_num(m_num);
|
||||
auto new_num = old_num * red_conv.m_num;
|
||||
auto rem = new_num % red_conv.m_den;
|
||||
new_num /= red_conv.m_den;
|
||||
auto new_num = old_num * red_conv.num();
|
||||
auto rem = new_num % red_conv.denom();
|
||||
new_num /= red_conv.denom();
|
||||
if (new_num.isBig())
|
||||
{
|
||||
GncRational rr(new_num, new_denom);
|
||||
GncNumeric nn(rr);
|
||||
rr = rr.convert<RoundType::truncate>(new_denom);
|
||||
return {static_cast<int64_t>(rr.m_num), new_denom, 0};
|
||||
return {static_cast<int64_t>(rr.num()), new_denom, 0};
|
||||
}
|
||||
return {static_cast<int64_t>(new_num), static_cast<int64_t>(red_conv.m_den),
|
||||
static_cast<int64_t>(rem)};
|
||||
return {static_cast<int64_t>(new_num),
|
||||
static_cast<int64_t>(red_conv.denom()), static_cast<int64_t>(rem)};
|
||||
}
|
||||
|
||||
int64_t
|
||||
@@ -331,25 +332,26 @@ GncNumeric::to_decimal(unsigned int max_places) const
|
||||
rr = rr.convert<RoundType::never>(powten(max_places)); //May throw
|
||||
/* rr might have gotten reduced a bit too much; if so, put it back: */
|
||||
unsigned int pwr{1};
|
||||
for (; pwr <= max_places && !(rr.m_den % powten(pwr)); ++pwr);
|
||||
for (; pwr <= max_places && !(rr.denom() % powten(pwr)); ++pwr);
|
||||
auto reduce_to = powten(pwr);
|
||||
if (rr.m_den % reduce_to)
|
||||
GncInt128 rr_num(rr.num()), rr_den(rr.denom());
|
||||
if (rr_den % reduce_to)
|
||||
{
|
||||
auto factor(reduce_to / rr.m_den);
|
||||
rr.m_num *= factor;
|
||||
rr.m_den *= factor;
|
||||
auto factor(reduce_to / rr.denom());
|
||||
rr_num *= factor;
|
||||
rr_den *= factor;
|
||||
}
|
||||
while (rr.m_num % 10 == 0)
|
||||
while (rr_num % 10 == 0)
|
||||
{
|
||||
rr.m_num /= 10;
|
||||
rr.m_den /= 10;
|
||||
rr_num /= 10;
|
||||
rr_den /= 10;
|
||||
}
|
||||
try
|
||||
{
|
||||
/* Construct from the parts to avoid the GncRational constructor's
|
||||
* automatic rounding.
|
||||
*/
|
||||
return {static_cast<int64_t>(rr.m_num), static_cast<int64_t>(rr.m_den)};
|
||||
return {static_cast<int64_t>(rr_num), static_cast<int64_t>(rr_den)};
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
@@ -399,12 +401,8 @@ GncNumeric::cmp(GncNumeric b)
|
||||
auto b_num = b.num();
|
||||
return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
|
||||
}
|
||||
// GncInt128 a_den(m_den), b_den(b.denom());
|
||||
// auto lcm = a_den.gcd(b_den);
|
||||
// GncInt128 a_num(m_num * gcd / a_den), b_num(b.num() * gcd / b_den);
|
||||
// return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
|
||||
GncRational an(*this), bn(b);
|
||||
return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
|
||||
return an.cmp(bn);
|
||||
}
|
||||
|
||||
GncNumeric
|
||||
@@ -454,49 +452,6 @@ operator/(GncNumeric a, GncNumeric b)
|
||||
return static_cast<GncNumeric>(rr);
|
||||
}
|
||||
|
||||
int
|
||||
cmp(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b);
|
||||
}
|
||||
|
||||
bool
|
||||
operator<(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b) < 0;
|
||||
}
|
||||
|
||||
bool
|
||||
operator>(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b) > 0;
|
||||
}
|
||||
|
||||
bool
|
||||
operator==(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b) == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
operator<=(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b) <= 0;
|
||||
}
|
||||
|
||||
bool
|
||||
operator>=(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b) >= 0;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(GncNumeric a, GncNumeric b)
|
||||
{
|
||||
return a.cmp(b) != 0;
|
||||
}
|
||||
|
||||
|
||||
template <typename T, typename I> T
|
||||
convert(T num, I new_denom, int how)
|
||||
{
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
class GncRational;
|
||||
|
||||
/**
|
||||
* The primary numeric class for representing amounts and values.
|
||||
/**@ingroup QOF
|
||||
* @brief The primary numeric class for representing amounts and values.
|
||||
*
|
||||
* Calculations are generally performed in 128-bit (by converting to
|
||||
* GncRational) and reducing the result. If the result would overflow a 64-bit
|
||||
@@ -255,6 +255,7 @@ public:
|
||||
* @return -1 if this < b, 0 if ==, 1 if this > b.
|
||||
*/
|
||||
int cmp(GncNumeric b);
|
||||
int cmp(int64_t b) { return cmp(GncNumeric(b, 1)); }
|
||||
private:
|
||||
struct round_param
|
||||
{
|
||||
@@ -274,7 +275,7 @@ private:
|
||||
|
||||
/**
|
||||
* \defgroup gnc_numeric_arithmetic_operators
|
||||
*
|
||||
* @{
|
||||
* Normal arithmetic operators. The class arithmetic operators are implemented
|
||||
* in terms of these operators. They use GncRational operators internally then
|
||||
* call the GncNumeric(GncRational&) constructor which will silently round
|
||||
@@ -285,12 +286,45 @@ private:
|
||||
*
|
||||
* \param a The right-side operand
|
||||
* \param b The left-side operand
|
||||
* \return A new GncNumeric computed from the sum.
|
||||
* \return A GncNumeric computed from the operation.
|
||||
*/
|
||||
GncNumeric operator+(GncNumeric a, GncNumeric b);
|
||||
inline GncNumeric operator+(GncNumeric a, int64_t b)
|
||||
{
|
||||
return a + GncNumeric(b, 1);
|
||||
}
|
||||
inline GncNumeric operator+(int64_t a, GncNumeric b)
|
||||
{
|
||||
return b + GncNumeric(a, 1);
|
||||
}
|
||||
GncNumeric operator-(GncNumeric a, GncNumeric b);
|
||||
inline GncNumeric operator-(GncNumeric a, int64_t b)
|
||||
{
|
||||
return a - GncNumeric(b, 1);
|
||||
}
|
||||
inline GncNumeric operator-(int64_t a, GncNumeric b)
|
||||
{
|
||||
return b - GncNumeric(a, 1);
|
||||
}
|
||||
GncNumeric operator*(GncNumeric a, GncNumeric b);
|
||||
inline GncNumeric operator*(GncNumeric a, int64_t b)
|
||||
{
|
||||
return a * GncNumeric(b, 1);
|
||||
}
|
||||
inline GncNumeric operator*(int64_t a, GncNumeric b)
|
||||
{
|
||||
return b * GncNumeric(a, 1);
|
||||
}
|
||||
GncNumeric operator/(GncNumeric a, GncNumeric b);
|
||||
inline GncNumeric operator/(GncNumeric a, int64_t b)
|
||||
{
|
||||
return a / GncNumeric(b, 1);
|
||||
}
|
||||
inline GncNumeric operator/(int64_t a, GncNumeric b)
|
||||
{
|
||||
return b / GncNumeric(a, 1);
|
||||
}
|
||||
/** @} */
|
||||
/**
|
||||
* std::stream output operator. Uses standard integer operator<< so should obey
|
||||
* locale rules. Numbers are presented as integers if the denominator is 1, as a
|
||||
@@ -346,18 +380,33 @@ std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>&
|
||||
/**
|
||||
* @return -1 if a < b, 0 if a == b, 1 if a > b.
|
||||
*/
|
||||
int cmp(GncNumeric a, GncNumeric b);
|
||||
inline int cmp(GncNumeric a, GncNumeric b) { return a.cmp(b); }
|
||||
inline int cmp(GncNumeric a, int64_t b) { return a.cmp(b); }
|
||||
inline int cmp(int64_t a, GncNumeric b) { return GncNumeric(a, 1).cmp(b); }
|
||||
|
||||
/**
|
||||
* \defgroup gnc_numeric_comparison_operators
|
||||
* @{
|
||||
* Standard comparison operators, which do what one would expect.
|
||||
*/
|
||||
bool operator<(GncNumeric a, GncNumeric b);
|
||||
bool operator>(GncNumeric a, GncNumeric b);
|
||||
bool operator==(GncNumeric a, GncNumeric b);
|
||||
bool operator<=(GncNumeric a, GncNumeric b);
|
||||
bool operator>=(GncNumeric a, GncNumeric b);
|
||||
bool operator!=(GncNumeric a, GncNumeric b);
|
||||
inline bool operator<(GncNumeric a, GncNumeric b) { return cmp(a, b) < 0; }
|
||||
inline bool operator<(GncNumeric a, int64_t b) { return cmp(a, b) < 0; }
|
||||
inline bool operator<(int64_t a, GncNumeric b) { return cmp(a, b) < 0; }
|
||||
inline bool operator>(GncNumeric a, GncNumeric b) { return cmp(a, b) > 0; }
|
||||
inline bool operator>(GncNumeric a, int64_t b) { return cmp(a, b) > 0; }
|
||||
inline bool operator>(int64_t a, GncNumeric b) { return cmp(a, b) > 0; }
|
||||
inline bool operator==(GncNumeric a, GncNumeric b) { return cmp(a, b) == 0; }
|
||||
inline bool operator==(GncNumeric a, int64_t b) { return cmp(a, b) == 0; }
|
||||
inline bool operator==(int64_t a, GncNumeric b) { return cmp(a, b) == 0; }
|
||||
inline bool operator<=(GncNumeric a, GncNumeric b) { return cmp(a, b) <= 0; }
|
||||
inline bool operator<=(GncNumeric a, int64_t b) { return cmp(a, b) <= 0; }
|
||||
inline bool operator<=(int64_t a, GncNumeric b) { return cmp(a, b) <= 0; }
|
||||
inline bool operator>=(GncNumeric a, GncNumeric b) { return cmp(a, b) >= 0; }
|
||||
inline bool operator>=(GncNumeric a, int64_t b) { return cmp(a, b) >= 0; }
|
||||
inline bool operator>=(int64_t a, GncNumeric b) { return cmp(a, b) >= 0; }
|
||||
inline bool operator!=(GncNumeric a, GncNumeric b) { return cmp(a, b) != 0; }
|
||||
inline bool operator!=(GncNumeric a, int64_t b) { return cmp(a, b) != 0; }
|
||||
inline bool operator!=(int64_t a, GncNumeric b) { return cmp(a, b) != 0; }
|
||||
/** @} */
|
||||
/**
|
||||
* Convenience function to quickly return 10**digits.
|
||||
|
||||
@@ -78,91 +78,27 @@ GncRational::operator gnc_numeric () const noexcept
|
||||
GncRational
|
||||
GncRational::operator-() const noexcept
|
||||
{
|
||||
GncRational b(*this);
|
||||
b.m_num = - b.m_num;
|
||||
return b;
|
||||
return GncRational(-m_num, m_den);
|
||||
}
|
||||
|
||||
GncRational&
|
||||
GncRational::inv () noexcept
|
||||
GncRational
|
||||
GncRational::inv () const noexcept
|
||||
{
|
||||
if (m_den < 0)
|
||||
{
|
||||
m_num *= -m_den;
|
||||
m_den = 1;
|
||||
}
|
||||
std::swap(m_num, m_den);
|
||||
if (m_num == 0)
|
||||
return *this;
|
||||
if (m_num < 0)
|
||||
return GncRational(-m_den, -m_num);
|
||||
return GncRational(m_den, m_num);
|
||||
}
|
||||
|
||||
reduce();
|
||||
GncRational
|
||||
GncRational::abs() const noexcept
|
||||
{
|
||||
if (m_num < 0)
|
||||
return -*this;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator+(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator+ called with out-of-range operand.");
|
||||
GncInt128 lcm = a.m_den.lcm(b.m_den);
|
||||
GncInt128 num(a.m_num * lcm / a.m_den + b.m_num * lcm / b.m_den);
|
||||
if (!(lcm.valid() && num.valid()))
|
||||
throw std::overflow_error("Operator+ overflowed.");
|
||||
GncRational retval(num, lcm);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator-(GncRational a, GncRational b)
|
||||
{
|
||||
GncRational retval = a + (-b);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator*(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator* called with out-of-range operand.");
|
||||
GncInt128 num (a.m_num * b.m_num), den(a.m_den * b.m_den);
|
||||
if (!(num.valid() && den.valid()))
|
||||
throw std::overflow_error("Operator* overflowed.");
|
||||
GncRational retval(num, den);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator/(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator/ called with out-of-range operand.");
|
||||
if (b.m_num == 0)
|
||||
throw std::underflow_error("Divide by 0.");
|
||||
if (b.m_num.isNeg())
|
||||
{
|
||||
a.m_num = -a.m_num;
|
||||
b.m_num = -b.m_num;
|
||||
}
|
||||
|
||||
/* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
|
||||
* and it's just a_num/b_num.
|
||||
*/
|
||||
if (a.m_den == b.m_den)
|
||||
return GncRational(a.m_num, b.m_num);
|
||||
|
||||
/* Protect against possibly preventable overflow: */
|
||||
if (a.m_num.isBig() || a.m_den.isBig() ||
|
||||
b.m_num.isBig() || b.m_den.isBig())
|
||||
{
|
||||
GncInt128 gcd = b.m_den.gcd(a.m_den);
|
||||
b.m_den /= gcd;
|
||||
a.m_den /= gcd;
|
||||
}
|
||||
|
||||
GncInt128 num(a.m_num * b.m_den), den(a.m_den * b.m_num);
|
||||
if (!(num.valid() && den.valid()))
|
||||
throw std::overflow_error("Operator/ overflowed.");
|
||||
return GncRational(num, den);
|
||||
}
|
||||
|
||||
void
|
||||
GncRational::operator+=(GncRational b)
|
||||
{
|
||||
@@ -191,6 +127,19 @@ GncRational::operator/=(GncRational b)
|
||||
*this = std::move(new_val);
|
||||
}
|
||||
|
||||
int
|
||||
GncRational::cmp(GncRational b)
|
||||
{
|
||||
if (m_den == b.denom())
|
||||
{
|
||||
auto b_num = b.num();
|
||||
return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
|
||||
}
|
||||
auto gcd = m_den.gcd(b.denom());
|
||||
GncInt128 a_num(m_num * b.denom() / gcd), b_num(b.num() * m_den / gcd);
|
||||
return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
|
||||
}
|
||||
|
||||
GncRational::round_param
|
||||
GncRational::prepare_conversion (GncInt128 new_denom) const
|
||||
{
|
||||
@@ -199,10 +148,10 @@ GncRational::prepare_conversion (GncInt128 new_denom) const
|
||||
GncRational conversion(new_denom, m_den);
|
||||
auto red_conv = conversion.reduce();
|
||||
GncInt128 old_num(m_num);
|
||||
auto new_num = old_num * red_conv.m_num;
|
||||
auto rem = new_num % red_conv.m_den;
|
||||
new_num /= red_conv.m_den;
|
||||
return {new_num, red_conv.m_den, rem};
|
||||
auto new_num = old_num * red_conv.num();
|
||||
auto rem = new_num % red_conv.denom();
|
||||
new_num /= red_conv.denom();
|
||||
return {new_num, red_conv.denom(), rem};
|
||||
}
|
||||
|
||||
GncInt128
|
||||
@@ -267,3 +216,70 @@ GncRational::round_to_numeric() const
|
||||
new_v = new_v.convert<RoundType::half_down>(m_den / divisor);
|
||||
return new_v;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator+(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator+ called with out-of-range operand.");
|
||||
GncInt128 lcm = a.denom().lcm(b.denom());
|
||||
GncInt128 num(a.num() * lcm / a.denom() + b.num() * lcm / b.denom());
|
||||
if (!(lcm.valid() && num.valid()))
|
||||
throw std::overflow_error("Operator+ overflowed.");
|
||||
GncRational retval(num, lcm);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator-(GncRational a, GncRational b)
|
||||
{
|
||||
GncRational retval = a + (-b);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator*(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator* called with out-of-range operand.");
|
||||
GncInt128 num (a.num() * b.num()), den(a.denom() * b.denom());
|
||||
if (!(num.valid() && den.valid()))
|
||||
throw std::overflow_error("Operator* overflowed.");
|
||||
GncRational retval(num, den);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GncRational
|
||||
operator/(GncRational a, GncRational b)
|
||||
{
|
||||
if (!(a.valid() && b.valid()))
|
||||
throw std::range_error("Operator/ called with out-of-range operand.");
|
||||
auto a_num = a.num(), b_num = b.num(), a_den = a.denom(), b_den = b.denom();
|
||||
if (b_num == 0)
|
||||
throw std::underflow_error("Divide by 0.");
|
||||
if (b_num.isNeg())
|
||||
{
|
||||
a_num = -a_num;
|
||||
b_num = -b_num;
|
||||
}
|
||||
|
||||
/* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
|
||||
* and it's just a_num/b_num.
|
||||
*/
|
||||
if (a_den == b_den)
|
||||
return GncRational(a_num, b_num);
|
||||
|
||||
/* Protect against possibly preventable overflow: */
|
||||
if (a_num.isBig() || a_den.isBig() ||
|
||||
b_num.isBig() || b_den.isBig())
|
||||
{
|
||||
GncInt128 gcd = b_den.gcd(a_den);
|
||||
b_den /= gcd;
|
||||
a_den /= gcd;
|
||||
}
|
||||
|
||||
GncInt128 num(a_num * b_den), den(a_den * b_num);
|
||||
if (!(num.valid() && den.valid()))
|
||||
throw std::overflow_error("Operator/ overflowed.");
|
||||
return GncRational(num, den);
|
||||
}
|
||||
|
||||
@@ -34,16 +34,43 @@ enum class DenomType;
|
||||
/** @ingroup QOF
|
||||
* @brief Rational number class using GncInt128 for the numerator
|
||||
* and denominator.
|
||||
*
|
||||
* This class provides far greater overflow protection compared to GncNumeric at
|
||||
* the expense of doubling the size, so GncNumeric is preferred for storage into
|
||||
* objects. Furthermore the backends are not able to store GncRational numbers;
|
||||
* storage in SQL would require using BLOBs which would preclude calculations in
|
||||
* queries. GncRational exists *primarily* as a more overflow-resistant
|
||||
* calculation facility for GncNumeric. It's available for cases where one needs
|
||||
* an error instead of an automatically rounded value for a calculation that
|
||||
* produces a result that won't fit into an int64 without rounding.
|
||||
|
||||
* Errors: Errors are signalled by exceptions as follows:
|
||||
* * A zero denominator will raise a std::invalid_argument.
|
||||
* * Division by zero will raise a std::underflow_error.
|
||||
* * Overflowing 128 bits will raise a std::overflow_error.
|
||||
* * Failure to convert a number as specified by the arguments to convert() will
|
||||
* raise a std::domain_error.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
class GncRational
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Default constructor provides the zero value.
|
||||
*/
|
||||
GncRational() : m_num(0), m_den(1) {}
|
||||
GncRational (gnc_numeric n) noexcept;
|
||||
GncRational(GncNumeric n) noexcept;
|
||||
/**
|
||||
* GncInt128 constructor. This will take any flavor of built-in integer
|
||||
* thanks to implicit construction of the GncInt128s.
|
||||
*/
|
||||
GncRational (GncInt128 num, GncInt128 den) noexcept
|
||||
: m_num(num), m_den(den) {}
|
||||
/** Convenience constructor from the C API's gnc_numeric. */
|
||||
GncRational (gnc_numeric n) noexcept;
|
||||
/** GncNumeric constructor. */
|
||||
GncRational(GncNumeric n) noexcept;
|
||||
GncRational(const GncRational& rhs) = default;
|
||||
GncRational(GncRational&& rhs) = default;
|
||||
GncRational& operator=(const GncRational& rhs) = default;
|
||||
@@ -122,6 +149,10 @@ public:
|
||||
params.rem, RT2T<RT>()), new_denom);
|
||||
}
|
||||
|
||||
/** Numerator accessor */
|
||||
GncInt128 num() { return m_num; }
|
||||
/** Denominator accessor */
|
||||
GncInt128 denom() { return m_den; }
|
||||
/** @defgroup gnc_rational_mutators
|
||||
* @{
|
||||
* Standard mutating arithmetic operators.
|
||||
@@ -132,10 +163,17 @@ public:
|
||||
void operator/=(GncRational b);
|
||||
/** @} */
|
||||
/** Inverts the number, equivalent of /= {1, 1} */
|
||||
GncRational& inv() noexcept;
|
||||
GncRational inv() const noexcept;
|
||||
/** Absolute value; return value is always >= 0 and of same magnitude. */
|
||||
GncRational abs() const noexcept;
|
||||
/** Compare function
|
||||
*
|
||||
* @param b GncNumeric or integer value to compare to.
|
||||
* @return -1 if < b, 0 if equal, 1 if > b.
|
||||
*/
|
||||
int cmp(GncRational b);
|
||||
int cmp(GncInt128 b) { return cmp(GncRational(b, 1)); }
|
||||
|
||||
GncInt128 m_num;
|
||||
GncInt128 m_den;
|
||||
private:
|
||||
struct round_param
|
||||
{
|
||||
@@ -152,11 +190,90 @@ private:
|
||||
* finish computing a GncNumeric with the new denominator.
|
||||
*/
|
||||
round_param prepare_conversion(GncInt128 new_denom) const;
|
||||
GncInt128 m_num;
|
||||
GncInt128 m_den;
|
||||
};
|
||||
|
||||
GncRational operator+(GncRational a, GncRational b);
|
||||
GncRational operator-(GncRational a, GncRational b);
|
||||
GncRational operator*(GncRational a, GncRational b);
|
||||
GncRational operator/(GncRational a, GncRational b);
|
||||
/**
|
||||
* @return -1 if a < b, 0 if a == b, 1 if a > b.
|
||||
*/
|
||||
inline int cmp(GncRational a, GncRational b) { return a.cmp(b); }
|
||||
inline int cmp(GncRational a, GncInt128 b) { return a.cmp(b); }
|
||||
inline int cmp(GncInt128 a, GncRational b) { return GncRational(a, 1).cmp(b); }
|
||||
|
||||
/**
|
||||
* \defgroup gnc_rational_comparison_operators
|
||||
* @{
|
||||
* Standard comparison operators, which do what one would expect.
|
||||
*/
|
||||
inline bool operator<(GncRational a, GncRational b) { return cmp(a, b) < 0; }
|
||||
inline bool operator<(GncRational a, GncInt128 b) { return cmp(a, b) < 0; }
|
||||
inline bool operator<(GncInt128 a, GncRational b) { return cmp(a, b) < 0; }
|
||||
inline bool operator>(GncRational a, GncRational b) { return cmp(a, b) > 0; }
|
||||
inline bool operator>(GncRational a, GncInt128 b) { return cmp(a, b) > 0; }
|
||||
inline bool operator>(GncInt128 a, GncRational b) { return cmp(a, b) > 0; }
|
||||
inline bool operator==(GncRational a, GncRational b) { return cmp(a, b) == 0; }
|
||||
inline bool operator==(GncRational a, GncInt128 b) { return cmp(a, b) == 0; }
|
||||
inline bool operator==(GncInt128 a, GncRational b) { return cmp(a, b) == 0; }
|
||||
inline bool operator<=(GncRational a, GncRational b) { return cmp(a, b) <= 0; }
|
||||
inline bool operator<=(GncRational a, GncInt128 b) { return cmp(a, b) <= 0; }
|
||||
inline bool operator<=(GncInt128 a, GncRational b) { return cmp(a, b) <= 0; }
|
||||
inline bool operator>=(GncRational a, GncRational b) { return cmp(a, b) >= 0; }
|
||||
inline bool operator>=(GncRational a, GncInt128 b) { return cmp(a, b) >= 0; }
|
||||
inline bool operator>=(GncInt128 a, GncRational b) { return cmp(a, b) >= 0; }
|
||||
inline bool operator!=(GncRational a, GncRational b) { return cmp(a, b) != 0; }
|
||||
inline bool operator!=(GncRational a, GncInt128 b) { return cmp(a, b) != 0; }
|
||||
inline bool operator!=(GncInt128 a, GncRational b) { return cmp(a, b) != 0; }
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* \defgroup gnc_rational_arithmetic_operators
|
||||
*
|
||||
* Normal arithmetic operators. The class arithmetic operators are implemented
|
||||
* in terms of these operators.
|
||||
*
|
||||
* These operators can throw std::overflow_error, std::underflow_error, or
|
||||
* std::invalid argument as indicated in the class documentation.
|
||||
*
|
||||
* \param a The right-side operand
|
||||
* \param b The left-side operand
|
||||
* \return A GncRational computed from the operation.
|
||||
*/
|
||||
GncRational operator+(GncRational a, GncRational b);
|
||||
inline GncRational operator+(GncRational a, GncInt128 b)
|
||||
{
|
||||
return a + GncRational(b, 1);
|
||||
}
|
||||
inline GncRational operator+(GncInt128 a, GncRational b)
|
||||
{
|
||||
return b + GncRational(a, 1);
|
||||
}
|
||||
GncRational operator-(GncRational a, GncRational b);
|
||||
inline GncRational operator-(GncRational a, GncInt128 b)
|
||||
{
|
||||
return a - GncRational(b, 1);
|
||||
}
|
||||
inline GncRational operator-(GncInt128 a, GncRational b)
|
||||
{
|
||||
return b - GncRational(a, 1);
|
||||
}
|
||||
GncRational operator*(GncRational a, GncRational b);
|
||||
inline GncRational operator*(GncRational a, GncInt128 b)
|
||||
{
|
||||
return a * GncRational(b, 1);
|
||||
}
|
||||
inline GncRational operator*(GncInt128 a, GncRational b)
|
||||
{
|
||||
return b * GncRational(a, 1);
|
||||
}
|
||||
GncRational operator/(GncRational a, GncRational b);
|
||||
inline GncRational operator/(GncRational a, GncInt128 b)
|
||||
{
|
||||
return a / GncRational(b, 1);
|
||||
}
|
||||
inline GncRational operator/(GncInt128 a, GncRational b)
|
||||
{
|
||||
return b / GncRational(a, 1);
|
||||
}
|
||||
/** @} */
|
||||
#endif //__GNC_RATIONAL_HPP__
|
||||
|
||||
@@ -30,8 +30,8 @@ TEST(gncrational_constructors, test_default_constructor)
|
||||
{
|
||||
EXPECT_NO_THROW({
|
||||
GncRational value;
|
||||
EXPECT_EQ(value.m_num, 0);
|
||||
EXPECT_EQ(value.m_den, 1);
|
||||
EXPECT_EQ(value.num(), 0);
|
||||
EXPECT_EQ(value.denom(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ TEST(gncrational_constructors, test_gnc_numeric_constructor)
|
||||
gnc_numeric input = gnc_numeric_create(123, 456);
|
||||
EXPECT_NO_THROW({
|
||||
GncRational value(input);
|
||||
EXPECT_EQ(input.num, value.m_num);
|
||||
EXPECT_EQ(input.denom, value.m_den);
|
||||
EXPECT_EQ(input.num, value.num());
|
||||
EXPECT_EQ(input.denom, value.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ TEST(gncrational_constructors, test_gnc_int128_constructor)
|
||||
GncInt128 num(123), denom(456);
|
||||
EXPECT_NO_THROW({
|
||||
GncRational value(num, denom);
|
||||
EXPECT_EQ(123, value.m_num);
|
||||
EXPECT_EQ(456, value.m_den);
|
||||
EXPECT_EQ(123, value.num());
|
||||
EXPECT_EQ(456, value.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ TEST(gncrational_constructors, test_implicit_int_constructor)
|
||||
int num(123), denom(456);
|
||||
EXPECT_NO_THROW({
|
||||
GncRational value(num, denom);
|
||||
EXPECT_EQ(123, value.m_num);
|
||||
EXPECT_EQ(456, value.m_den);
|
||||
EXPECT_EQ(123, value.num());
|
||||
EXPECT_EQ(456, value.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -71,11 +71,11 @@ TEST(gncrational_operators, test_addition)
|
||||
GncRational a(123456789987654321, 1000000000);
|
||||
GncRational b(65432198765432198, 100000000);
|
||||
GncRational c = a + b;
|
||||
EXPECT_EQ (777778777641976301, c.m_num);
|
||||
EXPECT_EQ (1000000000, c.m_den);
|
||||
EXPECT_EQ (777778777641976301, c.num());
|
||||
EXPECT_EQ (1000000000, c.denom());
|
||||
a += b;
|
||||
EXPECT_EQ (777778777641976301, a.m_num);
|
||||
EXPECT_EQ (1000000000, a.m_den);
|
||||
EXPECT_EQ (777778777641976301, a.num());
|
||||
EXPECT_EQ (1000000000, a.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,17 +85,17 @@ TEST(gncrational_operators, test_subtraction)
|
||||
GncRational a(123456789987654321, 1000000000);
|
||||
GncRational b(65432198765432198, 100000000);
|
||||
GncRational c = a - b;
|
||||
EXPECT_EQ (-530865197666667659, c.m_num);
|
||||
EXPECT_TRUE(c.m_num.isNeg());
|
||||
EXPECT_EQ (1000000000, c.m_den);
|
||||
EXPECT_EQ (-530865197666667659, c.num());
|
||||
EXPECT_TRUE(c.num().isNeg());
|
||||
EXPECT_EQ (1000000000, c.denom());
|
||||
c = b - a;
|
||||
EXPECT_EQ (530865197666667659, c.m_num);
|
||||
EXPECT_FALSE(c.m_num.isNeg());
|
||||
EXPECT_EQ (1000000000, c.m_den);
|
||||
EXPECT_EQ (530865197666667659, c.num());
|
||||
EXPECT_FALSE(c.num().isNeg());
|
||||
EXPECT_EQ (1000000000, c.denom());
|
||||
a -= b;
|
||||
EXPECT_EQ (-530865197666667659, a.m_num);
|
||||
EXPECT_TRUE(a.m_num.isNeg());
|
||||
EXPECT_EQ (1000000000, a.m_den);
|
||||
EXPECT_EQ (-530865197666667659, a.num());
|
||||
EXPECT_TRUE(a.num().isNeg());
|
||||
EXPECT_EQ (1000000000, a.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,12 +106,12 @@ TEST(gncrational_operators, test_multiplication)
|
||||
GncRational b(65432198765432198, 100000000);
|
||||
GncRational c = a * b;
|
||||
EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
|
||||
UINT64_C(8081008345983448486)), c.m_num);
|
||||
EXPECT_EQ (100000000000000000, c.m_den);
|
||||
UINT64_C(8081008345983448486)), c.num());
|
||||
EXPECT_EQ (100000000000000000, c.denom());
|
||||
a *= b;
|
||||
EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
|
||||
UINT64_C(8081008345983448486)), a.m_num);
|
||||
EXPECT_EQ (100000000000000000, a.m_den);
|
||||
UINT64_C(8081008345983448486)), a.num());
|
||||
EXPECT_EQ (100000000000000000, a.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,14 +122,14 @@ TEST(gncrational_operators, test_division)
|
||||
GncRational b(65432198765432198, 100000000);
|
||||
GncRational c = a / b;
|
||||
EXPECT_EQ (GncInt128(UINT64_C(669260),
|
||||
UINT64_C(11059994577585475840)), c.m_num);
|
||||
UINT64_C(11059994577585475840)), c.num());
|
||||
EXPECT_EQ (GncInt128(UINT64_C(3547086),
|
||||
UINT64_C(11115994079396609024)), c.m_den);
|
||||
UINT64_C(11115994079396609024)), c.denom());
|
||||
a /= b;
|
||||
EXPECT_EQ (GncInt128(UINT64_C(669260),
|
||||
UINT64_C(11059994577585475840)), a.m_num);
|
||||
UINT64_C(11059994577585475840)), a.num());
|
||||
EXPECT_EQ (GncInt128(UINT64_C(3547086),
|
||||
UINT64_C(11115994079396609024)), a.m_den);
|
||||
UINT64_C(11115994079396609024)), a.denom());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,12 +148,12 @@ TEST(gncrational_functions, test_round_to_numeric)
|
||||
expected = expected.convert<RoundType::bankers>(100);
|
||||
auto rounded = c.round_to_numeric();
|
||||
rounded = rounded.convert<RoundType::bankers>(100);
|
||||
EXPECT_EQ(0, expected.m_num - rounded.m_num);
|
||||
EXPECT_FALSE(rounded.m_num.isBig());
|
||||
EXPECT_FALSE(rounded.m_den.isBig());
|
||||
EXPECT_FALSE(rounded.m_num.isNan());
|
||||
EXPECT_FALSE(rounded.m_den.isNan());
|
||||
EXPECT_FALSE(rounded.m_num.isOverflow());
|
||||
EXPECT_FALSE(rounded.m_den.isOverflow());
|
||||
EXPECT_EQ(0, expected.num() - rounded.num());
|
||||
EXPECT_FALSE(rounded.num().isBig());
|
||||
EXPECT_FALSE(rounded.denom().isBig());
|
||||
EXPECT_FALSE(rounded.num().isNan());
|
||||
EXPECT_FALSE(rounded.denom().isNan());
|
||||
EXPECT_FALSE(rounded.num().isOverflow());
|
||||
EXPECT_FALSE(rounded.denom().isOverflow());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user