Convert GncRational to throw instead of using a status byte.

More consistent with GncNumeric and saves a word of memory per instance.
Still bleeping huge because the two GncInt128s each need 128 bits (2 or 4 words)
plus a word for status (for 3 bits!).
Also provide a couple of convenience functions, is_big() and valid() to
test if the either numerator and denominator is big or overflowed or NaN.
This commit is contained in:
John Ralls 2017-01-30 15:01:27 -08:00
parent ff7e6a37d5
commit c633e80a24
7 changed files with 169 additions and 148 deletions

View File

@ -211,6 +211,12 @@ GncInt128::isNan () const noexcept
return (m_flags & NaN);
}
bool
GncInt128::valid() const noexcept
{
return !(m_flags & (overflow | NaN));
}
bool
GncInt128::isZero() const noexcept
{

View File

@ -210,6 +210,10 @@ enum // Values for m_flags
* @return true if the object represents 0.
*/
bool isZero() const noexcept;
/**
* @return true if neither the overflow nor nan flags are set.
*/
bool valid() const noexcept;
/**
* @return the number of bits used to represent the value

View File

@ -328,14 +328,7 @@ GncNumeric::to_decimal(unsigned int max_places) const
return GncNumeric(m_num / excess, powten(max_places));
}
GncRational rr(*this);
rr.round(powten(max_places), RoundType::never);
if (rr.m_error)
{
std::ostringstream msg;
msg << "GncNumeric " << *this
<< " could not be represented as a decimal without rounding.\n";
throw std::range_error(msg.str());
}
rr.round(powten(max_places), RoundType::never); //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);
@ -804,11 +797,8 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto sum = ar + br;
sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
if (sum.m_error)
return gnc_numeric_error(sum.m_error);
if (sum.m_num.isBig() || sum.m_den.isBig() ||
sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
sum.m_num.isNan() || sum.m_den.isNan())
if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(sum);
}
@ -827,6 +817,11 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
@ -854,11 +849,7 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto sum = ar - br;
sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
if (sum.m_error)
return gnc_numeric_error(sum.m_error);
if (sum.m_num.isBig() || sum.m_den.isBig() ||
sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
sum.m_num.isNan() || sum.m_den.isNan())
if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(sum);
}
@ -877,6 +868,11 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
@ -903,11 +899,7 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto prod = ar * br;
prod.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
if (prod.m_error)
return gnc_numeric_error(prod.m_error);
if (prod.m_num.isBig() || prod.m_den.isBig() ||
prod.m_num.isOverflow() || prod.m_den.isOverflow() ||
prod.m_num.isNan() || prod.m_den.isNan())
if (prod.is_big() || !prod.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(prod);
}
@ -926,6 +918,11 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
@ -953,11 +950,7 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto quot = ar / br;
quot.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
if (quot.m_error)
return gnc_numeric_error(quot.m_error);
if (quot.m_num.isBig() || quot.m_den.isBig() ||
quot.m_num.isOverflow() || quot.m_den.isOverflow() ||
quot.m_num.isNan() || quot.m_den.isNan())
if (quot.is_big() || !quot.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(quot);
}
@ -976,6 +969,11 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
@ -1056,7 +1054,11 @@ gnc_numeric_reduce(gnc_numeric in)
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
@ -1112,6 +1114,11 @@ gnc_numeric_invert(gnc_numeric num)
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
@ -1144,6 +1151,11 @@ double_to_gnc_numeric(double in, gint64 denom, gint how)
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************

View File

@ -26,7 +26,7 @@
GncRational::GncRational(GncNumeric n) noexcept :
m_num(n.num()), m_den(n.denom()), m_error(GNC_ERROR_OK)
m_num(n.num()), m_den(n.denom())
{
if (m_den.isNeg())
{
@ -36,7 +36,7 @@ GncRational::GncRational(GncNumeric n) noexcept :
}
GncRational::GncRational (gnc_numeric n) noexcept :
m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK}
m_num (n.num), m_den (n.denom)
{
if (m_den.isNeg())
{
@ -45,13 +45,26 @@ GncRational::GncRational (gnc_numeric n) noexcept :
}
}
bool
GncRational::valid() const noexcept
{
if (m_num.valid() && m_den.valid())
return true;
return false;
}
bool
GncRational::is_big() const noexcept
{
if (m_num.isBig() || m_den.isBig())
return true;
return false;
}
GncRational::operator gnc_numeric () const noexcept
{
if (m_num.isOverflow() || m_num.isNan() ||
m_den.isOverflow() || m_den.isNan())
if (!valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
if (m_error != GNC_ERROR_OK)
return gnc_numeric_error (m_error);
try
{
return {static_cast<int64_t>(m_num), static_cast<int64_t>(m_den)};
@ -87,16 +100,12 @@ GncRational::inv () noexcept
GncRational
operator+(GncRational a, GncRational b)
{
if (a.m_error || b.m_error)
{
if (b.m_error)
return GncRational(0, 1, b.m_error);
return GncRational(0, 1, a.m_error);
}
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.isOverflow() || lcm.isNan() || num.isOverflow() || num.isNan())
return GncRational(0, 1, GNC_ERROR_OVERFLOW);
if (!(lcm.valid() && num.valid()))
throw std::overflow_error("Operator+ overflowed.");
GncRational retval(num, lcm);
return retval;
}
@ -111,15 +120,11 @@ operator-(GncRational a, GncRational b)
GncRational
operator*(GncRational a, GncRational b)
{
if (a.m_error || b.m_error)
{
if (b.m_error)
return GncRational(0, 1, b.m_error);
return GncRational(0, 1, a.m_error);
}
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.isOverflow() || num.isNan() || den.isOverflow() || den.isNan())
return GncRational(0, 1, GNC_ERROR_OVERFLOW);
if (!(num.valid() && den.valid()))
throw std::overflow_error("Operator* overflowed.");
GncRational retval(num, den);
return retval;
}
@ -127,12 +132,10 @@ operator*(GncRational a, GncRational b)
GncRational
operator/(GncRational a, GncRational b)
{
if (a.m_error || b.m_error)
{
if (b.m_error)
return GncRational(0, 1, b.m_error);
return GncRational(0, 1, a.m_error);
}
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;
@ -155,8 +158,8 @@ operator/(GncRational a, GncRational b)
}
GncInt128 num(a.m_num * b.m_den), den(a.m_den * b.m_num);
if (num.isOverflow() || num.isNan() || den.isOverflow() || den.isNan())
return GncRational(0, 1, GNC_ERROR_OVERFLOW);
if (!(num.valid() && den.valid()))
throw std::overflow_error("Operator/ overflowed.");
return GncRational(num, den);
}
@ -261,8 +264,7 @@ GncRational::round (GncInt128 new_den, RoundType rtype)
switch (rtype)
{
case RoundType::never:
m_error = GNC_ERROR_REMAINDER;
return;
throw std::domain_error("Rounding required when 'never round' specified.");
case RoundType::floor:
if (new_num.isNeg()) ++new_num;
break;

View File

@ -37,16 +37,23 @@ enum class DenomType;
class GncRational
{
public:
GncRational() : m_num(0), m_den(1), m_error(GNC_ERROR_OK) {}
GncRational() : m_num(0), m_den(1) {}
GncRational (gnc_numeric n) noexcept;
GncRational(GncNumeric n) noexcept;
GncRational (GncInt128 num, GncInt128 den,
GNCNumericErrorCode err=GNC_ERROR_OK) noexcept
: m_num(num), m_den(den), m_error(err) {}
GncRational (GncInt128 num, GncInt128 den) noexcept
: m_num(num), m_den(den) {}
GncRational(const GncRational& rhs) = default;
GncRational(GncRational&& rhs) = default;
GncRational& operator=(const GncRational& rhs) = default;
GncRational& operator=(GncRational&& rhs) = default;
/** Report if both members are valid numbers.
* \return true if neither numerator nor denominator are Nan or Overflowed.
*/
bool valid() const noexcept;
/** Report if either numerator or denominator are too big to fit in an int64_t.
* \return true if either is too big.
*/
bool is_big() const noexcept;
/** Conversion operator; use static_cast<gnc_numeric>(foo). */
operator gnc_numeric() const noexcept;
/** Make a new GncRational with the opposite sign. */
@ -78,7 +85,6 @@ public:
GncInt128 m_num;
GncInt128 m_den;
GNCNumericErrorCode m_error;
};
GncRational operator+(GncRational a, GncRational b);

