Extract new class GncNumeric.

Similar to GncRational, except that it’s based on int64_t instead of
GncInt128 and throws instead of using a status byte.

Most calculations are performed using GncRational, the result is then
rounded (RoundType::half_down) to fit. GncRational should be used in
circumstances where the automatic rounding is undesirable.
This commit is contained in:
John Ralls
2017-01-30 13:17:05 -08:00
parent 4fef04c17b
commit 82fe06e390
8 changed files with 1557 additions and 98 deletions

View File

@@ -36,12 +36,14 @@ extern "C"
}
#include <stdint.h>
#include <regex>
#include <sstream>
#include <cstdlib>
#include "gnc-numeric.h"
#include "gnc-numeric.hpp"
#include "gnc-rational.hpp"
using GncNumeric = GncRational;
static QofLogModule log_module = "qof";
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000,
INT64_C(10000000000), INT64_C(100000000000),
@@ -52,14 +54,538 @@ static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
INT64_C(1000000000000000000)};
#define POWTEN_OVERFLOW -5
static inline gint64
powten (int exp)
int64_t
powten (int64_t exp)
{
if (exp > 18 || exp < -18)
return POWTEN_OVERFLOW;
return exp < 0 ? -pten[-exp] : pten[exp];
}
GncNumeric::GncNumeric(GncRational rr)
{
if (rr.m_num.isNan() || rr.m_den.isNan())
throw std::underflow_error("Operation resulted in NaN.");
if (rr.m_num.isOverflow() || rr.m_den.isOverflow())
throw std::overflow_error("Operation overflowed a 128-bit int.");
if (rr.m_num.isBig() || rr.m_den.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);
}
GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
{
if (isnan(d) || fabs(d) > 1e18)
{
std::ostringstream msg;
msg << "Unable to construct a GncNumeric from " << d << ".\n";
throw std::invalid_argument(msg.str());
}
constexpr auto max_denom = INT64_MAX / 10;
auto logval = log10(fabs(d));
int64_t den;
if (logval > 0.0)
den = powten(18 - static_cast<int>(floor(logval) + 1.0));
else
den = powten(17);
auto num = static_cast<int64_t>(floor(static_cast<double>(den) * d));
if (num == 0)
return;
GncNumeric q(num, den);
auto r = q.reduce();
m_num = r.num();
m_den = r.denom();
}
GncNumeric::GncNumeric(const std::string& str, bool autoround)
{
static const std::string numer_frag("(-?[0-9]+)");
static const std::string denom_frag("([0-9]+)");
static const std::string hex_frag("(0x[a-f0-9]+)");
static const std::string slash( "[ \\t]*/[ \\t]*");
/* The llvm standard C++ library refused to recognize the - in the
* numer_frag patter with the default ECMAScript syntax so we use the awk
* syntax.
*/
static const std::regex numeral(numer_frag, std::regex::awk);
static const std::regex hex(hex_frag, std::regex::awk);
static const std::regex numeral_rational(numer_frag + slash + denom_frag,
std::regex::awk);
static const std::regex hex_rational(hex_frag + slash + hex_frag,
std::regex::awk);
static const std::regex hex_over_num(hex_frag + slash + denom_frag,
std::regex::awk);
static const std::regex num_over_hex(numer_frag + slash + hex_frag,
std::regex::awk);
static const std::regex decimal(numer_frag + "[.,]" + denom_frag,
std::regex::awk);
std::smatch m;
/* The order of testing the regexes is from the more restrictve to the less
* restrictive, as less-restrictive ones will match patterns that would also
* match the more-restrictive and so invoke the wrong construction.
*/
if (str.empty())
throw std::invalid_argument("Can't construct a GncNumeric from an empty string.");
if (std::regex_search(str, m, hex_rational))
{
GncNumeric n(stoll(m[1].str(), nullptr, 16),
stoll(m[2].str(), nullptr, 16));
m_num = n.num();
m_den = n.denom();
return;
}
if (std::regex_search(str, m, hex_over_num))
{
GncNumeric n(stoll(m[1].str(), nullptr, 16),
stoll(m[2].str()));
m_num = n.num();
m_den = n.denom();
return;
}
if (std::regex_search(str, m, num_over_hex))
{
GncNumeric n(stoll(m[1].str()),
stoll(m[2].str(), nullptr, 16));
m_num = n.num();
m_den = n.denom();
return;
}
if (std::regex_search(str, m, numeral_rational))
{
GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
m_num = n.num();
m_den = n.denom();
return;
}
if (std::regex_search(str, m, decimal))
{
GncInt128 high(stoll(m[1].str()));
GncInt128 low(stoll(m[2].str()));
int64_t d = powten(m[2].str().length());
GncInt128 n = high * d + (high > 0 ? low : -low);
if (!autoround && n.isBig())
{
std::ostringstream errmsg;
errmsg << "Decimal string " << m[1].str() << "." << m[2].str()
<< "can't be represented in a GncNumeric without rounding.";
throw std::overflow_error(errmsg.str());
}
while (n.isBig() && d > 0)
{
n >>= 1;
d >>= 1;
}
if (n.isBig()) //Shouldn't happen, of course
{
std::ostringstream errmsg;
errmsg << "Decimal string " << m[1].str() << "." << m[2].str()
<< " can't be represented in a GncNumeric, even after reducing denom to " << d;
throw std::overflow_error(errmsg.str());
}
GncNumeric gncn(static_cast<int64_t>(n), d);
m_num = gncn.num();
m_den = gncn.denom();
return;
}
if (std::regex_search(str, m, hex))
{
GncNumeric n(stoll(m[1].str(), nullptr, 16),INT64_C(1));
m_num = n.num();
m_den = n.denom();
return;
}
if (std::regex_search(str, m, numeral))
{
GncNumeric n(stoll(m[1].str()), INT64_C(1));
m_num = n.num();
m_den = n.denom();
return;
}
std::ostringstream errmsg;
errmsg << "String " << str << " contains no recognizable numeric value.";
throw std::invalid_argument(errmsg.str());
}
GncNumeric::operator gnc_numeric() const noexcept
{
return {m_num, m_den};
}
GncNumeric::operator double() const noexcept
{
return static_cast<double>(m_num) / static_cast<double>(m_den);
}
GncNumeric
GncNumeric::operator-() const noexcept
{
GncNumeric b(*this);
b.m_num = - b.m_num;
return b;
}
GncNumeric
GncNumeric::inv() const noexcept
{
if (m_num == 0)
return *this;
if (m_num < 0)
return GncNumeric(-m_den, -m_num);
return GncNumeric(m_den, m_num);
}
GncNumeric
GncNumeric::abs() const noexcept
{
if (m_num < 0)
return -*this;
return *this;
}
GncNumeric
GncNumeric::reduce() const noexcept
{
return static_cast<GncNumeric>(GncRational(*this).reduce());
}
GncNumeric::round_param
GncNumeric::prepare_conversion(int64_t new_denom) const
{
if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
return {m_num, m_den, 0};
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;
if (new_num.isBig())
{
GncRational rr(new_num, new_denom);
GncNumeric nn(rr);
rr.round(new_denom, RoundType::truncate);
return {static_cast<int64_t>(rr.m_num), new_denom, 0};
}
return {static_cast<int64_t>(new_num), static_cast<int64_t>(red_conv.m_den),
static_cast<int64_t>(rem)};
}
int64_t
GncNumeric::sigfigs_denom(unsigned figs) const noexcept
{
int64_t num_abs{std::abs(m_num)};
bool not_frac = num_abs > m_den;
int64_t val{ not_frac ? num_abs / m_den : m_den / num_abs };
unsigned digits{};
while (val >= 10)
{
++digits;
val /= 10;
}
return not_frac ? powten(figs - digits - 1) : powten(figs + digits);
}
std::string
GncNumeric::to_string() const noexcept
{
std::ostringstream out;
out << *this;
return out.str();
}
GncNumeric
GncNumeric::to_decimal(unsigned int max_places) const
{
if (max_places > 17)
max_places = 17;
bool is_pwr_ten = true;
for (int pwr = 1; pwr <= 17 && m_den > powten(pwr); ++pwr)
if (m_den % powten(pwr))
{
is_pwr_ten = false;
break;
}
if (m_num == 0 || (is_pwr_ten && m_den < powten(max_places)))
return *this; // Nothing to do.
if (is_pwr_ten)
{
/* See if we can reduce m_num to fit in max_places */
auto excess = m_den / powten(max_places);
if (m_num % excess)
{
std::ostringstream msg;
msg << "GncNumeric " << *this
<< " could not be represented in " << max_places
<< " decimal places without rounding.\n";
throw std::range_error(msg.str());
}
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 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);
if (rr.m_den % powten(pwr))
{
auto factor(powten(pwr) / rr.m_den);
rr.m_num *= factor;
rr.m_den *= factor;
}
while (rr.m_num % 10 == 0)
{
rr.m_num /= 10;
rr.m_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)};
}
catch (const std::invalid_argument& err)
{
std::ostringstream msg;
msg << "GncNumeric " << *this
<< " could not be represented as a decimal without rounding.\n";
throw std::range_error(msg.str());
}
catch (const std::overflow_error& err)
{
std::ostringstream msg;
msg << "GncNumeric " << *this
<< " overflows when attempting to convert it to decimal.\n";
throw std::range_error(msg.str());
}
}
void
GncNumeric::operator+=(GncNumeric b)
{
*this = *this + b;
}
void
GncNumeric::operator-=(GncNumeric b)
{
*this = *this - b;
}
void
GncNumeric::operator*=(GncNumeric b)
{
*this = *this * b;
}
void
GncNumeric::operator/=(GncNumeric b)
{
*this = *this / b;
}
int
GncNumeric::cmp(GncNumeric b)
{
if (m_den == b.denom())
{
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);
}
GncNumeric
operator+(GncNumeric a, GncNumeric b)
{
if (a.num() == 0)
return b;
if (b.num() == 0)
return a;
GncRational ar(a), br(b);
auto rr = ar + br;
return static_cast<GncNumeric>(rr);
}
GncNumeric
operator-(GncNumeric a, GncNumeric b)
{
return a + (-b);
}
GncNumeric
operator*(GncNumeric a, GncNumeric b)
{
if (a.num() == 0 || b.num() == 0)
{
GncNumeric retval;
return retval;
}
GncRational ar(a), br(b);
auto rr = ar * br;
return static_cast<GncNumeric>(rr);
}
GncNumeric
operator/(GncNumeric a, GncNumeric b)
{
if (a.num() == 0)
{
GncNumeric retval;
return retval;
}
if (b.num() == 0)
throw std::underflow_error("Attempt to divide by zero.");
GncRational ar(a), br(b);
auto rr = ar / br;
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;
}
static gnc_numeric
convert(GncNumeric num, int64_t new_denom, int how)
{
// std::cout << "Converting " << num << ".\n";
auto rtype = static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK);
unsigned int figs = GNC_HOW_GET_SIGFIGS(how);
auto dtype = static_cast<DenomType>(how & GNC_NUMERIC_DENOM_MASK);
bool sigfigs = dtype == DenomType::sigfigs;
if (dtype == DenomType::reduce)
num = num.reduce();
try
{
switch (rtype)
{
case RoundType::floor:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::floor>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::floor>(new_denom));
case RoundType::ceiling:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::ceiling>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::ceiling>(new_denom));
case RoundType::truncate:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::truncate>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::truncate>(new_denom));
case RoundType::promote:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::promote>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::promote>(new_denom));
case RoundType::half_down:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::half_down>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::half_down>(new_denom));
case RoundType::half_up:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::half_up>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::half_up>(new_denom));
case RoundType::bankers:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::bankers>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::bankers>(new_denom));
case RoundType::never:
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::never>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::never>(new_denom));
default:
/* round-truncate just returns the numerator unchanged. The old gnc-numeric
* convert had no "default" behavior at rounding that had the same result, but
* we need to make it explicit here to run the rest of the conversion code.
*/
if (sigfigs)
return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::truncate>(figs));
else
return static_cast<gnc_numeric>(num.convert<RoundType::truncate>(new_denom));
// return static_cast<gnc_numeric>(num);
}
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
catch (const std::overflow_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::exception& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
}
/* =============================================================== */
/* This function is small, simple, and used everywhere below,
@@ -182,7 +708,7 @@ gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
return -1;
}
GncNumeric an (a), bn (b);
GncRational an (a), bn (b);
return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
}
@@ -256,7 +782,7 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
return gnc_numeric_error(GNC_ERROR_ARG);
}
GncNumeric an (a), bn (b);
GncRational an (a), bn (b);
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
@@ -303,7 +829,7 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
return gnc_numeric_error(GNC_ERROR_ARG);
}
GncNumeric an (a), bn (b);
GncRational an (a), bn (b);
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
@@ -332,7 +858,7 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
return gnc_numeric_error(GNC_ERROR_ARG);
}
GncNumeric an (a), bn (b);
GncRational an (a), bn (b);
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
@@ -384,7 +910,7 @@ gnc_numeric_abs(gnc_numeric a)
gnc_numeric
gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
GncNumeric a (in), b (gnc_numeric_zero());
GncRational a (in), b (gnc_numeric_zero());
GncDenom d (a, b, denom, how);
try
{
@@ -415,7 +941,7 @@ gnc_numeric_reduce(gnc_numeric in)
if (in.denom < 0) /* Negative denoms multiply num, can't be reduced. */
return in;
GncNumeric a (in), b (gnc_numeric_zero());
GncRational a (in), b (gnc_numeric_zero());
GncDenom d (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
try
{
@@ -521,7 +1047,7 @@ gnc_numeric_invert(gnc_numeric num)
{
if (num.num == 0)
return gnc_numeric_zero();
return static_cast<gnc_numeric>(GncNumeric(num).inv());
return static_cast<gnc_numeric>(GncRational(num).inv());
}
/* *******************************************************************
* double_to_gnc_numeric

View File

@@ -0,0 +1,473 @@
/********************************************************************
* gnc-numeric.hpp - A rational number library for int64 *
* Copyright 2017 John Ralls <jralls@ceridwen.us> *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
* *
*******************************************************************/
#ifndef __GNC_NUMERIC_HPP__
#define __GNC_NUMERIC_HPP__
#include <string>
#include <iostream>
#include "gnc-numeric.h"
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,
};
template <RoundType rt>
struct RT2T
{
RoundType value = rt;
};
/* The following templates implement the rounding policies for the convert and
* convert_sigfigs template functions.
*/
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::never>)
{
if (rem == 0)
return num;
throw std::domain_error("Rounding required when 'never round' specified.");
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::floor>)
{
// std::cout << "Rounding to floor with num " << num << " den " << den
// << ", and rem " << rem << ".\n";
if (rem == 0)
return num;
if (num < 0)
return num + 1;
return num;
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::ceiling>)
{
if (rem == 0)
return num;
if (num > 0)
return num + 1;
return num;
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::truncate>)
{
return num;
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::promote>)
{
if (rem == 0)
return num;
return num + (num < 0 ? -1 : 1);
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::half_down>)
{
if (rem == 0)
return num;
if (rem * 2 > den)
return num + (num < 0 ? -1 : 1);
return num;
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::half_up>)
{
if (rem == 0)
return num;
if (rem * 2 >= den)
return num + (num < 0 ? -1 : 1);
return num;
}
inline int64_t
round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::bankers>)
{
if (rem == 0)
return num;
if (rem * 2 > den || (rem * 2 == den && num % 2))
return num += (num < 0 ? -1 : 1);
return num;
}
class GncRational;
/**
* 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
* representation then the GncNumeric(GncRational&) constructor will call
* GncRational::round_to_numeric() to get the value to fit. It will not raise an
* exeception, so in the unlikely event that you need an error instead of
* rounding, use GncRational directly.
*
* 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.
*
* Rounding Policy: GncNumeric provides a convert() member function that object
* amount and value setters (and *only* those functions!) should call to set a
* number which is represented in the commodity's SCU. Since SCUs are seldom 18
* digits the convert may result in rounding, which will be performed in the
* method specified by the arguments passed to convert(). Overflows may result
* in internal rounding as described earlier.
*/
class GncNumeric
{
public:
/**
* Default constructor provides the zero value.
*/
GncNumeric() : m_num (0), m_den(1) {}
/**
* Integer constructor.
*
* Unfortunately specifying a default for denom causes ambiguity errors with
* the other single-argument constructors on older versions of gcc, so one
* must always specify both argumets.
*
* \param num The Numerator
* \param denom The Denominator
*/
GncNumeric(int64_t num, int64_t denom) :
m_num(num), m_den(denom) {
if (denom == 0)
throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
}
/**
* GncRational constructor.
*
* This constructor will round rr's GncInt128s to fit into the int64_t
* members using RoundType::half-down.
*
* \param rr A GncRational.
*/
GncNumeric(GncRational rr);
/**
* gnc_numeric constructor, used for interfacing old code. This function
* should not be used outside of gnc-numeric.cpp.
*
* \param in A gnc_numeric.
*/
GncNumeric(gnc_numeric in) : m_num(in.num), m_den(in.denom)
{
if (in.denom == 0)
throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
/* gnc_numeric has a dumb convention that a negative denominator means
* to multiply the numerator by the denominator instead of dividing.
*/
if (in.denom < 0)
{
m_num *= -in.denom;
m_den = 1;
}
}
/**
* Double constructor.
*
* @param d The double to be converted. In order to fit in an int64_t, its
* absolute value must be < 1e18; if its absolute value is < 1e-18 it will
* be represented as 0, though for practical purposes nearly all commodities
* will round to zero at 1e-9 or larger.
*/
GncNumeric(double d);
/**
* String constructor.
*
* Accepts integer values in decimal and hexadecimal. Does not accept
* thousands separators. If the string contains a '/' it is taken to
* separate the numerator and denominator; if it conains either a '.' or a
* ',' it is taken as a decimal point and the integers on either side will
* be combined and a denominator will be the appropriate power of 10. If
* neither is present the number will be treated as an integer and m_den
* will be set to 1.
*
* Whitespace around a '/' is ignored. A correctly-formatted number will be
* extracted from a larger string.
*
* Numbers that cannot be represented with int64_ts will throw
* std::out_of_range unless a decimal point is found (see above). A 0
* denominator will cause the constructor to throw std::underflow_error. An
* empty string or one which contains no recognizable number will result in
* std::invalid_argument.
*/
GncNumeric(const std::string& str, bool autoround=false);
/**
* gnc_numeric conversion. Use static_cast<gnc_numeric>(foo)
*/
operator gnc_numeric() const noexcept;
/**
* gnc_numeric conversion. Use static_cast<double>(foo)
*/
operator double() const noexcept;
/**
* Accessor for numerator value.
*/
int64_t num() const noexcept { return m_num; }
/**
* Accessor for denominator value.
*/
int64_t denom() const noexcept { return m_den; }
/**
* @return A GncNumeric with the opposite sign.
*/
GncNumeric operator-() const noexcept;
/**
* @return 0 if this == 0 else 1/this.
*/
GncNumeric inv() const noexcept;
/**
* @return -this if this < 0 else this.
*/
GncNumeric abs() const noexcept;
/**
* Reduce this to an equivalent fraction with the least common multiple as
* the denominator.
*
* @return reduced GncNumeric
*/
GncNumeric reduce() const noexcept;
/**
* Convert a GncNumeric to use a new denominator. If rounding is necessary
* use the indicated template specification. For example, to use half-up
* rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you
* specify RoundType::never this will throw std::domain_error if rounding is
* required.
*
* \param new_denom The new denominator to convert the fraction to.
* \return A new GncNumeric having the requested denominator.
*/
template <RoundType RT>
GncNumeric convert(int64_t new_denom) const
{
auto params = prepare_conversion(new_denom);
if (new_denom == GNC_DENOM_AUTO)
new_denom = m_den;
if (params.rem == 0)
return GncNumeric(params.num, new_denom);
return GncNumeric(round(params.num, params.den,
params.rem, RT2T<RT>()), new_denom);
}
/**
* Convert with the specified sigfigs. The resulting denominator depends on
* the value of the GncNumeric, such that the specified significant digits
* are retained in the numerator and the denominator is always a power of
* 10. This is of rather dubious benefit in an accounting program, but it's
* used in several places so it needs to be implemented.
*
* @param figs The number of digits to use for the numerator.
* @return A GncNumeric with the specified number of digits in the numerator
* and the appropriate power-of-ten denominator.
*/
template <RoundType RT>
GncNumeric convert_sigfigs(int figs) const
{
auto new_denom(sigfigs_denom(figs));
auto params = prepare_conversion(new_denom);
if (new_denom == 0) //It had better not, but just in case...
new_denom = 1;
if (params.rem == 0)
return GncNumeric(params.num, new_denom);
return GncNumeric(round(params.num, params.den,
params.rem, RT2T<RT>()), new_denom);
}
/**
* Return a string representation of the GncNumeric. See operator<< for
* details.
*/
std::string to_string() const noexcept;
/**
* Convert the numeric to have a power-of-10 denominator if possible without
* rounding. Throws a std::rane_error on failure; the message will explain
* the details.
*
* @param max_places exponent of the largest permissible denominator.
* @return A GncNumeric value with the new denominator.
*/
GncNumeric to_decimal(unsigned int max_places=17) const;
/**
* \defgroup gnc_numeric_mutators
*
* These are the standard mutating operators. They use GncRational's
* operators and then call the GncRational constructor, which will silently
* round half-down.
*
* @{
*/
void operator+=(GncNumeric b);
void operator-=(GncNumeric b);
void operator*=(GncNumeric b);
void operator/=(GncNumeric b);
/* @} */
/** Compare function
*
* @param b GncNumeric or int to compare to.
* @return -1 if this < b, 0 if ==, 1 if this > b.
*/
int cmp(GncNumeric b);
private:
struct round_param
{
int64_t num;
int64_t den;
int64_t rem;
};
/* Calculates the denominator required to convert to figs sigfigs. */
int64_t sigfigs_denom(unsigned figs) const noexcept;
/* Calculates a round_param struct to pass to a rounding function that will
* finish computing a GncNumeric with the new denominator.
*/
round_param prepare_conversion(int64_t new_denom) const;
int64_t m_num;
int64_t m_den;
};
/**
* \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
* half-down to obtain int64_t members.
*
* These operators can throw std::overflow_error, std::underflow_error, or
* std::invalid argument as indicated in the class GncNumeric documentation.
*
* \param a The right-side operand
* \param b The left-side operand
* \return A new GncNumeric computed from the sum.
*/
GncNumeric operator+(GncNumeric a, GncNumeric b);
GncNumeric operator-(GncNumeric a, GncNumeric b);
GncNumeric operator*(GncNumeric a, GncNumeric b);
GncNumeric operator/(GncNumeric a, GncNumeric b);
/**
* 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
* decimal if the denominator is a multiple of 10, otherwise as
* "numerator/denominator".
*/
/* Implementation adapted from "The C++ Standard Library, Second Edition" by
* Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
*/
template <typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, GncNumeric n)
{
std::basic_ostringstream<charT, traits> ss;
ss.copyfmt(s);
ss.width(0);
if (n.denom() == 1)
ss << n.num();
else if (n.denom() % 10 == 0)
ss << n.num() / n.denom() << "."
<< (n.num() > 0 ? n.num() : -n.num()) % n.denom();
else
ss << n.num() << "/" << n.denom();
s << ss.str();
return s;
}
/**
* std::stream input operator.
*
* Doesn't do any special space handling, spaces in the input stream will
* delimit elements. The result will be that if a number is presented as "123 /
* 456", the resulting GncNumeric will be 123/1 and the rest will go to the next
* parameter in the stream call. The GncNumeric will be constructed with the
* string constructor with autorounding. It will throw in the event of any
* errors noted in the string constructor documentation.
*/
/* Implementation adapted from "The C++ Standard Library, Second Edition" by
* Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
*/
template <typename charT, typename traits>
std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& s, GncNumeric& n)
{
std::string instr;
s >> instr;
if (s)
n = GncNumeric(instr, true);
return s;
}
/**
* @return -1 if a < b, 0 if a == b, 1 if a > b.
*/
int cmp(GncNumeric a, GncNumeric 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);
/** @} */
/**
* Convenience function to quickly return 10**digits.
* \param digits The desired exponent. Maximum value is 17.
* \return 10**digits
*/
int64_t powten(int64_t digits);
#endif // __GNC_NUMERIC_HPP__

