gnucash/libgnucash/engine/gnc-numeric.cpp
Christopher Lam 27c16517e9 [gnc-numeric.cpp] shortcut parsing num/denom as gnc_numeric
The gnc_numeric is serialised as "num/denom" with no whitespace, and
denom > 0. This function takes advantage of std::from_chars to parse
it. The "num" serialisation is also optimised as a free side effect of
this function.
2024-03-11 19:16:06 +08:00

1483 lines
41 KiB
C++

/********************************************************************
* gnc-numeric.c -- an exact-number library for accounting use *
* Copyright (C) 2000 Bill Gribble *
* Copyright (C) 2004 Linas Vepstas <linas@linas.org> *
* *
* 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 <glib.h>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <sstream>
#include <boost/regex.hpp>
#include <boost/locale/encoding_utf.hpp>
#include <config.h>
#include <stdexcept>
#include <stdint.h>
#include "gnc-int128.hpp"
#include "qof.h"
#include "gnc-numeric.hpp"
#include "gnc-rational.hpp"
#include <optional>
#include <charconv>
static QofLogModule log_module = "qof";
static const uint8_t max_leg_digits{18};
static const int64_t pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000,
INT64_C(10000000000), INT64_C(100000000000),
INT64_C(1000000000000), INT64_C(10000000000000),
INT64_C(100000000000000),
INT64_C(1000000000000000),
INT64_C(10000000000000000),
INT64_C(100000000000000000),
INT64_C(1000000000000000000)};
#define POWTEN_OVERFLOW -5
int64_t
powten (unsigned int exp)
{
if (exp > max_leg_digits)
exp = max_leg_digits;
return pten[exp];
}
GncNumeric::GncNumeric(GncRational rr)
{
/* Can't use isValid here because we want to throw different exceptions. */
if (rr.num().isNan() || rr.denom().isNan())
throw std::underflow_error("Operation resulted in NaN.");
if (rr.num().isOverflow() || rr.denom().isOverflow())
throw std::overflow_error("Operation overflowed a 128-bit int.");
if (rr.num().isBig() || rr.denom().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.num());
m_den = static_cast<int64_t>(rr.denom());
}
GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
{
static uint64_t max_leg_value{INT64_C(1000000000000000000)};
if (std::isnan(d) || fabs(d) > max_leg_value)
{
std::ostringstream msg;
msg << "Unable to construct a GncNumeric from " << d << ".\n";
throw std::invalid_argument(msg.str());
}
constexpr auto max_num = static_cast<double>(INT64_MAX);
auto logval = log10(fabs(d));
int64_t den;
uint8_t den_digits;
if (logval > 0.0)
den_digits = (max_leg_digits + 1) - static_cast<int>(floor(logval));
else
den_digits = max_leg_digits;
den = powten(den_digits);
auto num_d = static_cast<double>(den) * d;
while (fabs(num_d) > max_num && den_digits > 1)
{
den = powten(--den_digits);
num_d = static_cast<double>(den) * d;
}
auto num = static_cast<int64_t>(floor(num_d));
if (num == 0)
return;
GncNumeric q(num, den);
auto r = q.reduce();
m_num = r.num();
m_den = r.denom();
}
using boost::regex;
using boost::regex_search;
using boost::smatch;
static std::pair<int64_t, int64_t>
reduce_number_pair(std::pair<GncInt128, GncInt128>num_pair,
const std::string& num_str, bool autoround)
{
auto [n, d] = num_pair;
if (!autoround && n.isBig()) {
std::ostringstream errmsg;
errmsg << "Decimal string " << num_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 " << num_str
<< " can't be represented in a GncNumeric, even after reducing "
"denom to "
<< d;
throw std::overflow_error(errmsg.str());
}
return std::make_pair(static_cast<int64_t>(n), static_cast<int64_t>(d));
}
static std::pair<GncInt128, int64_t>
numeric_from_decimal_match(const std::string& integer, const std::string& decimal)
{
auto neg = (!integer.empty() && integer[0] == '-');
GncInt128 high((neg && integer.length() > 1) || (!neg && !integer.empty())
? stoll(integer)
: 0);
GncInt128 low{ decimal.empty() ? 0 : stoll(decimal)};
auto exp10 = decimal.length();
int64_t d = powten(exp10);
GncInt128 n = high * d + (neg ? -low : low);
while (exp10 > max_leg_digits) {
/* If the arg to powten is bigger than max_leg_digits
it returns 10**max_leg_digits so reduce exp10 by
that amount */
n = n / powten(exp10 - max_leg_digits);
exp10 -= max_leg_digits;
}
return std::make_pair(n, d);
}
static std::pair<GncInt128, GncInt128>
numeric_from_scientific_match(smatch &m)
{
int exp{m[4].matched ? stoi(m[4].str()) : 0};
auto neg_exp{exp < 0};
exp = neg_exp ? -exp : exp;
if (exp >= max_leg_digits)
{
std::ostringstream errmsg;
errmsg << "Exponent " << m[3].str() << " in match " << m[0].str()
<< " exceeds range that GnuCash can parse.";
throw std::overflow_error(errmsg.str());
}
GncInt128 num, denom;
auto mult = powten(exp);
if (m[1].matched)
{
denom = neg_exp ? mult : 1;
num = neg_exp ? stoll(m[1].str()) : mult * stoll(m[1].str());
}
else
{
auto [d_num, d_denom] = numeric_from_decimal_match(m[2].str(), m[3].str());
if (neg_exp || d_denom > mult)
{
num = d_num;
denom = neg_exp ? d_denom * mult : d_denom / mult;
}
else
{
num = d_num * mult / d_denom;
denom = 1;
}
}
return std::make_pair(num, denom);
}
static std::optional<gnc_numeric>
fast_numeral_rational (const char* str)
{
if (!str || !str[0])
return {};
// because minint64 = -9223372036854775808 and has 20 characters,
// the maximum strlen to handle is 20+19+1 = 40. 48 is a nice
// number in 64-bit.
auto end_ptr{(const char*)memchr (str, '\0', 48)};
if (!end_ptr)
return {};
int64_t num, denom{};
auto result = std::from_chars (str, end_ptr, num);
if (result.ec != std::errc())
return {};
if (result.ptr == end_ptr)
return gnc_numeric_create (num, 1);
if (*result.ptr != '/')
return {};
result = std::from_chars (result.ptr + 1, end_ptr, denom);
if (result.ec != std::errc() || result.ptr != end_ptr || denom <= 0)
return {};
return gnc_numeric_create (num, denom);
}
GncNumeric::GncNumeric(const std::string &str, bool autoround) {
static const std::string maybe_sign ("(-?)");
static const std::string opt_signed_int("(-?[0-9]*)");
static const std::string unsigned_int("([0-9]+)");
static const std::string hex_frag("(0[xX][A-Fa-f0-9]+)");
static const std::string slash("[ \\t]*/[ \\t]*");
static const std::string whitespace("[ \\t]+");
/* The llvm standard C++ library refused to recognize the - in the
* opt_signed_int pattern with the default ECMAScript syntax so we use the
* awk syntax.
*/
static const regex numeral(opt_signed_int);
static const regex hex(hex_frag);
static const regex numeral_rational(opt_signed_int + slash + unsigned_int);
static const regex integer_and_fraction(maybe_sign + unsigned_int + whitespace + unsigned_int + slash + unsigned_int);
static const regex hex_rational(hex_frag + slash + hex_frag);
static const regex hex_over_num(hex_frag + slash + unsigned_int);
static const regex num_over_hex(opt_signed_int + slash + hex_frag);
static const regex decimal(opt_signed_int + "[.,]" + unsigned_int);
static const regex scientific("(?:(-?[0-9]+[.,]?)|(-?[0-9]*)[.,]([0-9]+))[Ee](-?[0-9]+)");
static const regex has_hex_prefix(".*0[xX]$");
smatch m, x;
/* 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 (auto res = fast_numeral_rational (str.c_str()))
{
m_num = res->num;
m_den = res->denom;
return;
}
if (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 (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 (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 (regex_search(str, m, integer_and_fraction))
{
GncNumeric n(stoll(m[3].str()), stoll(m[4].str()));
n += stoll(m[2].str());
m_num = m[1].str().empty() ? n.num() : -n.num();
m_den = n.denom();
return;
}
if (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 (regex_search(str, m, scientific) && ! regex_match(m.prefix().str(), x, has_hex_prefix))
{
auto [num, denom] =
reduce_number_pair(numeric_from_scientific_match(m),
str, autoround);
m_num = num;
m_den = denom;
return;
}
if (regex_search(str, m, decimal))
{
std::string integer{m[1].matched ? m[1].str() : ""};
std::string decimal{m[2].matched ? m[2].str() : ""};
auto [num, denom] =
reduce_number_pair(numeric_from_decimal_match(integer, decimal),
str, autoround);
m_num = num;
m_den = denom;
return;
}
if (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 (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.num();
auto rem = new_num % red_conv.denom();
new_num /= red_conv.denom();
if (new_num.isBig())
{
GncRational rr(new_num, new_denom);
GncNumeric nn(rr);
rr = rr.convert<RoundType::truncate>(new_denom);
return {static_cast<int64_t>(rr.num()), new_denom, 0};
}
return {static_cast<int64_t>(new_num),
static_cast<int64_t>(red_conv.denom()), static_cast<int64_t>(rem)};
}
int64_t
GncNumeric::sigfigs_denom(unsigned figs) const noexcept
{
if (m_num == 0)
return 1;
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(digits < figs ? figs - digits - 1 : 0) :
powten(figs + digits);
}
std::string
GncNumeric::to_string() const noexcept
{
std::ostringstream out;
out << *this;
return out.str();
}
bool
GncNumeric::is_decimal() const noexcept
{
for (unsigned pwr = 0; pwr < max_leg_digits && m_den >= pten[pwr]; ++pwr)
{
if (m_den == pten[pwr])
return true;
if (m_den % pten[pwr])
return false;
}
return false;
}
GncNumeric
GncNumeric::to_decimal(unsigned int max_places) const
{
if (max_places > max_leg_digits)
max_places = max_leg_digits;
if (m_num == 0)
return GncNumeric();
if (is_decimal())
{
if (m_num == 0 || m_den < powten(max_places))
return *this; // Nothing to do.
/* 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 = rr.convert<RoundType::never>(powten(max_places)); //May throw
/* rr might have gotten reduced a bit too much; if so, put it back: */
unsigned int pwr{1};
for (; pwr <= max_places && !(rr.denom() % powten(pwr)); ++pwr);
auto reduce_to = powten(pwr);
GncInt128 rr_num(rr.num()), rr_den(rr.denom());
if (rr_den % reduce_to)
{
auto factor(reduce_to / rr.denom());
rr_num *= factor;
rr_den *= factor;
}
while (!rr_num.isZero() && rr_num > 9 && rr_den > 9 && rr_num % 10 == 0)
{
rr_num /= 10;
rr_den /= 10;
}
try
{
/* Construct from the parts to avoid the GncRational constructor's
* automatic rounding.
*/
return {static_cast<int64_t>(rr_num), static_cast<int64_t>(rr_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());
}
catch (const std::underflow_error& err)
{
std::ostringstream msg;
msg << "GncNumeric " << *this
<< " underflows 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;
}
GncRational an(*this), bn(b);
return an.cmp(bn);
}
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);
}
template <typename T, typename I> T
convert(T num, I new_denom, int how)
{
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();
switch (rtype)
{
case RoundType::floor:
if (sigfigs)
return num.template convert_sigfigs<RoundType::floor>(figs);
else
return num.template convert<RoundType::floor>(new_denom);
case RoundType::ceiling:
if (sigfigs)
return num.template convert_sigfigs<RoundType::ceiling>(figs);
else
return num.template convert<RoundType::ceiling>(new_denom);
case RoundType::truncate:
if (sigfigs)
return num.template convert_sigfigs<RoundType::truncate>(figs);
else
return num.template convert<RoundType::truncate>(new_denom);
case RoundType::promote:
if (sigfigs)
return num.template convert_sigfigs<RoundType::promote>(figs);
else
return num.template convert<RoundType::promote>(new_denom);
case RoundType::half_down:
if (sigfigs)
return num.template convert_sigfigs<RoundType::half_down>(figs);
else
return num.template convert<RoundType::half_down>(new_denom);
case RoundType::half_up:
if (sigfigs)
return num.template convert_sigfigs<RoundType::half_up>(figs);
else
return num.template convert<RoundType::half_up>(new_denom);
case RoundType::bankers:
if (sigfigs)
return num.template convert_sigfigs<RoundType::bankers>(figs);
else
return num.template convert<RoundType::bankers>(new_denom);
case RoundType::never:
if (sigfigs)
return num.template convert_sigfigs<RoundType::never>(figs);
else
return num.template 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 num.template convert_sigfigs<RoundType::truncate>(figs);
else
return num.template convert<RoundType::truncate>(new_denom);
}
}
/* =============================================================== */
/* This function is small, simple, and used everywhere below,
* lets try to inline it.
*/
GNCNumericErrorCode
gnc_numeric_check(gnc_numeric in)
{
if (G_LIKELY(in.denom != 0))
{
return GNC_ERROR_OK;
}
else if (in.num)
{
if ((0 < in.num) || (-4 > in.num))
{
in.num = (gint64) GNC_ERROR_OVERFLOW;
}
return (GNCNumericErrorCode) in.num;
}
else
{
return GNC_ERROR_ARG;
}
}
/* *******************************************************************
* gnc_numeric_zero_p
********************************************************************/
gboolean
gnc_numeric_zero_p(gnc_numeric a)
{
if (gnc_numeric_check(a))
{
return 0;
}
else
{
if ((a.num == 0) && (a.denom != 0))
{
return 1;
}
else
{
return 0;
}
}
}
/* *******************************************************************
* gnc_numeric_negative_p
********************************************************************/
gboolean
gnc_numeric_negative_p(gnc_numeric a)
{
if (gnc_numeric_check(a))
{
return 0;
}
else
{
if ((a.num < 0) && (a.denom != 0))
{
return 1;
}
else
{
return 0;
}
}
}
/* *******************************************************************
* gnc_numeric_positive_p
********************************************************************/
gboolean
gnc_numeric_positive_p(gnc_numeric a)
{
if (gnc_numeric_check(a))
{
return 0;
}
else
{
if ((a.num > 0) && (a.denom != 0))
{
return 1;
}
else
{
return 0;
}
}
}
/* *******************************************************************
* gnc_numeric_compare
* returns 1 if a>b, -1 if b>a, 0 if a == b
********************************************************************/
int
gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
{
if (gnc_numeric_check(a) || gnc_numeric_check(b))
{
return 0;
}
if (a.denom == b.denom)
{
if (a.num == b.num) return 0;
if (a.num > b.num) return 1;
return -1;
}
GncNumeric an (a), bn (b);
return an.cmp(bn);
}
/* *******************************************************************
* gnc_numeric_eq
********************************************************************/
gboolean
gnc_numeric_eq(gnc_numeric a, gnc_numeric b)
{
return ((a.num == b.num) && (a.denom == b.denom));
}
/* *******************************************************************
* gnc_numeric_equal
********************************************************************/
gboolean
gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
{
if (gnc_numeric_check(a))
{
/* a is not a valid number, check b */
if (gnc_numeric_check(b))
/* Both invalid, consider them equal */
return TRUE;
else
/* a invalid, b valid */
return FALSE;
}
if (gnc_numeric_check(b))
/* a valid, b invalid */
return FALSE;
return gnc_numeric_compare (a, b) == 0;
}
/* *******************************************************************
* gnc_numeric_same
* would a and b be equal() if they were both converted to the same
* denominator?
********************************************************************/
int
gnc_numeric_same(gnc_numeric a, gnc_numeric b, gint64 denom,
gint how)
{
gnc_numeric aconv, bconv;
aconv = gnc_numeric_convert(a, denom, how);
bconv = gnc_numeric_convert(b, denom, how);
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
********************************************************************/
gnc_numeric
gnc_numeric_add(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how)
{
if (gnc_numeric_check(a) || gnc_numeric_check(b))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
try
{
denom = denom_lcd(a, b, denom, how);
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
{
GncNumeric an (a), bn (b);
GncNumeric sum = an + bn;
return static_cast<gnc_numeric>(convert(sum, denom, how));
}
GncRational ar(a), br(b);
auto sum = ar + br;
if (denom == GNC_DENOM_AUTO &&
(how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
return static_cast<gnc_numeric>(sum.round_to_numeric());
sum = convert(sum, denom, how);
if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return static_cast<gnc_numeric>(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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* gnc_numeric_sub
********************************************************************/
gnc_numeric
gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how)
{
if (gnc_numeric_check(a) || gnc_numeric_check(b))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
try
{
denom = denom_lcd(a, b, denom, how);
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
{
GncNumeric an (a), bn (b);
auto sum = an - bn;
return static_cast<gnc_numeric>(convert(sum, denom, how));
}
GncRational ar(a), br(b);
auto sum = ar - br;
if (denom == GNC_DENOM_AUTO &&
(how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
return static_cast<gnc_numeric>(sum.round_to_numeric());
sum = convert(sum, denom, how);
if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return static_cast<gnc_numeric>(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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* gnc_numeric_mul
********************************************************************/
gnc_numeric
gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how)
{
if (gnc_numeric_check(a) || gnc_numeric_check(b))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
try
{
denom = denom_lcd(a, b, denom, how);
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
{
GncNumeric an (a), bn (b);
auto prod = an * bn;
return static_cast<gnc_numeric>(convert(prod, denom, how));
}
GncRational ar(a), br(b);
auto prod = ar * br;
if (denom == GNC_DENOM_AUTO &&
(how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
return static_cast<gnc_numeric>(prod.round_to_numeric());
prod = convert(prod, denom, how);
if (prod.is_big() || !prod.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return static_cast<gnc_numeric>(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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* gnc_numeric_div
********************************************************************/
gnc_numeric
gnc_numeric_div(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how)
{
if (gnc_numeric_check(a) || gnc_numeric_check(b))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
try
{
denom = denom_lcd(a, b, denom, how);
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
{
GncNumeric an (a), bn (b);
auto quot = an / bn;
return static_cast<gnc_numeric>(convert(quot, denom, how));
}
GncRational ar(a), br(b);
auto quot = ar / br;
if (denom == GNC_DENOM_AUTO &&
(how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
return static_cast<gnc_numeric>(quot.round_to_numeric());
quot = static_cast<gnc_numeric>(convert(quot, denom, how));
if (quot.is_big() || !quot.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return static_cast<gnc_numeric>(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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* gnc_numeric_neg
* negate the argument
********************************************************************/
gnc_numeric
gnc_numeric_neg(gnc_numeric a)
{
if (gnc_numeric_check(a))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
return gnc_numeric_create(- a.num, a.denom);
}
/* *******************************************************************
* gnc_numeric_abs
* return the absolute value of the argument
********************************************************************/
gnc_numeric
gnc_numeric_abs(gnc_numeric a)
{
if (gnc_numeric_check(a))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
return gnc_numeric_create(ABS(a.num), a.denom);
}
/* *******************************************************************
* gnc_numeric_convert
********************************************************************/
gnc_numeric
gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
if (gnc_numeric_check(in))
return in;
try
{
return convert(GncNumeric(in), denom, how);
}
catch (const std::invalid_argument& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::overflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::underflow_error& err)
{
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
catch (const std::domain_error& err)
{
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* reduce a fraction by GCF elimination. This is NOT done as a
* part of the arithmetic API unless GNC_HOW_DENOM_REDUCE is specified
* as the output denominator.
********************************************************************/
gnc_numeric
gnc_numeric_reduce(gnc_numeric in)
{
if (gnc_numeric_check(in))
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
if (in.denom < 0) /* Negative denoms multiply num, can't be reduced. */
return in;
try
{
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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* gnc_numeric_to_decimal
*
* Attempt to convert the denominator to an exact power of ten without
* rounding. TRUE is returned if 'a' has been converted or was already
* decimal. Otherwise, FALSE is returned and 'a' remains unchanged.
* The 'max_decimal_places' parameter may be NULL.
********************************************************************/
gboolean
gnc_numeric_to_decimal(gnc_numeric *a, guint8 *max_decimal_places)
{
int max_places = max_decimal_places == NULL ? max_leg_digits :
*max_decimal_places;
if (a->num == 0) return TRUE;
try
{
GncNumeric an (*a);
auto bn = an.to_decimal(max_places);
*a = static_cast<gnc_numeric>(bn);
return TRUE;
}
catch (const std::exception& err)
{
PINFO ("%s", err.what());
return FALSE;
}
}
gnc_numeric
gnc_numeric_invert(gnc_numeric num)
{
if (num.num == 0)
return gnc_numeric_zero();
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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* double_to_gnc_numeric
********************************************************************/
#ifdef _MSC_VER
# define rint /* */
#endif
gnc_numeric
double_to_gnc_numeric(double in, gint64 denom, gint how)
{
try
{
GncNumeric an(in);
return convert(an, denom, how);
}
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);
}
catch (const std::domain_error& err)
{
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_REMAINDER);
}
}
/* *******************************************************************
* gnc_numeric_to_double
********************************************************************/
double
gnc_numeric_to_double(gnc_numeric in)
{
if (in.denom > 0)
{
return (double)in.num / (double)in.denom;
}
else
{
return (double)(in.num * -in.denom);
}
}
/* *******************************************************************
* gnc_numeric_error
********************************************************************/
gnc_numeric
gnc_numeric_error(GNCNumericErrorCode error_code)
{
return gnc_numeric_create(error_code, 0LL);
}
/* *******************************************************************
* gnc_numeric text IO
********************************************************************/
gchar *
gnc_numeric_to_string(gnc_numeric n)
{
char *result;
int64_t tmpnum = n.num;
int64_t tmpdenom = n.denom;
result = g_strdup_printf("%" PRId64 "/%" PRId64, tmpnum, tmpdenom);
return result;
}
gchar *
gnc_num_dbg_to_string(gnc_numeric n)
{
static char buff[1000];
static char *p = buff;
static const size_t size = 50;
int64_t tmpnum = n.num;
int64_t tmpdenom = n.denom;
p += size;
if ((size_t)(p - buff) > (sizeof(buff) - size))
p = buff;
snprintf(p, size, "%" PRId64 "/%" PRId64, tmpnum, tmpdenom);
return p;
}
gnc_numeric
gnc_numeric_from_string (const gchar* str)
{
if (!str)
return gnc_numeric_error (GNC_ERROR_ARG);
// the default gnc_numeric string format is "num/denom", whereby
// the denom must be >= 1. this speedily parses it. this also
// parses "num" as num/1.
if (auto res = fast_numeral_rational (str))
return *res;
try
{
return GncNumeric (str);
}
catch (const std::exception& err)
{
PWARN("%s", err.what());
return gnc_numeric_error (GNC_ERROR_ARG);
}
}
/* *******************************************************************
* GValue handling
********************************************************************/
static gnc_numeric*
gnc_numeric_boxed_copy_func( gnc_numeric *in_gnc_numeric )
{
if (!in_gnc_numeric)
return nullptr;
/* newvalue will be passed to g_free so we must allocate with g_malloc. */
auto newvalue = static_cast<gnc_numeric*>(g_malloc (sizeof (gnc_numeric)));
*newvalue = *in_gnc_numeric;
return newvalue;
}
static void
gnc_numeric_boxed_free_func( gnc_numeric *in_gnc_numeric )
{
g_free( in_gnc_numeric );
}
G_DEFINE_BOXED_TYPE (gnc_numeric, gnc_numeric, gnc_numeric_boxed_copy_func, gnc_numeric_boxed_free_func)
/* *******************************************************************
* gnc_numeric misc testing
********************************************************************/
#ifdef _GNC_NUMERIC_TEST
static char *
gnc_numeric_print(gnc_numeric in)
{
char * retval;
if (gnc_numeric_check(in))
{
retval = g_strdup_printf("<ERROR> [%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
in.num,
in.denom);
}
else
{
retval = g_strdup_printf("[%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
in.num,
in.denom);
}
return retval;
}
int
main(int argc, char ** argv)
{
gnc_numeric a = gnc_numeric_create(1, 3);
gnc_numeric b = gnc_numeric_create(1, 4);
gnc_numeric c;
gnc_numeric err;
printf("multiply (EXACT): %s * %s = %s\n",
gnc_numeric_print(a), gnc_numeric_print(b),
gnc_numeric_print(gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT)));
printf("multiply (REDUCE): %s * %s = %s\n",
gnc_numeric_print(a), gnc_numeric_print(b),
gnc_numeric_print(gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE)));
return 0;
}
#endif
std::ostream&
operator<<(std::ostream& s, GncNumeric n)
{
using boost::locale::conv::utf_to_utf;
std::basic_ostringstream<wchar_t> ss;
ss.imbue(s.getloc());
ss << n;
s << utf_to_utf<char>(ss.str());
return s;
}
const char* gnc_numeric_errorCode_to_string(GNCNumericErrorCode error_code)
{
switch (error_code)
{
case GNC_ERROR_OK:
return "GNC_ERROR_OK";
case GNC_ERROR_ARG:
return "GNC_ERROR_ARG";
case GNC_ERROR_OVERFLOW:
return "GNC_ERROR_OVERFLOW";
case GNC_ERROR_DENOM_DIFF:
return "GNC_ERROR_DENOM_DIFF";
case GNC_ERROR_REMAINDER:
return "GNC_ERROR_REMAINDER";
default:
return "<unknown>";
}
}
/* ======================== END OF FILE =================== */