/******************************************************************** * gnc-numeric.c -- an exact-number library for accounting use * * Copyright (C) 2000 Bill Gribble * * Copyright (C) 2004 Linas Vepstas * * * * 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 #include #include #include #include #include #include #include #include extern "C" { #include #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(rr.num()); m_den = static_cast(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(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(floor(logval) + 1.0); else den_digits = max_leg_digits; den = powten(den_digits); auto num_d = static_cast(den) * d; while (fabs(num_d) > max_num && den_digits > 1) { den = powten(--den_digits); num_d = static_cast(den) * d; } auto num = static_cast(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 patter 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(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(m_num) / static_cast(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(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(new_denom); return {static_cast(rr.num()), new_denom, 0}; } return {static_cast(new_num), static_cast(red_conv.denom()), static_cast(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(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(rr_num), static_cast(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(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(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(rr); } template T convert(T num, I new_denom, int how) { auto rtype = static_cast(how & GNC_NUMERIC_RND_MASK); unsigned int figs = GNC_HOW_GET_SIGFIGS(how); auto dtype = static_cast(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(figs); else return num.template convert(new_denom); case RoundType::ceiling: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(new_denom); case RoundType::truncate: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(new_denom); case RoundType::promote: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(new_denom); case RoundType::half_down: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(new_denom); case RoundType::half_up: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(new_denom); case RoundType::bankers: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(new_denom); case RoundType::never: if (sigfigs) return num.template convert_sigfigs(figs); else return num.template convert(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(figs); else return num.template convert(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(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(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(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(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(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(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(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(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(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(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(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(quot.round_to_numeric()); quot = static_cast(convert(quot, denom, how)); if (quot.is_big() || !quot.valid()) return gnc_numeric_error(GNC_ERROR_OVERFLOW); return static_cast(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(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(bn); return TRUE; } catch (const std::exception& err) { PWARN("%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(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(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(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(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(" [%" 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 ss; ss.imbue(s.getloc()); ss << n; s << utf_to_utf(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 ""; } } /* ======================== END OF FILE =================== */