Change signature of GncRational::round from taking a GncDenominator…

to a separate denominator and RoundType.
This commit is contained in:
John Ralls 2017-01-30 12:42:08 -08:00
parent 6f5d628b12
commit 3975b0b465
4 changed files with 122 additions and 74 deletions

View File

@ -34,6 +34,7 @@ extern "C"
#include <stdlib.h>
#include <string.h>
#ifdef __cplusplus
#include "qof.h"
}
#endif
#include <stdint.h>
@ -262,8 +263,14 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
return static_cast<gnc_numeric>(an.add(bn, new_denom));
try
{
return static_cast<gnc_numeric>(an.add(bn, new_denom));
}
catch (const std::overflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
}
/* *******************************************************************
@ -302,8 +309,15 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
try
{
return static_cast<gnc_numeric>(an.mul(bn, new_denom));
}
catch (const std::overflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
return static_cast<gnc_numeric>(an.mul(bn, new_denom));
}
@ -324,8 +338,14 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
return static_cast<gnc_numeric>(an.div(bn, new_denom));
try
{
return static_cast<gnc_numeric>(an.div(bn, new_denom));
}
catch (const std::overflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
}
/* *******************************************************************
@ -368,8 +388,16 @@ gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
GncNumeric a (in), b (gnc_numeric_zero());
GncDenom d (a, b, denom, how);
a.round (d);
return static_cast<gnc_numeric>(a);
try
{
d.reduce(a);
a.round (d.get(), d.m_round);
return static_cast<gnc_numeric>(a);
}
catch (const std::overflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
}
@ -391,8 +419,16 @@ gnc_numeric_reduce(gnc_numeric in)
return in;
GncNumeric a (in), b (gnc_numeric_zero());
GncDenom d (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
a.round (d);
return static_cast<gnc_numeric>(a);
try
{
d.reduce(a);
a.round (d.get(), d.m_round);
return static_cast<gnc_numeric>(a);
}
catch (const std::overflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
}

View File

@ -189,45 +189,41 @@ GncRational::operator/=(GncRational b)
}
GncRational&
GncRational::mul (const GncRational& b, GncDenom& d) noexcept
GncRational::mul (const GncRational& b, GncDenom& d)
{
*this *= b;
round (d);
d.reduce(*this);
round (d.get(), d.m_round);
return *this;
}
GncRational&
GncRational::div (GncRational b, GncDenom& d) noexcept
GncRational::div (GncRational b, GncDenom& d)
{
*this /= b;
round (d);
d.reduce(*this);
round (d.get(), d.m_round);
return *this;
}
GncRational&
GncRational::add (const GncRational& b, GncDenom& d) noexcept
GncRational::add (const GncRational& b, GncDenom& d)
{
*this += b;
round (d);
d.reduce(*this);
round (d.get(), d.m_round);
return *this;
}
GncRational&
GncRational::sub (const GncRational& b, GncDenom& d) noexcept
GncRational::sub (const GncRational& b, GncDenom& d)
{
return add(-b, d);
}
void
GncRational::round (GncDenom& denom) noexcept
GncRational::round (GncInt128 new_den, RoundType rtype)
{
denom.reduce (*this);
if (m_error == GNC_ERROR_OK && denom.m_error != GNC_ERROR_OK)
{
m_error = denom.m_error;
return;
}
GncInt128 new_den = denom.get();
if (new_den == 0) new_den = m_den;
if (!(m_num.isBig() || new_den.isBig() ))
{
@ -243,9 +239,20 @@ GncRational::round (GncDenom& denom) noexcept
GncInt128 new_num {}, remainder {};
if (new_den.isNeg())
m_num.div(-new_den * m_den, new_num, remainder);
else
else if (new_den != m_den)
(m_num * new_den).div(m_den, new_num, remainder);
else
{
new_num = m_num;
new_den = m_den;
remainder = 0;
}
if (new_num.isOverflow() || new_den.isOverflow() || remainder.isOverflow())
throw std::overflow_error("Overflow during rounding.");
if (new_num.isNan() || new_den.isNan() || remainder.isNan())
{
throw std::underflow_error("Underflow during rounding.");
}
if (remainder.isZero() && !(new_num.isBig() || new_den.isBig()))
{
m_num = new_num;
@ -255,49 +262,52 @@ GncRational::round (GncDenom& denom) noexcept
if (new_num.isBig() || new_den.isBig())
{
if (!denom.m_auto)
{
m_error = GNC_ERROR_OVERFLOW;
return;
}
/* First, try to reduce it */
GncInt128 gcd = new_num.gcd(new_den);
if (!(gcd.isNan() || gcd.isOverflow()))
{
new_num /= gcd;
new_den /= gcd;
remainder /= gcd;
}
/* if that didn't work, shift both num and den down until neither is "big", th
/* if that didn't work, shift both num and den down until neither is "big", then
* fall through to rounding.
*/
while (new_num && new_num.isBig() && new_den && new_den.isBig())
while (rtype != RoundType::never && new_num && new_num.isBig() &&
new_den && new_den.isBig())
{
new_num >>= 1;
new_den >>= 1;
remainder >>= 1;
}
}
if (remainder == 0)
{
m_num = new_num;
m_den = new_den;
return;
}
/* If we got here, then we can't exactly represent the rational with
* new_denom. We must either round or punt.
*/
switch (denom.m_round)
switch (rtype)
{
case GncDenom::RoundType::never:
case RoundType::never:
m_error = GNC_ERROR_REMAINDER;
return;
case GncDenom::RoundType::floor:
case RoundType::floor:
if (new_num.isNeg()) ++new_num;
break;
case GncDenom::RoundType::ceiling:
case RoundType::ceiling:
if (! new_num.isNeg()) ++new_num;
break;
case GncDenom::RoundType::truncate:
case RoundType::truncate:
break;
case GncDenom::RoundType::promote:
case RoundType::promote:
new_num += new_num.isNeg() ? -1 : 1;
break;
case GncDenom::RoundType::half_down:
case RoundType::half_down:
if (new_den.isNeg())
{
if (remainder * 2 > m_den * new_den)
@ -306,7 +316,7 @@ GncRational::round (GncDenom& denom) noexcept
else if (remainder * 2 > m_den)
new_num += new_num.isNeg() ? -1 : 1;
break;
case GncDenom::RoundType::half_up:
case RoundType::half_up:
if (new_den.isNeg())
{
if (remainder * 2 >= m_den * new_den)
@ -315,7 +325,7 @@ GncRational::round (GncDenom& denom) noexcept
else if (remainder * 2 >= m_den)
new_num += new_num.isNeg() ? -1 : 1;
break;
case GncDenom::RoundType::bankers:
case RoundType::bankers:
if (new_den.isNeg())
{
if (remainder * 2 > m_den * -new_den ||
@ -366,7 +376,7 @@ GncRational::round_to_numeric() const
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);
new_rational.round(gnc_denom.get(), gnc_denom.m_round);
return new_rational;
}
auto quot(m_den / m_num);
@ -387,15 +397,15 @@ GncRational::round_to_numeric() const
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);
new_rational.round(gnc_denom.get(), gnc_denom.m_round);
return new_rational;
}
GncDenom::GncDenom (GncRational& a, GncRational& b,
int64_t spec, unsigned int how) noexcept :
m_value (spec),
m_round (static_cast<GncDenom::RoundType>(how & GNC_NUMERIC_RND_MASK)),
m_type (static_cast<GncDenom::DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
m_round (static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK)),
m_type (static_cast<DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
m_auto (spec == GNC_DENOM_AUTO),
m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8),
m_error (GNC_ERROR_OK)

View File

@ -28,6 +28,28 @@
struct GncDenom;
enum class RoundType
{
floor = GNC_HOW_RND_FLOOR,
ceiling = GNC_HOW_RND_CEIL,
truncate = GNC_HOW_RND_TRUNC,
promote = GNC_HOW_RND_PROMOTE,
half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
half_up = GNC_HOW_RND_ROUND_HALF_UP,
bankers = GNC_HOW_RND_ROUND,
never = GNC_HOW_RND_NEVER,
};
enum class DenomType
{
den_auto = GNC_DENOM_AUTO,
exact = GNC_HOW_DENOM_EXACT,
reduce = GNC_HOW_DENOM_REDUCE,
lcd = GNC_HOW_DENOM_LCD,
fixed = GNC_HOW_DENOM_FIXED,
sigfigs = GNC_HOW_DENOM_SIGFIG,
};
/** @ingroup QOF
* @brief Rational number class using GncInt128 for the numerator and denominator.
*/
@ -62,14 +84,14 @@ public:
/** Round/convert this to the denominator provided by d, according to d's
* m_round value.
*/
void round (GncDenom& d) noexcept;
/* These are mutators; in other words, they implement the equivalent of
* operators *=, /=, +=, and -=. They return a reference to this for chaining.
*/
GncRational& mul(const GncRational& b, GncDenom& d) noexcept;
GncRational& div(GncRational b, GncDenom& d) noexcept;
GncRational& add(const GncRational& b, GncDenom& d) noexcept;
GncRational& sub(const GncRational& b, GncDenom& d) noexcept;
GncRational& mul(const GncRational& b, GncDenom& d);
GncRational& div(GncRational b, GncDenom& d);
GncRational& add(const GncRational& b, GncDenom& d);
GncRational& sub(const GncRational& b, GncDenom& d);
void round (GncInt128 new_den, RoundType rtype);
void operator+=(GncRational b);
void operator-=(GncRational b);
void operator*=(GncRational b);
@ -95,26 +117,6 @@ struct GncDenom
void reduce (const GncRational& a) noexcept;
GncInt128 get () const noexcept { return m_value; }
enum class RoundType : int
{
floor = GNC_HOW_RND_FLOOR,
ceiling = GNC_HOW_RND_CEIL,
truncate = GNC_HOW_RND_TRUNC,
promote = GNC_HOW_RND_PROMOTE,
half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
half_up = GNC_HOW_RND_ROUND_HALF_UP,
bankers = GNC_HOW_RND_ROUND,
never = GNC_HOW_RND_NEVER,
};
enum class DenomType : int
{
exact = GNC_HOW_DENOM_EXACT,
reduce = GNC_HOW_DENOM_REDUCE,
lcd = GNC_HOW_DENOM_LCD,
fixed = GNC_HOW_DENOM_FIXED,
sigfigs = GNC_HOW_DENOM_SIGFIG,
};
GncInt128 m_value;
RoundType m_round;
DenomType m_type;

View File

@ -792,7 +792,7 @@ check_mult_div (void)
* the overflow is eliminated.
*/
check_binary_op (gnc_numeric_error (GNC_ERROR_REMAINDER),
check_binary_op (gnc_numeric_error (GNC_ERROR_OVERFLOW),
gnc_numeric_div(a, b, GNC_DENOM_AUTO,
GNC_HOW_RND_NEVER | GNC_HOW_DENOM_EXACT),
a, b, "expected %s got %s = %s / %s for div exact");