Add GncRational::reduce() and GncRational::round_to_numeric().

This commit is contained in:
John Ralls 2017-01-30 10:49:57 -08:00
parent 340fb9761c
commit b0dfd96a93
2 changed files with 71 additions and 0 deletions

View File

@ -20,6 +20,7 @@
* * * *
*******************************************************************/ *******************************************************************/
#include <sstream>
#include "gnc-rational.hpp" #include "gnc-rational.hpp"
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
@ -334,6 +335,62 @@ GncRational::round (GncDenom& denom) noexcept
return; return;
} }
GncRational
GncRational::reduce() const
{
auto gcd = m_den.gcd(m_num);
if (gcd.isNan() || gcd.isOverflow())
throw std::overflow_error("Reduce failed, calculation of gcd overflowed.");
return GncRational(m_num / gcd, m_den / gcd);
}
GncRational
GncRational::round_to_numeric() const
{
if (m_num.isZero())
return GncRational(); //Default constructor makes 0/1
if (!(m_num.isBig() || m_den.isBig()))
return *this;
if (m_num.abs() > m_den)
{
auto quot(m_num / m_den);
if (quot.isBig())
{
std::ostringstream msg;
msg << " Cannot be represented as a "
<< "GncNumeric. Its integer value is too large.\n";
throw std::overflow_error(msg.str());
}
GncRational new_rational(*this);
GncRational scratch(1, 1);
auto divisor = static_cast<int64_t>(m_den / (m_num.abs() >> 62));
GncDenom gnc_denom(new_rational, scratch, divisor,
GNC_HOW_RND_ROUND_HALF_DOWN);
new_rational.round(gnc_denom);
return new_rational;
}
auto quot(m_den / m_num);
if (quot.isBig())
return GncRational(); //Smaller than can be represented as a GncNumeric
auto divisor = m_den >> 62;
if (m_num.isBig())
{
GncInt128 oldnum(m_num), num, rem;
oldnum.div(divisor, num, rem);
auto den = m_den / divisor;
num += rem * 2 >= den ? 1 : 0;
GncRational new_rational(num, den);
return new_rational;
}
GncRational new_rational(*this);
GncRational scratch(1, 1);
auto int_div = static_cast<int64_t>(m_den / divisor);
GncDenom gnc_denom(new_rational, scratch, int_div,
GNC_HOW_RND_ROUND_HALF_DOWN);
new_rational.round(gnc_denom);
return new_rational;
}
GncDenom::GncDenom (GncRational& a, GncRational& b, GncDenom::GncDenom (GncRational& a, GncRational& b,
int64_t spec, unsigned int how) noexcept : int64_t spec, unsigned int how) noexcept :
m_value (spec), m_value (spec),

View File

@ -45,6 +45,20 @@ public:
operator gnc_numeric() const noexcept; operator gnc_numeric() const noexcept;
/** Make a new GncRational with the opposite sign. */ /** Make a new GncRational with the opposite sign. */
GncRational operator-() const noexcept; GncRational operator-() const noexcept;
/**
* Reduce this to an equivalent fraction with the least common multiple as the
* denominator.
*
* @return reduced GncRational
*/
GncRational reduce() const;
/**
* Round to fit an int64_t, finding the closest possible approximation.
*
* Throws std::overflow_error if m_den is 1 and m_num is big.
* @return rounded GncRational
*/
GncRational round_to_numeric() const;
/** Round/convert this to the denominator provided by d, according to d's /** Round/convert this to the denominator provided by d, according to d's
* m_round value. * m_round value.
*/ */