diff --git a/po/POTFILES.in b/po/POTFILES.in index d0aaf53c6a1..4acbea68559 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -430,6 +430,7 @@ src/import-export/qif-imp/gnc-plugin-qif-import.c [type: gettext/gsettings]src/import-export/qif-imp/gschemas/org.gnucash.dialogs.import.qif.gschema.xml.in.in src/libqof/qof/gnc-date.cpp src/libqof/qof/gnc-numeric.cpp +src/libqof/qof/gnc-rational.cpp src/libqof/qof/guid.cpp src/libqof/qof/kvp_frame.cpp src/libqof/qof/kvp-util.cpp diff --git a/src/libqof/qof/Makefile.am b/src/libqof/qof/Makefile.am index a3a078b8fb2..0648195c246 100644 --- a/src/libqof/qof/Makefile.am +++ b/src/libqof/qof/Makefile.am @@ -25,6 +25,7 @@ AM_CPPFLAGS = \ libgnc_qof_la_SOURCES = \ gnc-date.cpp \ gnc-numeric.cpp \ + gnc-rational.cpp \ guid.cpp \ kvp-util.cpp \ kvp_frame.cpp \ @@ -51,6 +52,7 @@ qofinclude_HEADERS = \ gnc-date-p.h \ gnc-date.h \ gnc-numeric.h \ + gnc-rational.hpp \ guid.h \ kvp-util-p.h \ kvp-util.h \ diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp index 0a9c8b9d142..cb3468bf489 100644 --- a/src/libqof/qof/gnc-numeric.cpp +++ b/src/libqof/qof/gnc-numeric.cpp @@ -39,14 +39,16 @@ extern "C" #include "gnc-numeric.h" #include "qofint128.hpp" +#include "gnc-rational.hpp" + +using GncNumeric = GncRational; -/* static short module = MOD_ENGINE; */ static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 10000000000000000, 100000000000000000, 1000000000000000000}; -#define POWTEN_OVERFLOW -5 +static const int POWTEN_OVERFLOW {-5}; static inline gint64 powten (int exp) @@ -55,281 +57,7 @@ powten (int exp) return POWTEN_OVERFLOW; return exp < 0 ? -pten[-exp] : pten[exp]; } -struct GncNumeric; //Forward declaration - -struct GncDenom -{ - GncDenom (GncNumeric& a, GncNumeric& b, int64_t spec, uint how) noexcept; - void reduce (const GncNumeric& a) noexcept; - QofInt128 get () const noexcept { return m_value; } - - enum class RoundType : int - { - floor = GNC_HOW_RND_FLOOR, - ceiling = GNC_HOW_RND_CEIL, - truncate = GNC_HOW_RND_TRUNC, - promote = GNC_HOW_RND_PROMOTE, - half_down = GNC_HOW_RND_ROUND_HALF_DOWN, - half_up = GNC_HOW_RND_ROUND_HALF_UP, - bankers = GNC_HOW_RND_ROUND, - never = GNC_HOW_RND_NEVER, - }; - enum class DenomType : int - { - exact = GNC_HOW_DENOM_EXACT, - reduce = GNC_HOW_DENOM_REDUCE, - lcd = GNC_HOW_DENOM_LCD, - fixed = GNC_HOW_DENOM_FIXED, - sigfigs = GNC_HOW_DENOM_SIGFIG, - }; - - QofInt128 m_value; - RoundType m_round; - DenomType m_type; - bool m_auto; - uint m_sigfigs; - GNCNumericErrorCode m_error; -}; - -struct GncNumeric -{ - GncNumeric (gnc_numeric n) noexcept; - GncNumeric (QofInt128 num, QofInt128 den) noexcept; - operator gnc_numeric() const noexcept; - void round (GncDenom& denom) noexcept; - - QofInt128 m_num; - QofInt128 m_den; - GNCNumericErrorCode m_error; -}; - -GncNumeric::GncNumeric (gnc_numeric n) noexcept : - m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK} -{ - if (m_den.isNeg()) - { - m_num *= -m_den; - m_den = 1; - } -} - -GncNumeric::GncNumeric (QofInt128 num, QofInt128 den) noexcept : - m_num (num), m_den (den), m_error {} -{ -} - -GncNumeric::operator gnc_numeric () const noexcept -{ - if (m_num.isOverflow() || m_num.isNan() || - m_den.isOverflow() || m_den.isNan()) - return gnc_numeric_error(GNC_ERROR_OVERFLOW); - if (m_error != GNC_ERROR_OK) - return gnc_numeric_error (m_error); - try - { - return {static_cast(m_num), static_cast(m_den)}; - } - catch (std::overflow_error) - { - return gnc_numeric_error (GNC_ERROR_OVERFLOW); - } -} - -void -GncNumeric::round (GncDenom& denom) noexcept -{ - denom.reduce (*this); - if (m_error == GNC_ERROR_OK && denom.m_error != GNC_ERROR_OK) - { - m_error = denom.m_error; - return; - } - QofInt128 new_den = denom.get(); - if (new_den == 0) new_den = m_den; - if (!(m_num.isBig() || new_den.isBig() )) - { - if (m_den == new_den) - return; - - if (m_num.isZero()) - { - m_den = new_den; - return; - } - } - QofInt128 new_num {}, remainder {}; - if (new_den.isNeg()) - m_num.div(-new_den * m_den, new_num, remainder); - else - (m_num * new_den).div(m_den, new_num, remainder); - - if (remainder.isZero() && !(new_num.isBig() || new_den.isBig())) - { - m_num = new_num; - m_den = new_den; - return; - } - - if (new_num.isBig() || new_den.isBig()) - { - if (!denom.m_auto) - { - m_error = GNC_ERROR_OVERFLOW; - return; - } - /* First, try to reduce it */ - QofInt128 gcd = new_num.gcd(new_den); - new_num /= gcd; - new_den /= gcd; - remainder /= gcd; - -/* if that didn't work, shift both num and den down until neither is "big", then - * fall through to rounding. - */ - while (new_num && new_num.isBig() && new_den && new_den.isBig()) - { - new_num >>= 1; - new_den >>= 1; - remainder >>= 1; - } - } - -/* If we got here, then we can't exactly represent the rational with - * new_denom. We must either round or punt. - */ - switch (denom.m_round) - { - case GncDenom::RoundType::never: - m_error = GNC_ERROR_REMAINDER; - return; - case GncDenom::RoundType::floor: - if (new_num.isNeg()) ++new_num; - break; - case GncDenom::RoundType::ceiling: - if (! new_num.isNeg()) ++new_num; - break; - case GncDenom::RoundType::truncate: - break; - case GncDenom::RoundType::promote: - new_num += new_num.isNeg() ? -1 : 1; - break; - case GncDenom::RoundType::half_down: - if (new_den.isNeg()) - { - if (remainder * 2 > m_den * new_den) - new_num += new_num.isNeg() ? -1 : 1; - } - else if (remainder * 2 > m_den) - new_num += new_num.isNeg() ? -1 : 1; - break; - case GncDenom::RoundType::half_up: - if (new_den.isNeg()) - { - if (remainder * 2 >= m_den * new_den) - new_num += new_num.isNeg() ? -1 : 1; - } - else if (remainder * 2 >= m_den) - new_num += new_num.isNeg() ? -1 : 1; - break; - case GncDenom::RoundType::bankers: - if (new_den.isNeg()) - { - if (remainder * 2 > m_den * -new_den || - (remainder * 2 == m_den * -new_den && new_num % 2)) - new_num += new_num.isNeg() ? -1 : 1; - } - else - { - if (remainder * 2 > m_den || - (remainder * 2 == m_den && new_num % 2)) - new_num += new_num.isNeg() ? -1 : 1; - } - break; - } - m_num = new_num; - m_den = new_den; - return; -} - -GncDenom::GncDenom (GncNumeric& a, GncNumeric& b, - int64_t spec, uint how) noexcept : - m_value (spec), - m_round (static_cast(how & GNC_NUMERIC_RND_MASK)), - m_type (static_cast(how & GNC_NUMERIC_DENOM_MASK)), - m_auto (spec == GNC_DENOM_AUTO), - m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8), - m_error (GNC_ERROR_OK) - -{ - - if (!m_auto) - return; - switch (m_type) - { - case DenomType::fixed: - if (a.m_den == b.m_den) - { - m_value = a.m_den; - } - else if (b.m_num == 0) - { - m_value = a.m_den; - b.m_den = a.m_den; - } - else if (a.m_num == 0) - { - m_value = b.m_den; - a.m_den = b.m_den; - } - else - { - m_error = GNC_ERROR_DENOM_DIFF; - } - m_auto = false; - break; - - case DenomType::lcd: - m_value = a.m_den.lcm(b.m_den); - m_auto = false; - break; - default: - break; - - } -} - -void -GncDenom::reduce (const GncNumeric& a) noexcept -{ - if (!m_auto) - return; - switch (m_type) - { - default: - break; - case DenomType::reduce: - m_value = a.m_den / a.m_num.gcd(a.m_den); - break; - - case DenomType::sigfigs: - QofInt128 val {}; - if (a.m_num.abs() > a.m_den) - val = a.m_num.abs() / a.m_den; - else - val = a.m_den / a.m_num.abs(); - uint digits {}; - while (val >= 10) - { - ++digits; - val /= 10; - } - m_value = (a.m_num.abs() > a.m_den ? powten (m_sigfigs - digits - 1) : - powten (m_sigfigs + digits)); - m_auto = false; - break; - } -} /* =============================================================== */ /* This function is small, simple, and used everywhere below, diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp new file mode 100644 index 00000000000..efbdd671dd9 --- /dev/null +++ b/src/libqof/qof/gnc-rational.cpp @@ -0,0 +1,266 @@ +/******************************************************************** + * gnc-rational.hpp - A rational number library * + * Copyright 2014 John Ralls * + * 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 "gnc-rational.hpp" + +static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, + 10000000, 100000000, 1000000000, 10000000000, + 100000000000, 1000000000000, 10000000000000, + 100000000000000, 10000000000000000, + 100000000000000000, 1000000000000000000}; +static const int POWTEN_OVERFLOW {-5}; + +static inline gint64 +powten (int exp) +{ + if (exp > 18 || exp < -18) + return POWTEN_OVERFLOW; + return exp < 0 ? -pten[-exp] : pten[exp]; +} + +GncRational::GncRational (gnc_numeric n) noexcept : + m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK} +{ + if (m_den.isNeg()) + { + m_num *= -m_den; + m_den = 1; + } +} + +GncRational::GncRational (QofInt128 num, QofInt128 den) noexcept : + m_num (num), m_den (den), m_error {} +{ +} + +GncRational::operator gnc_numeric () const noexcept +{ + if (m_num.isOverflow() || m_num.isNan() || + m_den.isOverflow() || m_den.isNan()) + return gnc_numeric_error(GNC_ERROR_OVERFLOW); + if (m_error != GNC_ERROR_OK) + return gnc_numeric_error (m_error); + try + { + return {static_cast(m_num), static_cast(m_den)}; + } + catch (std::overflow_error) + { + return gnc_numeric_error (GNC_ERROR_OVERFLOW); + } +} + +void +GncRational::round (GncDenom& denom) noexcept +{ + denom.reduce (*this); + if (m_error == GNC_ERROR_OK && denom.m_error != GNC_ERROR_OK) + { + m_error = denom.m_error; + return; + } + QofInt128 new_den = denom.get(); + if (new_den == 0) new_den = m_den; + if (!(m_num.isBig() || new_den.isBig() )) + { + if (m_den == new_den) + return; + + if (m_num.isZero()) + { + m_den = new_den; + return; + } + } + QofInt128 new_num {}, remainder {}; + if (new_den.isNeg()) + m_num.div(-new_den * m_den, new_num, remainder); + else + (m_num * new_den).div(m_den, new_num, remainder); + + if (remainder.isZero() && !(new_num.isBig() || new_den.isBig())) + { + m_num = new_num; + m_den = new_den; + return; + } + + if (new_num.isBig() || new_den.isBig()) + { + if (!denom.m_auto) + { + m_error = GNC_ERROR_OVERFLOW; + return; + } + + /* First, try to reduce it */ + QofInt128 gcd = new_num.gcd(new_den); + new_num /= gcd; + new_den /= gcd; + remainder /= gcd; + +/* if that didn't work, shift both num and den down until neither is "big", th + * fall through to rounding. + */ + while (new_num && new_num.isBig() && new_den && new_den.isBig()) + { + new_num >>= 1; + new_den >>= 1; + remainder >>= 1; + } + } + +/* If we got here, then we can't exactly represent the rational with + * new_denom. We must either round or punt. + */ + switch (denom.m_round) + { + case GncDenom::RoundType::never: + m_error = GNC_ERROR_REMAINDER; + return; + case GncDenom::RoundType::floor: + if (new_num.isNeg()) ++new_num; + break; + case GncDenom::RoundType::ceiling: + if (! new_num.isNeg()) ++new_num; + break; + case GncDenom::RoundType::truncate: + break; + case GncDenom::RoundType::promote: + new_num += new_num.isNeg() ? -1 : 1; + break; + case GncDenom::RoundType::half_down: + if (new_den.isNeg()) + { + if (remainder * 2 > m_den * new_den) + new_num += new_num.isNeg() ? -1 : 1; + } + else if (remainder * 2 > m_den) + new_num += new_num.isNeg() ? -1 : 1; + break; + case GncDenom::RoundType::half_up: + if (new_den.isNeg()) + { + if (remainder * 2 >= m_den * new_den) + new_num += new_num.isNeg() ? -1 : 1; + } + else if (remainder * 2 >= m_den) + new_num += new_num.isNeg() ? -1 : 1; + break; + case GncDenom::RoundType::bankers: + if (new_den.isNeg()) + { + if (remainder * 2 > m_den * -new_den || + (remainder * 2 == m_den * -new_den && new_num % 2)) + new_num += new_num.isNeg() ? -1 : 1; + } + else + { + if (remainder * 2 > m_den || + (remainder * 2 == m_den && new_num % 2)) + new_num += new_num.isNeg() ? -1 : 1; + } + break; + } + m_num = new_num; + m_den = new_den; + return; +} + +GncDenom::GncDenom (GncRational& a, GncRational& b, + int64_t spec, uint how) noexcept : + m_value (spec), + m_round (static_cast(how & GNC_NUMERIC_RND_MASK)), + m_type (static_cast(how & GNC_NUMERIC_DENOM_MASK)), + m_auto (spec == GNC_DENOM_AUTO), + m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8), + m_error (GNC_ERROR_OK) + +{ + + if (!m_auto) + return; + switch (m_type) + { + case DenomType::fixed: + if (a.m_den == b.m_den) + { + m_value = a.m_den; + } + else if (b.m_num == 0) + { + m_value = a.m_den; + b.m_den = a.m_den; + } + else if (a.m_num == 0) + { + m_value = b.m_den; + a.m_den = b.m_den; + } + else + { + m_error = GNC_ERROR_DENOM_DIFF; + } + m_auto = false; + break; + + case DenomType::lcd: + m_value = a.m_den.lcm(b.m_den); + m_auto = false; + break; + default: + break; + + } +} + +void +GncDenom::reduce (const GncRational& a) noexcept +{ + if (!m_auto) + return; + switch (m_type) + { + default: + break; + case DenomType::reduce: + m_value = a.m_den / a.m_num.gcd(a.m_den); + break; + + case DenomType::sigfigs: + QofInt128 val {}; + if (a.m_num.abs() > a.m_den) + val = a.m_num.abs() / a.m_den; + else + val = a.m_den / a.m_num.abs(); + uint digits {}; + while (val >= 10) + { + ++digits; + val /= 10; + } + m_value = (a.m_num.abs() > a.m_den ? powten (m_sigfigs - digits - 1) : + powten (m_sigfigs + digits)); + m_auto = false; + break; + } +} diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp new file mode 100644 index 00000000000..456d28cad4b --- /dev/null +++ b/src/libqof/qof/gnc-rational.hpp @@ -0,0 +1,78 @@ +/******************************************************************** + * gnc-rational.hpp - A rational number library * + * Copyright 2014 John Ralls * + * 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 "qofint128.hpp" +#include "gnc-numeric.h" + +struct GncDenom; + +class GncRational +{ +public: + GncRational (gnc_numeric n) noexcept; + GncRational (QofInt128 num, QofInt128 den) noexcept; + operator gnc_numeric() const noexcept; + void round (GncDenom& d) noexcept; + GncRational& mul(const GncRational&, GncDenom& d) noexcept; + GncRational& div(const GncRational&, GncDenom& d) noexcept; + GncRational& add(const GncRational&, GncDenom& d) noexcept; + GncRational& sub(const GncRational&, GncDenom& d) noexcept; + + + QofInt128 m_num; + QofInt128 m_den; + GNCNumericErrorCode m_error; +}; + +struct GncDenom +{ + GncDenom (GncRational& a, GncRational& b, int64_t spec, uint how) noexcept; + void reduce (const GncRational& a) noexcept; + QofInt128 get () const noexcept { return m_value; } + + enum class RoundType : int + { + floor = GNC_HOW_RND_FLOOR, + ceiling = GNC_HOW_RND_CEIL, + truncate = GNC_HOW_RND_TRUNC, + promote = GNC_HOW_RND_PROMOTE, + half_down = GNC_HOW_RND_ROUND_HALF_DOWN, + half_up = GNC_HOW_RND_ROUND_HALF_UP, + bankers = GNC_HOW_RND_ROUND, + never = GNC_HOW_RND_NEVER, + }; + enum class DenomType : int + { + exact = GNC_HOW_DENOM_EXACT, + reduce = GNC_HOW_DENOM_REDUCE, + lcd = GNC_HOW_DENOM_LCD, + fixed = GNC_HOW_DENOM_FIXED, + sigfigs = GNC_HOW_DENOM_SIGFIG, + }; + + QofInt128 m_value; + RoundType m_round; + DenomType m_type; + bool m_auto; + uint m_sigfigs; + GNCNumericErrorCode m_error; +}; +