mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Reimplement gnc_numeric in terms of GncNumeric instead of GncRational.
Except when how has DenomType::exact; that triggers direct use of GncRational and direct rounding to the specified denominator.
This commit is contained in:
parent
c3d22c429f
commit
ff7e6a37d5
@ -1378,7 +1378,8 @@ PrintAmountInternal(char *buf, gnc_numeric val, const GNCPrintAmountInfo *info)
|
||||
{
|
||||
rounding.num = 5; /* Limit the denom to 10^13 ~= 2^44, leaving max at ~524288 */
|
||||
rounding.denom = pow(10, max_dp + 1);
|
||||
val = gnc_numeric_add(val, rounding, val.denom, GNC_HOW_RND_TRUNC);
|
||||
val = gnc_numeric_add(val, rounding, val.denom,
|
||||
GNC_HOW_DENOM_EXACT | GNC_HOW_RND_TRUNC);
|
||||
|
||||
if (gnc_numeric_check(val))
|
||||
{
|
||||
|
@ -39,22 +39,12 @@ test_num_print_info (gnc_numeric n, GNCPrintAmountInfo print_info, int line)
|
||||
const char *s;
|
||||
gboolean ok, print_ok;
|
||||
|
||||
auto msg = "[PrintAmountInternal()] Bad numeric from rounding: GNC_ERROR_OVERFLOW.";
|
||||
auto log_domain = "gnc.gui";
|
||||
auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING);
|
||||
auto check = test_error_struct_new (log_domain, loglevel, msg);
|
||||
|
||||
/* Throws overflows during rounding step in xaccPrintAmount when the "fraction" is high. See bug 665707. */
|
||||
auto hdlr = g_log_set_handler (log_domain, loglevel,
|
||||
(GLogFunc)test_checked_handler, &check);
|
||||
s = xaccPrintAmount (n, print_info);
|
||||
print_ok = (s && s[0] != '\0');
|
||||
if (!print_ok)
|
||||
return;
|
||||
|
||||
ok = xaccParseAmount (s, print_info.monetary, &n_parsed, NULL);
|
||||
g_log_remove_handler (log_domain, hdlr);
|
||||
|
||||
|
||||
do_test_args (ok, "parsing failure", __FILE__, __LINE__,
|
||||
"num: %s, string %s (line %d)", gnc_numeric_to_string (n), s, line);
|
||||
@ -64,8 +54,6 @@ test_num_print_info (gnc_numeric n, GNCPrintAmountInfo print_info, int line)
|
||||
"start: %s, string %s, finish: %s (line %d)",
|
||||
gnc_numeric_to_string (n), s,
|
||||
gnc_numeric_to_string (n_parsed), line);
|
||||
test_error_struct_free (check);
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
@ -90,7 +78,8 @@ test_num (gnc_numeric n)
|
||||
print_info.force_fit = 0;
|
||||
print_info.round = 0;
|
||||
|
||||
n1 = gnc_numeric_convert (n, fraction, GNC_HOW_RND_ROUND_HALF_UP);
|
||||
n1 = gnc_numeric_convert (n, fraction, GNC_HOW_DENOM_EXACT |
|
||||
GNC_HOW_RND_ROUND_HALF_UP);
|
||||
if (gnc_numeric_check(n1))
|
||||
{
|
||||
do_test_args((gnc_numeric_check(n1) == GNC_ERROR_OVERFLOW),
|
||||
@ -133,6 +122,20 @@ static void
|
||||
run_tests (void)
|
||||
{
|
||||
int i;
|
||||
auto msg1 = "[gnc_numeric_mul()] Cannot be represented as a GncNumeric. Its integer value is too large.\n";
|
||||
auto msg2 = "[gnc_numeric_mul()] Overflow during rounding.";
|
||||
auto msg3 = "[convert()] Value too large to represent as int64_t";
|
||||
const char* log_domain = "qof";
|
||||
auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING);
|
||||
auto check1 = test_error_struct_new (log_domain, loglevel, msg1);
|
||||
auto check2 = test_error_struct_new (log_domain, loglevel, msg2);
|
||||
auto check3 = test_error_struct_new (log_domain, loglevel, msg3);
|
||||
test_add_error(check1);
|
||||
test_add_error(check2);
|
||||
test_add_error(check3);
|
||||
/* Throws overflows during rounding step in xaccPrintAmount when the "fraction" is high. See bug 665707. */
|
||||
auto hdlr = g_log_set_handler (log_domain, loglevel,
|
||||
(GLogFunc)test_list_handler, nullptr);
|
||||
|
||||
for (i = 0; i < 50; i++)
|
||||
{
|
||||
@ -147,10 +150,13 @@ run_tests (void)
|
||||
IS_VALID_NUM(n1, n);
|
||||
test_num (n);
|
||||
|
||||
n1 = gnc_numeric_mul (n, n, n.denom, GNC_HOW_RND_ROUND_HALF_UP);
|
||||
n1 = gnc_numeric_mul (n, n, n.denom,
|
||||
GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
|
||||
IS_VALID_NUM(n1, n);
|
||||
test_num (n);
|
||||
}
|
||||
g_log_remove_handler (log_domain, hdlr);
|
||||
test_clear_error_list();
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -708,9 +708,9 @@ gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
|
||||
return -1;
|
||||
}
|
||||
|
||||
GncRational an (a), bn (b);
|
||||
GncNumeric an (a), bn (b);
|
||||
|
||||
return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
|
||||
return an.cmp(bn);
|
||||
}
|
||||
|
||||
|
||||
@ -768,6 +768,17 @@ gnc_numeric_same(gnc_numeric a, gnc_numeric b, gint64 denom,
|
||||
return(gnc_numeric_equal(aconv, bconv));
|
||||
}
|
||||
|
||||
static int64_t
|
||||
denom_lcd(gnc_numeric a, gnc_numeric b, int64_t denom, int how)
|
||||
{
|
||||
if (denom == GNC_DENOM_AUTO &&
|
||||
(how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD)
|
||||
{
|
||||
GncInt128 ad(a.denom), bd(b.denom);
|
||||
denom = static_cast<int64_t>(ad.lcm(bd));
|
||||
}
|
||||
return denom;
|
||||
}
|
||||
|
||||
/* *******************************************************************
|
||||
* gnc_numeric_add
|
||||
@ -781,18 +792,39 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
|
||||
{
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
denom = denom_lcd(a, b, denom, how);
|
||||
try
|
||||
{
|
||||
return static_cast<gnc_numeric>(an.add(bn, new_denom));
|
||||
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
|
||||
{
|
||||
GncNumeric an (a), bn (b);
|
||||
auto sum = an + bn;
|
||||
return convert(sum, denom, how);
|
||||
}
|
||||
GncRational ar(a), br(b);
|
||||
auto sum = ar + br;
|
||||
sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
|
||||
if (sum.m_error)
|
||||
return gnc_numeric_error(sum.m_error);
|
||||
if (sum.m_num.isBig() || sum.m_den.isBig() ||
|
||||
sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
|
||||
sum.m_num.isNan() || sum.m_den.isNan())
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
return GncNumeric(sum);
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
}
|
||||
@ -810,10 +842,41 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
|
||||
{
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
|
||||
nb = b;
|
||||
nb.num = -nb.num;
|
||||
return gnc_numeric_add (a, nb, denom, how);
|
||||
denom = denom_lcd(a, b, denom, how);
|
||||
try
|
||||
{
|
||||
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
|
||||
{
|
||||
GncNumeric an (a), bn (b);
|
||||
auto sum = an - bn;
|
||||
return convert(sum, denom, how);
|
||||
}
|
||||
GncRational ar(a), br(b);
|
||||
auto sum = ar - br;
|
||||
sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
|
||||
if (sum.m_error)
|
||||
return gnc_numeric_error(sum.m_error);
|
||||
if (sum.m_num.isBig() || sum.m_den.isBig() ||
|
||||
sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
|
||||
sum.m_num.isNan() || sum.m_den.isNan())
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
return GncNumeric(sum);
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
}
|
||||
|
||||
/* *******************************************************************
|
||||
@ -828,20 +891,41 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
|
||||
{
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
|
||||
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);
|
||||
denom = denom_lcd(a, b, denom, how);
|
||||
try
|
||||
{
|
||||
return static_cast<gnc_numeric>(an.mul(bn, new_denom));
|
||||
}
|
||||
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
|
||||
{
|
||||
GncNumeric an (a), bn (b);
|
||||
auto prod = an * bn;
|
||||
return convert(prod, denom, how);
|
||||
}
|
||||
GncRational ar(a), br(b);
|
||||
auto prod = ar * br;
|
||||
prod.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
|
||||
if (prod.m_error)
|
||||
return gnc_numeric_error(prod.m_error);
|
||||
if (prod.m_num.isBig() || prod.m_den.isBig() ||
|
||||
prod.m_num.isOverflow() || prod.m_den.isOverflow() ||
|
||||
prod.m_num.isNan() || prod.m_den.isNan())
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
return GncNumeric(prod);
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -857,17 +941,39 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
|
||||
{
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
|
||||
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);
|
||||
denom = denom_lcd(a, b, denom, how);
|
||||
try
|
||||
{
|
||||
return static_cast<gnc_numeric>(an.div(bn, new_denom));
|
||||
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
|
||||
{
|
||||
GncNumeric an (a), bn (b);
|
||||
auto quot = an / bn;
|
||||
return convert(quot, denom, how);
|
||||
}
|
||||
GncRational ar(a), br(b);
|
||||
auto quot = ar / br;
|
||||
quot.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
|
||||
if (quot.m_error)
|
||||
return gnc_numeric_error(quot.m_error);
|
||||
if (quot.m_num.isBig() || quot.m_den.isBig() ||
|
||||
quot.m_num.isOverflow() || quot.m_den.isOverflow() ||
|
||||
quot.m_num.isNan() || quot.m_den.isNan())
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
return GncNumeric(quot);
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err) //Divide by zero
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
}
|
||||
@ -910,18 +1016,7 @@ gnc_numeric_abs(gnc_numeric a)
|
||||
gnc_numeric
|
||||
gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
|
||||
{
|
||||
GncRational a (in), b (gnc_numeric_zero());
|
||||
GncDenom d (a, b, denom, how);
|
||||
try
|
||||
{
|
||||
d.reduce(a);
|
||||
a.round (d.get(), d.m_round);
|
||||
return static_cast<gnc_numeric>(a);
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
return convert(GncNumeric(in), denom, how);
|
||||
}
|
||||
|
||||
|
||||
@ -941,18 +1036,27 @@ gnc_numeric_reduce(gnc_numeric in)
|
||||
|
||||
if (in.denom < 0) /* Negative denoms multiply num, can't be reduced. */
|
||||
return in;
|
||||
GncRational a (in), b (gnc_numeric_zero());
|
||||
GncDenom d (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
|
||||
try
|
||||
{
|
||||
d.reduce(a);
|
||||
a.round (d.get(), d.m_round);
|
||||
return static_cast<gnc_numeric>(a);
|
||||
GncNumeric an (in);
|
||||
return static_cast<gnc_numeric>(an.reduce());
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -968,87 +1072,48 @@ gnc_numeric_reduce(gnc_numeric in)
|
||||
gboolean
|
||||
gnc_numeric_to_decimal(gnc_numeric *a, guint8 *max_decimal_places)
|
||||
{
|
||||
guint8 decimal_places = 0;
|
||||
gnc_numeric converted_val;
|
||||
gint64 fraction;
|
||||
|
||||
g_return_val_if_fail(a, FALSE);
|
||||
|
||||
if (gnc_numeric_check(*a) != GNC_ERROR_OK)
|
||||
return FALSE;
|
||||
|
||||
converted_val = *a;
|
||||
if (converted_val.denom <= 0)
|
||||
int max_places = max_decimal_places == NULL ? 17 : *max_decimal_places;
|
||||
try
|
||||
{
|
||||
converted_val = gnc_numeric_convert(converted_val, 1, GNC_HOW_DENOM_EXACT);
|
||||
if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
|
||||
return FALSE;
|
||||
*a = converted_val;
|
||||
if (max_decimal_places)
|
||||
*max_decimal_places = decimal_places;
|
||||
GncNumeric an (*a);
|
||||
auto bn = an.to_decimal(max_places);
|
||||
*a = static_cast<gnc_numeric>(bn);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Zero is easily converted. */
|
||||
if (converted_val.num == 0)
|
||||
converted_val.denom = 1;
|
||||
|
||||
fraction = converted_val.denom;
|
||||
while (fraction != 1)
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
switch (fraction % 10)
|
||||
{
|
||||
case 0:
|
||||
fraction = fraction / 10;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
converted_val = gnc_numeric_mul(converted_val,
|
||||
gnc_numeric_create(2, 2),
|
||||
GNC_DENOM_AUTO,
|
||||
GNC_HOW_DENOM_EXACT |
|
||||
GNC_HOW_RND_NEVER);
|
||||
if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
|
||||
return FALSE;
|
||||
fraction = fraction / 5;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 4:
|
||||
case 6:
|
||||
case 8:
|
||||
converted_val = gnc_numeric_mul(converted_val,
|
||||
gnc_numeric_create(5, 5),
|
||||
GNC_DENOM_AUTO,
|
||||
GNC_HOW_DENOM_EXACT |
|
||||
GNC_HOW_RND_NEVER);
|
||||
if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
|
||||
return FALSE;
|
||||
fraction = fraction / 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
decimal_places += 1;
|
||||
PWARN("%s", err.what());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (max_decimal_places)
|
||||
*max_decimal_places = decimal_places;
|
||||
|
||||
*a = converted_val;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
gnc_numeric
|
||||
gnc_numeric_invert(gnc_numeric num)
|
||||
{
|
||||
if (num.num == 0)
|
||||
return gnc_numeric_zero();
|
||||
return static_cast<gnc_numeric>(GncRational(num).inv());
|
||||
try
|
||||
{
|
||||
return static_cast<gnc_numeric>(GncNumeric(num).inv());
|
||||
}
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
}
|
||||
|
||||
/* *******************************************************************
|
||||
* double_to_gnc_numeric
|
||||
********************************************************************/
|
||||
@ -1059,72 +1124,26 @@ gnc_numeric_invert(gnc_numeric num)
|
||||
gnc_numeric
|
||||
double_to_gnc_numeric(double in, gint64 denom, gint how)
|
||||
{
|
||||
gnc_numeric out;
|
||||
gint64 int_part = 0;
|
||||
double frac_part;
|
||||
gint64 frac_int = 0;
|
||||
double logval;
|
||||
double sigfigs;
|
||||
|
||||
if (isnan (in) || fabs (in) > 1e18)
|
||||
return gnc_numeric_error (GNC_ERROR_OVERFLOW);
|
||||
|
||||
if ((denom == GNC_DENOM_AUTO) && (how & GNC_HOW_DENOM_SIGFIG))
|
||||
try
|
||||
{
|
||||
if (fabs(in) < 10e-20)
|
||||
{
|
||||
logval = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
logval = log10(fabs(in));
|
||||
logval = ((logval > 0.0) ?
|
||||
(floor(logval) + 1.0) : (ceil(logval)));
|
||||
}
|
||||
sigfigs = GNC_HOW_GET_SIGFIGS(how);
|
||||
if ((denom = powten (sigfigs - logval)) == POWTEN_OVERFLOW)
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
|
||||
how = how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
|
||||
GncNumeric an(in);
|
||||
return convert(an, denom, how);
|
||||
}
|
||||
|
||||
int_part = (gint64)(floor(fabs(in)));
|
||||
frac_part = in - (double)int_part;
|
||||
|
||||
int_part = int_part * denom;
|
||||
frac_part = frac_part * (double)denom;
|
||||
|
||||
switch (how & GNC_NUMERIC_RND_MASK)
|
||||
catch (const std::overflow_error& err)
|
||||
{
|
||||
case GNC_HOW_RND_FLOOR:
|
||||
frac_int = (gint64)floor(frac_part);
|
||||
break;
|
||||
|
||||
case GNC_HOW_RND_CEIL:
|
||||
frac_int = (gint64)ceil(frac_part);
|
||||
break;
|
||||
|
||||
case GNC_HOW_RND_TRUNC:
|
||||
frac_int = (gint64)frac_part;
|
||||
break;
|
||||
|
||||
case GNC_HOW_RND_ROUND:
|
||||
case GNC_HOW_RND_ROUND_HALF_UP:
|
||||
frac_int = (gint64)rint(frac_part);
|
||||
break;
|
||||
|
||||
case GNC_HOW_RND_NEVER:
|
||||
frac_int = (gint64)floor(frac_part);
|
||||
if (frac_part != (double) frac_int)
|
||||
{
|
||||
/* signal an error */
|
||||
}
|
||||
break;
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
}
|
||||
catch (const std::invalid_argument& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
catch (const std::underflow_error& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return gnc_numeric_error(GNC_ERROR_ARG);
|
||||
}
|
||||
|
||||
out.num = int_part + frac_int;
|
||||
out.denom = denom;
|
||||
return out;
|
||||
}
|
||||
|
||||
/* *******************************************************************
|
||||
@ -1191,20 +1210,17 @@ gnc_num_dbg_to_string(gnc_numeric n)
|
||||
gboolean
|
||||
string_to_gnc_numeric(const gchar* str, gnc_numeric *n)
|
||||
{
|
||||
gint64 tmpnum;
|
||||
gint64 tmpdenom;
|
||||
|
||||
if (!str) return FALSE;
|
||||
|
||||
tmpnum = g_ascii_strtoll (str, NULL, 0);
|
||||
str = strchr (str, '/');
|
||||
if (!str) return FALSE;
|
||||
str ++;
|
||||
tmpdenom = g_ascii_strtoll (str, NULL, 0);
|
||||
|
||||
n->num = tmpnum;
|
||||
n->denom = tmpdenom;
|
||||
return TRUE;
|
||||
try
|
||||
{
|
||||
GncNumeric an(str);
|
||||
*n = static_cast<gnc_numeric>(an);
|
||||
return TRUE;
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
PWARN("%s", err.what());
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* *******************************************************************
|
||||
|
@ -245,9 +245,6 @@ typedef enum
|
||||
*/
|
||||
#define GNC_DENOM_AUTO 0
|
||||
|
||||
/** Use the value 1/n as the denominator of the output value. */
|
||||
#define GNC_DENOM_RECIPROCAL( a ) (- ( a ))
|
||||
|
||||
/** @} */
|
||||
|
||||
/** @name Constructors
|
||||
|
@ -47,7 +47,7 @@ GncRational::GncRational (gnc_numeric n) noexcept :
|
||||
|
||||
GncRational::operator gnc_numeric () const noexcept
|
||||
{
|
||||
if (m_num.isOverflow() || m_num.isNan() ||
|
||||
if (m_num.isOverflow() || m_num.isNan() ||
|
||||
m_den.isOverflow() || m_den.isNan())
|
||||
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
|
||||
if (m_error != GNC_ERROR_OK)
|
||||
@ -73,11 +73,14 @@ GncRational::operator-() const noexcept
|
||||
GncRational&
|
||||
GncRational::inv () noexcept
|
||||
{
|
||||
if (m_den < 0)
|
||||
{
|
||||
m_num *= -m_den;
|
||||
m_den = 1;
|
||||
}
|
||||
std::swap(m_num, m_den);
|
||||
|
||||
GncRational b {1, 1};
|
||||
GncDenom d {*this, b, INT64_C(0), GNC_HOW_RND_NEVER };
|
||||
d.reduce(*this);
|
||||
reduce();
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -185,39 +188,6 @@ GncRational::operator/=(GncRational b)
|
||||
*this = std::move(new_val);
|
||||
}
|
||||
|
||||
GncRational&
|
||||
GncRational::mul (const GncRational& b, GncDenom& d)
|
||||
{
|
||||
*this *= b;
|
||||
d.reduce(*this);
|
||||
round (d.get(), d.m_round);
|
||||
return *this;
|
||||
}
|
||||
|
||||
GncRational&
|
||||
GncRational::div (GncRational b, GncDenom& d)
|
||||
{
|
||||
*this /= b;
|
||||
d.reduce(*this);
|
||||
round (d.get(), d.m_round);
|
||||
return *this;
|
||||
}
|
||||
|
||||
GncRational&
|
||||
GncRational::add (const GncRational& b, GncDenom& d)
|
||||
{
|
||||
*this += b;
|
||||
d.reduce(*this);
|
||||
round (d.get(), d.m_round);
|
||||
return *this;
|
||||
}
|
||||
|
||||
GncRational&
|
||||
GncRational::sub (const GncRational& b, GncDenom& d)
|
||||
{
|
||||
return add(-b, d);
|
||||
}
|
||||
|
||||
void
|
||||
GncRational::round (GncInt128 new_den, RoundType rtype)
|
||||
{
|
||||
@ -263,9 +233,9 @@ GncRational::round (GncInt128 new_den, RoundType rtype)
|
||||
GncInt128 gcd = new_num.gcd(new_den);
|
||||
if (!(gcd.isNan() || gcd.isOverflow()))
|
||||
{
|
||||
new_num /= gcd;
|
||||
new_den /= gcd;
|
||||
remainder /= gcd;
|
||||
new_num /= gcd;
|
||||
new_den /= gcd;
|
||||
remainder /= gcd;
|
||||
}
|
||||
|
||||
/* if that didn't work, shift both num and den down until neither is "big", then
|
||||
@ -370,10 +340,7 @@ GncRational::round_to_numeric() const
|
||||
}
|
||||
GncRational new_rational(*this);
|
||||
GncRational scratch(1, 1);
|
||||
auto divisor = static_cast<int64_t>(m_den / (m_num.abs() >> 62));
|
||||
GncDenom gnc_denom(new_rational, scratch, divisor,
|
||||
GNC_HOW_RND_ROUND_HALF_DOWN);
|
||||
new_rational.round(gnc_denom.get(), gnc_denom.m_round);
|
||||
new_rational.round(m_den / (m_num.abs() >> 62), RoundType::half_down);
|
||||
return new_rational;
|
||||
}
|
||||
auto quot(m_den / m_num);
|
||||
@ -391,88 +358,6 @@ GncRational::round_to_numeric() const
|
||||
}
|
||||
GncRational new_rational(*this);
|
||||
GncRational scratch(1, 1);
|
||||
auto int_div = static_cast<int64_t>(m_den / divisor);
|
||||
GncDenom gnc_denom(new_rational, scratch, int_div,
|
||||
GNC_HOW_RND_ROUND_HALF_DOWN);
|
||||
new_rational.round(gnc_denom.get(), gnc_denom.m_round);
|
||||
new_rational.round(m_den / divisor, RoundType::half_down);
|
||||
return new_rational;
|
||||
}
|
||||
|
||||
GncDenom::GncDenom (GncRational& a, GncRational& b,
|
||||
int64_t spec, unsigned int how) noexcept :
|
||||
m_value (spec),
|
||||
m_round (static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK)),
|
||||
m_type (static_cast<DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
|
||||
m_auto (spec == GNC_DENOM_AUTO),
|
||||
m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8),
|
||||
m_error (GNC_ERROR_OK)
|
||||
|
||||
{
|
||||
|
||||
if (!m_auto)
|
||||
return;
|
||||
switch (m_type)
|
||||
{
|
||||
case DenomType::fixed:
|
||||
if (a.m_den == b.m_den)
|
||||
{
|
||||
m_value = a.m_den;
|
||||
}
|
||||
else if (b.m_num == 0)
|
||||
{
|
||||
m_value = a.m_den;
|
||||
b.m_den = a.m_den;
|
||||
}
|
||||
else if (a.m_num == 0)
|
||||
{
|
||||
m_value = b.m_den;
|
||||
a.m_den = b.m_den;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_error = GNC_ERROR_DENOM_DIFF;
|
||||
}
|
||||
m_auto = false;
|
||||
break;
|
||||
|
||||
case DenomType::lcd:
|
||||
m_value = a.m_den.lcm(b.m_den);
|
||||
m_auto = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
GncDenom::reduce (const GncRational& a) noexcept
|
||||
{
|
||||
if (!m_auto)
|
||||
return;
|
||||
switch (m_type)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case DenomType::reduce:
|
||||
m_value = a.m_den / a.m_num.gcd(a.m_den);
|
||||
break;
|
||||
|
||||
case DenomType::sigfigs:
|
||||
GncInt128 val {};
|
||||
if (a.m_num.abs() > a.m_den)
|
||||
val = a.m_num.abs() / a.m_den;
|
||||
else
|
||||
val = a.m_den / a.m_num.abs();
|
||||
unsigned int digits {};
|
||||
while (val >= 10)
|
||||
{
|
||||
++digits;
|
||||
val /= 10;
|
||||
}
|
||||
m_value = (a.m_num.abs() > a.m_den ? powten (m_sigfigs - digits - 1) :
|
||||
powten (m_sigfigs + digits));
|
||||
m_auto = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
#include "gnc-numeric.h"
|
||||
#include "gnc-int128.hpp"
|
||||
|
||||
struct GncDenom;
|
||||
class GncNumeric;
|
||||
enum class RoundType;
|
||||
enum class DenomType;
|
||||
|
||||
@ -68,13 +68,6 @@ public:
|
||||
/** Round/convert this to the denominator provided by d, according to d's
|
||||
* m_round value.
|
||||
*/
|
||||
/* These are mutators; in other words, they implement the equivalent of
|
||||
* operators *=, /=, +=, and -=. They return a reference to this for chaining.
|
||||
*/
|
||||
GncRational& mul(const GncRational& b, GncDenom& d);
|
||||
GncRational& div(GncRational b, GncDenom& d);
|
||||
GncRational& add(const GncRational& b, GncDenom& d);
|
||||
GncRational& sub(const GncRational& b, GncDenom& d);
|
||||
void round (GncInt128 new_den, RoundType rtype);
|
||||
void operator+=(GncRational b);
|
||||
void operator-=(GncRational b);
|
||||
@ -93,19 +86,4 @@ GncRational operator-(GncRational a, GncRational b);
|
||||
GncRational operator*(GncRational a, GncRational b);
|
||||
GncRational operator/(GncRational a, GncRational b);
|
||||
|
||||
|
||||
/** Encapsulates the rounding specifications computations. */
|
||||
struct GncDenom
|
||||
{
|
||||
GncDenom (GncRational& a, GncRational& b, int64_t spec, unsigned int how) noexcept;
|
||||
void reduce (const GncRational& a) noexcept;
|
||||
GncInt128 get () const noexcept { return m_value; }
|
||||
|
||||
GncInt128 m_value;
|
||||
RoundType m_round;
|
||||
DenomType m_type;
|
||||
bool m_auto;
|
||||
unsigned int m_sigfigs;
|
||||
GNCNumericErrorCode m_error;
|
||||
};
|
||||
#endif //__GNC_RATIONAL_HPP__
|
||||
|
@ -134,9 +134,15 @@ test_gnc_rational_SOURCES = \
|
||||
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
|
||||
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
|
||||
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
|
||||
$(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
|
||||
$(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
|
||||
$(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
|
||||
$(top_srcdir)/${MODULEPATH}/qoflog.cpp \
|
||||
gtest-gnc-rational.cpp
|
||||
|
||||
test_gnc_rational_CPPFLAGS = \
|
||||
-I${top_srcdir}/src \
|
||||
-I${top_srcdir}/src/libqof/qof \
|
||||
-I${GTEST_HEADERS} \
|
||||
${GLIB_CFLAGS}
|
||||
|
||||
@ -155,8 +161,14 @@ test_gnc_numeric_SOURCES = \
|
||||
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
|
||||
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
|
||||
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
|
||||
$(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
|
||||
$(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
|
||||
$(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
|
||||
$(top_srcdir)/${MODULEPATH}/qoflog.cpp \
|
||||
gtest-gnc-numeric.cpp
|
||||
test_gnc_numeric_CPPFLAGS = \
|
||||
-I${top_srcdir}/src \
|
||||
-I${top_srcdir}/src/libqof/qof \
|
||||
-I${GTEST_HEADERS} \
|
||||
${GLIB_CFLAGS}
|
||||
|
||||
|
@ -419,6 +419,10 @@ TEST(qofint128_functions, divide)
|
||||
EXPECT_EQ (zero, q);
|
||||
EXPECT_EQ (big, r);
|
||||
|
||||
big.div (big - 1, q, r);
|
||||
EXPECT_EQ(one, q);
|
||||
EXPECT_EQ(one, r);
|
||||
|
||||
EXPECT_EQ (big, big %= bigger);
|
||||
EXPECT_EQ (two, bigger /= big);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user