View File

@ -499,7 +499,7 @@ TEST(gnc_numeric_functions, test_conversion_to_decimal)
EXPECT_EQ(1000, r.denom());
EXPECT_THROW(r = a.to_decimal(2), std::range_error);
GncNumeric b(123456789, 456);
EXPECT_THROW(r = b.to_decimal(), std::range_error);
EXPECT_THROW(r = b.to_decimal(), std::domain_error);
GncNumeric c(123456789, 450);
EXPECT_NO_THROW(r = c.to_decimal());
EXPECT_EQ(27434842, r.num());

View File

@ -28,118 +28,109 @@
TEST(gncrational_constructors, test_default_constructor)
{
GncRational value;
EXPECT_EQ(value.m_num, 0);
EXPECT_EQ(value.m_den, 1);
EXPECT_EQ(value.m_error, GNC_ERROR_OK);
EXPECT_NO_THROW({
GncRational value;
EXPECT_EQ(value.m_num, 0);
EXPECT_EQ(value.m_den, 1);
});
}
TEST(gncrational_constructors, test_gnc_numeric_constructor)
{
gnc_numeric input = gnc_numeric_create(123, 456);
GncRational value(input);
EXPECT_EQ(input.num, value.m_num);
EXPECT_EQ(input.denom, value.m_den);
EXPECT_EQ(value.m_error, GNC_ERROR_OK);
EXPECT_NO_THROW({
GncRational value(input);
EXPECT_EQ(input.num, value.m_num);
EXPECT_EQ(input.denom, value.m_den);
});
}
TEST(gncrational_constructors, test_gnc_int128_constructor)
{
GncInt128 num(123), denom(456);
GncRational value(num, denom);
EXPECT_EQ(123, value.m_num);
EXPECT_EQ(456, value.m_den);
EXPECT_EQ(GNC_ERROR_OK, value.m_error);
EXPECT_NO_THROW({
GncRational value(num, denom);
EXPECT_EQ(123, value.m_num);
EXPECT_EQ(456, value.m_den);
});
}
TEST(gncrational_constructors, test_implicit_int_constructor)
{
int num(123), denom(456);
GncRational value(num, denom);
EXPECT_EQ(123, value.m_num);
EXPECT_EQ(456, value.m_den);
EXPECT_EQ(GNC_ERROR_OK, value.m_error);
}
TEST(gncrational_constructors, test_with_error_code)
{
int num(123), denom(456);
GncRational value(num, denom, GNC_ERROR_OVERFLOW);
EXPECT_EQ(123, value.m_num);
EXPECT_EQ(456, value.m_den);
EXPECT_EQ(GNC_ERROR_OVERFLOW, value.m_error);
EXPECT_NO_THROW({
GncRational value(num, denom);
EXPECT_EQ(123, value.m_num);
EXPECT_EQ(456, value.m_den);
});
}
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 (GNC_ERROR_OK, c.m_error);
a += b;
EXPECT_EQ (777778777641976301, a.m_num);
EXPECT_EQ (1000000000, a.m_den);
EXPECT_EQ (GNC_ERROR_OK, a.m_error);
EXPECT_NO_THROW({
GncRational a(123456789987654321, 1000000000);
GncRational b(65432198765432198, 100000000);
GncRational c = a + b;
EXPECT_EQ (777778777641976301, c.m_num);
EXPECT_EQ (1000000000, c.m_den);
a += b;
EXPECT_EQ (777778777641976301, a.m_num);
EXPECT_EQ (1000000000, a.m_den);
});
}
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 (GNC_ERROR_OK, c.m_error);
c = b - a;
EXPECT_EQ (530865197666667659, c.m_num);
EXPECT_FALSE(c.m_num.isNeg());
EXPECT_EQ (1000000000, c.m_den);
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
a -= b;
EXPECT_EQ (-530865197666667659, a.m_num);
EXPECT_TRUE(a.m_num.isNeg());
EXPECT_EQ (1000000000, a.m_den);
EXPECT_EQ (GNC_ERROR_OK, a.m_error);
EXPECT_NO_THROW({
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);
c = b - a;
EXPECT_EQ (530865197666667659, c.m_num);
EXPECT_FALSE(c.m_num.isNeg());
EXPECT_EQ (1000000000, c.m_den);
a -= b;
EXPECT_EQ (-530865197666667659, a.m_num);
EXPECT_TRUE(a.m_num.isNeg());
EXPECT_EQ (1000000000, a.m_den);
});
}
TEST(gncrational_operators, test_multiplication)
{
GncRational a(123456789987654321, 1000000000);
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);
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
a *= b;
EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
UINT64_C(8081008345983448486)), a.m_num);
EXPECT_EQ (100000000000000000, a.m_den);
EXPECT_EQ (GNC_ERROR_OK, a.m_error);
EXPECT_NO_THROW({
GncRational a(123456789987654321, 1000000000);
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);
a *= b;
EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
UINT64_C(8081008345983448486)), a.m_num);
EXPECT_EQ (100000000000000000, a.m_den);
});
}
TEST(gncrational_operators, test_division)
{
GncRational a(123456789987654321, 1000000000);
GncRational b(65432198765432198, 100000000);
GncRational c = a / b;
EXPECT_EQ (GncInt128(UINT64_C(669260), UINT64_C(11059994577585475840)),
c.m_num);
EXPECT_EQ (GncInt128(UINT64_C(3547086), UINT64_C(11115994079396609024)),
c.m_den);
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
a /= b;
EXPECT_EQ (GncInt128(UINT64_C(669260), UINT64_C(11059994577585475840)),
a.m_num);
EXPECT_EQ (GncInt128(UINT64_C(3547086), UINT64_C(11115994079396609024)),
a.m_den);
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
EXPECT_NO_THROW({
GncRational a(123456789987654321, 1000000000);
GncRational b(65432198765432198, 100000000);
GncRational c = a / b;
EXPECT_EQ (GncInt128(UINT64_C(669260),
UINT64_C(11059994577585475840)), c.m_num);
EXPECT_EQ (GncInt128(UINT64_C(3547086),
UINT64_C(11115994079396609024)), c.m_den);
a /= b;
EXPECT_EQ (GncInt128(UINT64_C(669260),
UINT64_C(11059994577585475840)), a.m_num);
EXPECT_EQ (GncInt128(UINT64_C(3547086),
UINT64_C(11115994079396609024)), a.m_den);
});
}
TEST(gncrational_functions, test_round_to_numeric)