mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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:
@@ -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
|
||||
|
||||
473
src/libqof/qof/gnc-numeric.hpp
Normal file
473
src/libqof/qof/gnc-numeric.hpp
Normal 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__
|
||||
@@ -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}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
507
src/libqof/qof/test/gtest-gnc-numeric.cpp
Normal file
507
src/libqof/qof/test/gtest-gnc-numeric.cpp
Normal 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());
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
Reference in New Issue
Block a user