View File

@@ -22,21 +22,7 @@
#include <sstream>
#include "gnc-rational.hpp"
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000, 10000000000,
100000000000, 1000000000000, 10000000000000,
100000000000000, 10000000000000000,
100000000000000000, 1000000000000000000};
static const int POWTEN_OVERFLOW {-5};
static inline gint64
powten (int exp)
{
if (exp > 18 || exp < -18)
return POWTEN_OVERFLOW;
return exp < 0 ? -pten[-exp] : pten[exp];
}
#include "gnc-numeric.hpp"
GncRational::GncRational (gnc_numeric n) noexcept :
m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK}

View File

@@ -27,28 +27,8 @@
#include "gnc-int128.hpp"
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,
};
enum class RoundType;
enum class DenomType;
/** @ingroup QOF
* @brief Rational number class using GncInt128 for the numerator and denominator.

View File

@@ -92,6 +92,13 @@ IF (NOT WIN32)
GNC_ADD_TEST(test-gnc-rational "${test_gnc_rational_SOURCES}"
gtest_qof_INCLUDES gtest_qof_LIBS)
SET(test_gnc_numeric_SOURCES
${MODULEPATH}/gnc-numeric.cpp
gtest-gnc-numeric.cpp
${GTEST_SRC})
GNC_ADD_TEST(test-gnc-numeric "${test_gnc_numeric_SOURCES}"
gtest_qof_INCLUDES gtest_qof_LIBS)
SET(test_gnc_timezone_SOURCES
${MODULEPATH}/gnc-timezone.cpp
gtest-gnc-timezone.cpp

View File

@@ -132,15 +132,42 @@ check_PROGRAMS += test-gnc-int128
test_gnc_rational_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
gtest-gnc-rational.cpp
test_gnc_rational_CPPFLAGS = -I${GTEST_HEADERS}
test_gnc_rational_LDADD = ${GTEST_LIBS}
test_gnc_rational_CPPFLAGS = \
-I${GTEST_HEADERS} \
${GLIB_CFLAGS}
test_gnc_rational_LDADD = \
${GTEST_LIBS} \
${GLIB_LIBS}
if !GOOGLE_TEST_LIBS
nodist_test_gnc_rational_SOURCES = \
${GTEST_SRC}/src/gtest_main.cc
endif
check_PROGRAMS += test-gnc-rational
check_PROGRAMS += test-gnc-int128
test_gnc_numeric_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
gtest-gnc-numeric.cpp
test_gnc_numeric_CPPFLAGS = \
-I${GTEST_HEADERS} \
${GLIB_CFLAGS}
test_gnc_numeric_LDADD = \
${GTEST_LIBS} \
${GLIB_LIBS}
if !GOOGLE_TEST_LIBS
nodist_test_gnc_numeric_SOURCES = \
${GTEST_SRC}/src/gtest_main.cc
endif
check_PROGRAMS += test-gnc-numeric
test_gnc_timezone_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-timezone.cpp \

View File

@@ -0,0 +1,507 @@
/********************************************************************
* gtest-gnc-numeric.cpp -- unit tests for the GncNumeric class *
* Copyright 2017 John Ralls <jralls@ceridwen.us> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#include <gtest/gtest.h>
#include "../gnc-numeric.hpp"
#include "../gnc-rational.hpp"
TEST(gncnumeric_constructors, test_default_constructor)
{
GncNumeric value;
EXPECT_EQ(value.num(), 0);
EXPECT_EQ(value.denom(), 1);
}
TEST(gncnumeric_constructors, test_gnc_rational_constructor)
{
GncInt128 num128(INT64_C(123456789), INT64_C(567894392130486208));
GncInt128 den128(INT64_C(456789123), INT64_C(543210987651234567));
GncRational in (num128, den128);
GncNumeric out;
ASSERT_NO_THROW(out = GncNumeric(in));
EXPECT_EQ(INT64_C(1246404345742679464), out.num());
EXPECT_EQ(INT64_C(4611686019021985017), out.denom());
}
TEST(gncnumeric_constructors, test_gnc_numeric_constructor)
{
gnc_numeric input = gnc_numeric_create(123, 456);
GncNumeric value(input);
EXPECT_EQ(input.num, value.num());
EXPECT_EQ(input.denom, value.denom());
input = gnc_numeric_create(123, 0);
ASSERT_THROW(GncNumeric throw_val(input), std::invalid_argument);
}
TEST(gncnumeric_constructors, test_int64_constructor)
{
int64_t num(123), denom(456);
GncNumeric value(num, denom);
EXPECT_EQ(123, value.num());
EXPECT_EQ(456, value.denom());
denom = INT64_C(0);
ASSERT_THROW(GncNumeric throw_val(num, denom), std::invalid_argument);
}
TEST(gncnumeric_constructors, test_implicit_int_constructor)
{
int num(123), denom(456);
GncNumeric value(num, denom);
EXPECT_EQ(123, value.num());
EXPECT_EQ(456, value.denom());
EXPECT_THROW(GncNumeric throw_val(123, 0), std::invalid_argument);
}
TEST(gncnumeric_constructors, test_double_constructor)
{
GncNumeric a(123456.789000);
EXPECT_EQ(123456789, a.num());
EXPECT_EQ(1000, a.denom());
GncNumeric b(-123456.789000);
EXPECT_EQ(-123456789, b.num());
EXPECT_EQ(1000, b.denom());
GncNumeric c(static_cast<double>(0.0000000123456789000));
EXPECT_EQ(123456789, c.num());
EXPECT_EQ(INT64_C(10000000000000000), c.denom());
GncNumeric d(1.23456789e-19);
EXPECT_EQ(0, d.num());
EXPECT_EQ(1, d.denom());
EXPECT_THROW(GncNumeric e(123456789e18), std::invalid_argument);
auto f = GncNumeric(1.1234567890123).convert_sigfigs<RoundType::bankers>(6);
EXPECT_EQ(112346, f.num());
EXPECT_EQ(100000, f.denom());
auto g = GncNumeric(0.011234567890123).convert_sigfigs<RoundType::bankers>(6);
EXPECT_EQ(112346, g.num());
EXPECT_EQ(10000000, g.denom());
auto h = GncNumeric(1123.4567890123).convert_sigfigs<RoundType::bankers>(6);
EXPECT_EQ(112346, h.num());
EXPECT_EQ(100, h.denom());
auto i = GncNumeric(1.1234567890123e-5).convert_sigfigs<RoundType::bankers>(6);
EXPECT_EQ(112346, i.num());
EXPECT_EQ(10000000000, i.denom());
}
TEST(gncnumeric_constructors, test_string_constructor)
{
EXPECT_NO_THROW(GncNumeric simple_num("123/456"));
GncNumeric simple_num("123/456");
EXPECT_EQ(123, simple_num.num());
EXPECT_EQ(456, simple_num.denom());
EXPECT_NO_THROW(GncNumeric neg_simple_num("-123/456"));
GncNumeric neg_simple_num("-123/456");
EXPECT_EQ(-123, neg_simple_num.num());
EXPECT_EQ(456, neg_simple_num.denom());
ASSERT_NO_THROW(GncNumeric with_spaces("123 / 456"));
GncNumeric with_spaces("123 / 456");
EXPECT_EQ(123, with_spaces.num());
EXPECT_EQ(456, with_spaces.denom());
ASSERT_NO_THROW(GncNumeric neg_with_spaces("-123 / 456"));
GncNumeric neg_with_spaces("-123 / 456");
EXPECT_EQ(-123, neg_with_spaces.num());
EXPECT_EQ(456, neg_with_spaces.denom());
ASSERT_NO_THROW(GncNumeric simple_int("123456"));
GncNumeric simple_int("123456");
EXPECT_EQ(123456, simple_int.num());
EXPECT_EQ(1, simple_int.denom());
ASSERT_NO_THROW(GncNumeric neg_simple_int("-123456"));
GncNumeric neg_simple_int("-123456");
EXPECT_EQ(-123456, neg_simple_int.num());
EXPECT_EQ(1, neg_simple_int.denom());
ASSERT_NO_THROW(GncNumeric simple_hex("0x1e240"));
GncNumeric simple_hex("0x1e240");
EXPECT_EQ(123456, simple_int.num());
EXPECT_EQ(1, simple_int.denom());
ASSERT_NO_THROW(GncNumeric simple_decimal("123.456"));
GncNumeric simple_decimal("123.456");
EXPECT_EQ(123456, simple_decimal.num());
EXPECT_EQ(1000, simple_decimal.denom());
ASSERT_NO_THROW(GncNumeric neg_simple_decimal("-123.456"));
GncNumeric neg_simple_decimal("-123.456");
EXPECT_EQ(-123456, neg_simple_decimal.num());
EXPECT_EQ(1000, neg_simple_decimal.denom());
ASSERT_NO_THROW(GncNumeric continental_decimal("123,456"));
GncNumeric continental_decimal("123,456");
EXPECT_EQ(123456, continental_decimal.num());
EXPECT_EQ(1000, continental_decimal.denom());
ASSERT_NO_THROW(GncNumeric neg_continental_decimal("-123,456"));
GncNumeric neg_continental_decimal("-123,456");
EXPECT_EQ(-123456, neg_continental_decimal.num());
EXPECT_EQ(1000, neg_continental_decimal.denom());
ASSERT_NO_THROW(GncNumeric hex_rational("0x1e240/0x1c8"));
GncNumeric hex_rational("0x1e240/0x1c8");
EXPECT_EQ(123456, hex_rational.num());
EXPECT_EQ(456, hex_rational.denom());
ASSERT_NO_THROW(GncNumeric hex_over_num("0x1e240/456"));
GncNumeric hex_over_num("0x1e240/456");
EXPECT_EQ(123456, hex_over_num.num());
EXPECT_EQ(456, hex_over_num.denom());
ASSERT_NO_THROW(GncNumeric num_over_hex("123456/0x1c8"));
GncNumeric num_over_hex("123456/0x1c8");
EXPECT_EQ(123456, num_over_hex.num());
EXPECT_EQ(456, num_over_hex.denom());
ASSERT_NO_THROW(GncNumeric embedded("The number is 123456/456"));
GncNumeric embedded("The number is 123456/456");
EXPECT_EQ(123456, embedded.num());
EXPECT_EQ(456, embedded.denom());
ASSERT_NO_THROW(GncNumeric embedded("The number is -123456/456"));
GncNumeric neg_embedded("The number is -123456/456");
EXPECT_EQ(-123456, neg_embedded.num());
EXPECT_EQ(456, neg_embedded.denom());
EXPECT_THROW(GncNumeric throw_zero_denom("123/0"), std::invalid_argument);
EXPECT_THROW(GncNumeric overflow("12345678987654321.123456"),
std::overflow_error);
EXPECT_NO_THROW(GncNumeric overflow("12345678987654321.123456", true));
GncNumeric overflow("12345678987654321.123456", true);
EXPECT_EQ(6028163568190586486, overflow.num());
EXPECT_EQ(488, overflow.denom());
EXPECT_THROW(GncNumeric auto_round("12345678987654321234/256", true),
std::out_of_range);
EXPECT_THROW(GncNumeric bad_string("Four score and seven"),
std::invalid_argument);
}
TEST(gncnumeric_output, string_output)
{
GncNumeric simple_int(123456, 1);
EXPECT_EQ("123456", simple_int.to_string());
GncNumeric neg_simple_int(-123456, 1);
EXPECT_EQ("-123456", neg_simple_int.to_string());
GncNumeric decimal_string(123456, 1000);
EXPECT_EQ("123.456", decimal_string.to_string());
GncNumeric neg_decimal_string(-123456, 1000);
EXPECT_EQ("-123.456", neg_decimal_string.to_string());
GncNumeric rational_string(123, 456);
EXPECT_EQ("123/456", rational_string.to_string());
GncNumeric neg_rational_string(-123, 456);
EXPECT_EQ("-123/456", neg_rational_string.to_string());
}
TEST(gncnumeric_stream, output_stream)
{
std::ostringstream output;
GncNumeric simple_int(INT64_C(123456));
output << simple_int;
EXPECT_EQ("123456", output.str());
output.str("");
GncNumeric decimal_string(123456, 1000);
output << decimal_string;
EXPECT_EQ("123.456", output.str());
output.str("");
GncNumeric rational_string(123, 456);
output << rational_string;
EXPECT_EQ("123/456", output.str());
}
TEST(gncnumeric_stream, input_stream)
{
std::istringstream input("123456");
GncNumeric numeric;
EXPECT_NO_THROW(input >> numeric);
EXPECT_EQ(123456, numeric.num());
EXPECT_EQ(1, numeric.denom());
input.clear();
input.str("123456/456");
EXPECT_NO_THROW(input >> numeric);
EXPECT_EQ(123456, numeric.num());
EXPECT_EQ(456, numeric.denom());
input.clear();
input.str("123456 / 456");
EXPECT_NO_THROW(input >> numeric);
EXPECT_EQ(123456, numeric.num());
EXPECT_EQ(1, numeric.denom());
input.clear();
input.str("0x1e240/0x1c8");
EXPECT_NO_THROW(input >> std::hex >> numeric);
EXPECT_EQ(123456, numeric.num());
EXPECT_EQ(456, numeric.denom());
input.clear();
input.str("0x1e240/456");
EXPECT_NO_THROW(input >> numeric);
EXPECT_EQ(123456, numeric.num());
EXPECT_EQ(456, numeric.denom());
input.clear();
input.str("123456/0x1c8");
EXPECT_NO_THROW(input >> numeric);
EXPECT_EQ(123456, numeric.num());
EXPECT_EQ(456, numeric.denom());
input.clear();
input.str("123/0");
EXPECT_THROW(input >> std::dec >> numeric, std::invalid_argument);
input.clear();
input.str("12345678987654321.123456");
EXPECT_NO_THROW(input >> numeric);
EXPECT_EQ(6028163568190586486, numeric.num());
EXPECT_EQ(488, numeric.denom());
input.clear();
input.str("12345678987654321234/256");
EXPECT_THROW(input >> numeric, std::out_of_range);
input.clear();
input.str("Four score and seven");
EXPECT_THROW(input >> numeric, std::invalid_argument);
}
TEST(gncnumeric_operators, gnc_numeric_conversion)
{
GncNumeric a(123456789, 9876);
gnc_numeric b = static_cast<decltype(b)>(a);
EXPECT_EQ(123456789, b.num);
EXPECT_EQ(9876, b.denom);
}
TEST(gncnumeric_operators, double_conversion)
{
GncNumeric a(123456789, 9876);
double b = static_cast<decltype(b)>(a);
EXPECT_EQ(12500.687424058324, b);
}
TEST(gncnumeric_operators, test_addition)
{
GncNumeric a(123456789987654321, 1000000000);
GncNumeric b(65432198765432198, 100000000);
GncNumeric c = a + b;
EXPECT_EQ (777778777641976301, c.num());
EXPECT_EQ (1000000000, c.denom());
a += b;
EXPECT_EQ (777778777641976301, a.num());
EXPECT_EQ (1000000000, a.denom());
}
TEST(gncnumeric_operators, test_subtraction)
{
GncNumeric a(123456789987654321, 1000000000);
GncNumeric b(65432198765432198, 100000000);
GncNumeric c = a - b;
EXPECT_EQ (-530865197666667659, c.num());
EXPECT_EQ (1000000000, c.denom());
c = b - a;
EXPECT_EQ (530865197666667659, c.num());
EXPECT_EQ (1000000000, c.denom());
a -= b;
EXPECT_EQ (-530865197666667659, a.num());
EXPECT_EQ (1000000000, a.denom());
GncNumeric d(2, 6), e(1, 4);
c = d - e;
EXPECT_EQ(1, c.num());
EXPECT_EQ(12, c.denom());
}
TEST(gncnumeric_operators, test_multiplication)
{
GncNumeric a(123456789987654321, 1000000000);
GncNumeric b(65432198765432198, 100000000);
GncNumeric c = a * b;
EXPECT_EQ (4604488056206217807, c.num());
EXPECT_EQ (57, c.denom());
a *= b;
EXPECT_EQ (4604488056206217807, a.num());
EXPECT_EQ (57, a.denom());
GncNumeric d(215815575996, 269275978715);
GncNumeric e(1002837599929, 1);
GncNumeric f, g;
EXPECT_NO_THROW(f = d * e);
EXPECT_NO_THROW(g = f.convert<RoundType::half_up>(1));
}
TEST(gncnumeric_operators, test_division)
{
GncNumeric a(123456789987654321, 1000000000);
GncNumeric b(65432198765432198, 100000000);
GncNumeric c = a / b;
EXPECT_EQ (123456789987654321, c.num());
EXPECT_EQ (654321987654321980, c.denom());
a /= b;
EXPECT_EQ (123456789987654321, a.num());
EXPECT_EQ (654321987654321980, a.denom());
}
TEST(gncnumeric_functions, test_cmp)
{
GncNumeric a(123456789, 9876), b(567894321, 6543);
auto c = a;
EXPECT_EQ(0, a.cmp(c));
EXPECT_EQ(-1, a.cmp(b));
EXPECT_EQ(1, b.cmp(a));
EXPECT_EQ(-1, b.cmp(INT64_C(88888)));
EXPECT_EQ(1, a.cmp(INT64_C(12500)));
}
TEST(gncnumeric_functions, test_invert)
{
GncNumeric a(123456789, 9876), b, c;
ASSERT_NO_THROW(c = b.inv());
EXPECT_EQ(0, c.num());
EXPECT_EQ(1, c.denom());
ASSERT_NO_THROW(b = a.inv());
EXPECT_EQ(9876, b.num());
EXPECT_EQ(123456789, b.denom());
}
TEST(gncnumeric_functions, test_reduce)
{
GncNumeric a(123456789, 5202504), b;
ASSERT_NO_THROW(b = a.reduce());
EXPECT_EQ(3607, b.num());
EXPECT_EQ(152, b.denom());
}
TEST(gncnumeric_functions, test_convert)
{
GncNumeric a(12345678, 456), b(-12345678, 456), c;
ASSERT_NO_THROW(c = a.convert<RoundType::never>(456));
EXPECT_EQ(12345678, c.num());
EXPECT_EQ(456, c.denom());
EXPECT_THROW(c = a.convert<RoundType::never>(128), std::domain_error);
ASSERT_NO_THROW(c = a.convert<RoundType::floor>(128));
EXPECT_EQ(3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::floor>(128));
EXPECT_EQ(-3465452, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::ceiling>(128));
EXPECT_EQ(3465454, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::ceiling>(128));
EXPECT_EQ(-3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::truncate>(128));
EXPECT_EQ(3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::truncate>(128));
EXPECT_EQ(-3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::promote>(128));
EXPECT_EQ(3465454, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::promote>(128));
EXPECT_EQ(-3465454, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::half_down>(128));
EXPECT_EQ(3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::half_down>(114));
EXPECT_EQ(3086419, c.num());
EXPECT_EQ(114, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::half_down>(118));
EXPECT_EQ(3194715, c.num());
EXPECT_EQ(118, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::half_down>(128));
EXPECT_EQ(-3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::half_down>(114));
EXPECT_EQ(-3086419, c.num());
EXPECT_EQ(114, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::half_down>(121));
EXPECT_EQ(-3275936, c.num());
EXPECT_EQ(121, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::half_up>(128));
EXPECT_EQ(3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::half_up>(114));
EXPECT_EQ(3086420, c.num());
EXPECT_EQ(114, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::half_up>(118));
EXPECT_EQ(3194715, c.num());
EXPECT_EQ(118, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::half_up>(128));
EXPECT_EQ(-3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::half_up>(114));
EXPECT_EQ(-3086420, c.num());
EXPECT_EQ(114, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::half_up>(121));
EXPECT_EQ(-3275936, c.num());
EXPECT_EQ(121, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::bankers>(128));
EXPECT_EQ(3465453, c.num());
EXPECT_EQ(128, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::bankers>(114));
EXPECT_EQ(3086420, c.num());
EXPECT_EQ(114, c.denom());
ASSERT_NO_THROW(c = a.convert<RoundType::bankers>(118));
EXPECT_EQ(3194715, c.num());
EXPECT_EQ(118, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::bankers>(127));
EXPECT_EQ(-3438380, c.num());
EXPECT_EQ(127, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::bankers>(114));
EXPECT_EQ(-3086420, c.num());
EXPECT_EQ(114, c.denom());
ASSERT_NO_THROW(c = b.convert<RoundType::bankers>(121));
EXPECT_EQ(-3275936, c.num());
EXPECT_EQ(121, c.denom());
GncNumeric o(123456789123456789, 128);
EXPECT_THROW(c = o.convert<RoundType::bankers>(54321098),
std::overflow_error);
GncNumeric d(7, 16);
ASSERT_NO_THROW(c = d.convert<RoundType::floor>(100));
EXPECT_EQ(43, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = d.convert<RoundType::ceiling>(100));
EXPECT_EQ(44, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = d.convert<RoundType::truncate>(100));
EXPECT_EQ(43, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = d.convert<RoundType::bankers>(100));
EXPECT_EQ(44, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = GncNumeric(1511, 1000).convert<RoundType::bankers>(100));
EXPECT_EQ(151, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = GncNumeric(1516, 1000).convert<RoundType::bankers>(100));
EXPECT_EQ(152, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = GncNumeric(1515, 1000).convert<RoundType::bankers>(100));
EXPECT_EQ(152, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = GncNumeric(1525, 1000).convert<RoundType::bankers>(100));
EXPECT_EQ(152, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = GncNumeric(1535, 1000).convert<RoundType::bankers>(100));
EXPECT_EQ(154, c.num());
EXPECT_EQ(100, c.denom());
ASSERT_NO_THROW(c = GncNumeric(1545, 1000).convert<RoundType::bankers>(100));
EXPECT_EQ(154, c.num());
EXPECT_EQ(100, c.denom());
}
TEST(gnc_numeric_functions, test_conversion_to_decimal)
{
GncNumeric a(123456789, 1000), r;
EXPECT_NO_THROW(r = a.to_decimal());
EXPECT_EQ(123456789, r.num());
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);
GncNumeric c(123456789, 450);
EXPECT_NO_THROW(r = c.to_decimal());
EXPECT_EQ(27434842, r.num());
EXPECT_EQ(100, r.denom());
}

View File

@@ -1,47 +0,0 @@
/********************************************************************
* test_qofbackend.c: GLib g_test test suite for qofbackend. *
* Copyright 2011 John Ralls <jralls@ceridwen.us> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#include "config.h"
#include <string.h>
#include <glib.h>
#include <qof.h>
#include <unittest-support.h>
static const gchar *suitename = "/qof/gnc_numeric";
static void
test_gnc_numeric_add (void)
{
gnc_numeric a = { 123456789987654321, 1000000000 };
gnc_numeric b = { 65432198765432198, 100000000 };
gnc_numeric goal_ab = { 777778777641976301, 1000000000 };
gnc_numeric result;
result = gnc_numeric_add (a, b, GNC_DENOM_AUTO,
GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER);
g_assert (gnc_numeric_equal (result, goal_ab));
}
void
test_suite_gnc_numeric ( void )
{
GNC_TEST_ADD_FUNC( suitename, "gnc-numeric add", test_gnc_numeric_add );
}