mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-12-01 13:09:41 -06:00
1361 lines
37 KiB
C++
1361 lines
37 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>
|
|
|
|
extern "C"
|
|
{
|
|
#include <config.h>
|
|
#include "qof.h"
|
|
}
|
|
|
|
#include "gnc-numeric.hpp"
|
|
#include "gnc-rational.hpp"
|
|
|
|
static QofLogModule log_module = "qof";
|
|
|
|
static const uint8_t max_leg_digits{17};
|
|
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(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) + 1.0);
|
|
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::smatch;
|
|
using boost::regex_search;
|
|
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 pattern with the default ECMAScript syntax so we use the
|
|
* awk syntax.
|
|
*/
|
|
static const regex numeral(numer_frag);
|
|
static const regex hex(hex_frag);
|
|
static const regex numeral_rational(numer_frag + slash + denom_frag);
|
|
static const regex hex_rational(hex_frag + slash + hex_frag);
|
|
static const regex hex_over_num(hex_frag + slash + denom_frag);
|
|
static const regex num_over_hex(numer_frag + slash + hex_frag);
|
|
static const regex decimal(numer_frag + "[.,]" + denom_frag);
|
|
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 (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, 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, decimal))
|
|
{
|
|
auto neg = (m[1].length() && m[1].str()[0] == '-');
|
|
GncInt128 high((neg && m[1].length() > 1) || (!neg && m[1].length()) ?
|
|
stoll(m[1].str()) : 0);
|
|
GncInt128 low(stoll(m[2].str()));
|
|
int64_t d = powten(m[2].str().length());
|
|
GncInt128 n = high * d + (neg ? -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 (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)
|
|
{
|
|
gint64 aa, bb;
|
|
|
|
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)
|
|
{
|
|
gnc_numeric nb;
|
|
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)
|
|
{
|
|
gchar *result;
|
|
gint64 tmpnum = n.num;
|
|
gint64 tmpdenom = n.denom;
|
|
|
|
result = g_strdup_printf("%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, tmpnum, tmpdenom);
|
|
|
|
return result;
|
|
}
|
|
|
|
gchar *
|
|
gnc_num_dbg_to_string(gnc_numeric n)
|
|
{
|
|
static char buff[1000];
|
|
static char *p = buff;
|
|
gint64 tmpnum = n.num;
|
|
gint64 tmpdenom = n.denom;
|
|
|
|
p += 100;
|
|
if (p - buff >= 1000) p = buff;
|
|
|
|
sprintf(p, "%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, tmpnum, tmpdenom);
|
|
|
|
return p;
|
|
}
|
|
|
|
gboolean
|
|
string_to_gnc_numeric(const gchar* str, gnc_numeric *n)
|
|
{
|
|
try
|
|
{
|
|
GncNumeric an(str);
|
|
*n = static_cast<gnc_numeric>(an);
|
|
return TRUE;
|
|
}
|
|
catch (const std::exception& err)
|
|
{
|
|
PWARN("%s", err.what());
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* *******************************************************************
|
|
* GValue handling
|
|
********************************************************************/
|
|
static gpointer
|
|
gnc_numeric_boxed_copy_func( gpointer in_ptr )
|
|
{
|
|
auto in_gnc_numeric = static_cast<gnc_numeric*>(in_ptr);
|
|
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( gpointer in_gnc_numeric )
|
|
{
|
|
g_free( in_gnc_numeric );
|
|
}
|
|
|
|
GType
|
|
gnc_numeric_get_type( void )
|
|
{
|
|
static GType type = 0;
|
|
|
|
if ( type == 0 )
|
|
{
|
|
type = g_boxed_type_register_static( "gnc_numeric",
|
|
gnc_numeric_boxed_copy_func,
|
|
gnc_numeric_boxed_free_func );
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/* *******************************************************************
|
|
* 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 =================== */
|