LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-rational.hpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 60.0 % 20 12
Test Date: 2026-11-02 18:19:43 Functions: 45.0 % 20 9
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************
       2              :  * gnc-rational.hpp - A rational number library                     *
       3              :  * Copyright 2014 John Ralls <jralls@ceridwen.us>                   *
       4              :  * This program is free software; you can redistribute it and/or    *
       5              :  * modify it under the terms of the GNU General Public License as   *
       6              :  * published by the Free Software Foundation; either version 2 of   *
       7              :  * the License, or (at your option) any later version.              *
       8              :  *                                                                  *
       9              :  * This program is distributed in the hope that it will be useful,  *
      10              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      11              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      12              :  * GNU General Public License for more details.                     *
      13              :  *                                                                  *
      14              :  * You should have received a copy of the GNU General Public License*
      15              :  * along with this program; if not, contact:                        *
      16              :  *                                                                  *
      17              :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      18              :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      19              :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      20              :  *                                                                  *
      21              :  *******************************************************************/
      22              : 
      23              : #ifndef __GNC_RATIONAL_HPP__
      24              : #define __GNC_RATIONAL_HPP__
      25              : 
      26              : #include "gnc-numeric.h"
      27              : #include "gnc-int128.hpp"
      28              : #include "gnc-rational-rounding.hpp"
      29              : 
      30              : class GncNumeric;
      31              : enum class RoundType;
      32              : enum class DenomType;
      33              : 
      34              : /** @ingroup QOF
      35              :  *  @brief Rational number class using GncInt128 for the numerator
      36              :  *  and denominator.
      37              :  *
      38              :  * This class provides far greater overflow protection compared to GncNumeric at
      39              :  * the expense of doubling the size, so GncNumeric is preferred for storage into
      40              :  * objects. Furthermore the backends are not able to store GncRational numbers;
      41              :  * storage in SQL would require using BLOBs which would preclude calculations in
      42              :  * queries. GncRational exists *primarily* as a more overflow-resistant
      43              :  * calculation facility for GncNumeric. It's available for cases where one needs
      44              :  * an error instead of an automatically rounded value for a calculation that
      45              :  * produces a result that won't fit into an int64 without rounding.
      46              : 
      47              :  * Errors: Errors are signalled by exceptions as follows:
      48              :  * * A zero denominator will raise a std::invalid_argument.
      49              :  * * Division by zero will raise a std::underflow_error.
      50              :  * * Overflowing 128 bits will raise a std::overflow_error.
      51              :  * * Failure to convert a number as specified by the arguments to convert() will
      52              :  * raise a std::domain_error.
      53              :  *
      54              :  */
      55              : 
      56              : 
      57              : class GncRational
      58              : {
      59              : public:
      60              :     /**
      61              :      * Default constructor provides the zero value.
      62              :      */
      63         3421 :     GncRational() : m_num(0), m_den(1) {}
      64              :     /**
      65              :      * GncInt128 constructor. This will take any flavor of built-in integer
      66              :      * thanks to implicit construction of the GncInt128s.
      67              :      */
      68       272860 :     GncRational (GncInt128 num, GncInt128 den) noexcept
      69       272860 :         : m_num(num), m_den(den) {}
      70              :     /** Convenience constructor from the C API's gnc_numeric. */
      71              :     GncRational (gnc_numeric n) noexcept;
      72              :     /** GncNumeric constructor. */
      73              :     GncRational(GncNumeric n) noexcept;
      74              :     GncRational(const GncRational& rhs) = default;
      75              :     GncRational(GncRational&& rhs) = default;
      76              :     GncRational& operator=(const GncRational& rhs) = default;
      77              :     GncRational& operator=(GncRational&& rhs) = default;
      78              :     ~GncRational() = default;
      79              :     /** Report if both members are valid numbers.
      80              :      * \return true if neither numerator nor denominator are Nan or Overflowed.
      81              :      */
      82              :     bool valid() const noexcept;
      83              :     /** Report if either numerator or denominator are too big to fit in an
      84              :      * int64_t.
      85              :      * \return true if either is too big.
      86              :      */
      87              :     bool is_big() const noexcept;
      88              :     /** Conversion operator; use static_cast<gnc_numeric>(foo). */
      89              :     operator gnc_numeric() const noexcept;
      90              :     /** Make a new GncRational with the opposite sign. */
      91              :     GncRational operator-() const noexcept;
      92              :     /**
      93              :      * Return an equivalent fraction with all common factors between the
      94              :      * numerator and the denominator removed.
      95              :      *
      96              :      * @return reduced GncRational
      97              :      */
      98              :     GncRational reduce() const;
      99              :     /**
     100              :      * Round to fit an int64_t, finding the closest possible approximation.
     101              :      *
     102              :      * Throws std::overflow_error if m_den is 1 and m_num is big.
     103              :      * @return rounded GncRational
     104              :      */
     105              :     GncRational round_to_numeric() const;
     106              :     /**
     107              :      * Convert a GncRational to use a new denominator. If rounding is necessary
     108              :      * use the indicated template specification. For example, to use half-up
     109              :      * rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you
     110              :      * specify RoundType::never this will throw std::domain_error if rounding is
     111              :      * required.
     112              :      *
     113              :      * \param new_denom The new denominator to convert the fraction to.
     114              :      * \return A new GncRational having the requested denominator.
     115              :      */
     116              :     template <RoundType RT>
     117         3477 :     GncRational convert (GncInt128 new_denom) const
     118              :     {
     119         3477 :         auto params = prepare_conversion(new_denom);
     120         3477 :         if (new_denom == GNC_DENOM_AUTO)
     121          494 :             new_denom = m_den;
     122         3477 :         if (params.rem == 0)
     123         2292 :             return GncRational(params.num, new_denom);
     124              :         return GncRational(round(params.num, params.den,
     125         1185 :                                  params.rem, RT2T<RT>()), new_denom);
     126              :     }
     127              : 
     128              :     /**
     129              :      * Convert with the specified sigfigs. The resulting denominator depends on
     130              :      * the value of the GncRational, such that the specified significant digits
     131              :      * are retained in the numerator and the denominator is always a power of
     132              :      * 10. This is of rather dubious benefit in an accounting program, but it's
     133              :      * used in several places so it needs to be implemented.
     134              :      *
     135              :      * @param figs The number of digits to use for the numerator.
     136              :      * @return A GncRational with the specified number of digits in the
     137              :      * numerator and the appropriate power-of-ten denominator.
     138              :      */
     139              :     template <RoundType RT>
     140            0 :     GncRational convert_sigfigs(unsigned int figs) const
     141              :     {
     142            0 :         auto new_denom(sigfigs_denom(figs));
     143            0 :         auto params = prepare_conversion(new_denom);
     144            0 :         if (new_denom == 0) //It had better not, but just in case...
     145            0 :             new_denom = 1;
     146            0 :         if (params.rem == 0)
     147            0 :             return GncRational(params.num, new_denom);
     148              :         return GncRational(round(params.num, params.den,
     149            0 :                                 params.rem, RT2T<RT>()), new_denom);
     150              :     }
     151              : 
     152              :     /** Numerator accessor */
     153      1221745 :     GncInt128 num() const noexcept { return m_num; }
     154              :     /** Denominator accessor */
     155      1696148 :     GncInt128 denom() const noexcept { return m_den; }
     156              :     /** @defgroup gnc_rational_mutators
     157              :      *  @{
     158              :      * Standard mutating arithmetic operators.
     159              :      */
     160              :     void operator+=(GncRational b);
     161              :     void operator-=(GncRational b);
     162              :     void operator*=(GncRational b);
     163              :     void operator/=(GncRational b);
     164              :     /** @} */
     165              :     /** Inverts the number, equivalent of /= {1, 1} */
     166              :     GncRational inv() const noexcept;
     167              :     /** Absolute value; return value is always >= 0 and of same magnitude. */
     168              :     GncRational abs() const noexcept;
     169              :     /** Compare function
     170              :      *
     171              :      * @param b GncNumeric or integer value to compare to.
     172              :      * @return -1 if < b, 0 if equal, 1 if > b.
     173              :      */
     174              :     int cmp(GncRational b);
     175              :     int cmp(GncInt128 b) { return cmp(GncRational(b, 1)); }
     176              : 
     177              : private:
     178              :     struct round_param
     179              :     {
     180              :         GncInt128 num;
     181              :         GncInt128 den;
     182              :         GncInt128 rem;
     183              :     };
     184              :     /* Calculates the denominator required to convert to figs sigfigs. Note that
     185              :      * it uses the same powten function that the GncNumeric version does because
     186              :      * 17 significant figures should be plenty.
     187              :      */
     188              :     GncInt128 sigfigs_denom(unsigned figs) const noexcept;
     189              :     /* Calculates a round_param struct to pass to a rounding function that will
     190              :      * finish computing a GncNumeric with the new denominator.
     191              :      */
     192              :     round_param prepare_conversion(GncInt128 new_denom) const;
     193              :     GncInt128 m_num;
     194              :     GncInt128 m_den;
     195              : };
     196              : 
     197              : /**
     198              :  * @return -1 if a < b, 0 if a == b, 1 if a > b.
     199              :  */
     200              : inline int cmp(GncRational a, GncRational b) { return a.cmp(b); }
     201              : inline int cmp(GncRational a, GncInt128 b) { return a.cmp(b); }
     202              : inline int cmp(GncInt128 a, GncRational b) { return GncRational(a, 1).cmp(b); }
     203              : 
     204              : /**
     205              :  * \defgroup gnc_rational_comparison_operators
     206              :  * @{
     207              :  * Standard comparison operators, which do what one would expect.
     208              :  */
     209              : inline bool operator<(GncRational a, GncRational b) { return cmp(a, b) < 0; }
     210              : inline bool operator<(GncRational a, GncInt128 b) { return cmp(a, b) < 0; }
     211              : inline bool operator<(GncInt128 a, GncRational b) { return cmp(a, b) < 0; }
     212              : inline bool operator>(GncRational a, GncRational b) { return cmp(a, b) > 0; }
     213              : inline bool operator>(GncRational a, GncInt128 b) { return cmp(a, b) > 0; }
     214              : inline bool operator>(GncInt128 a, GncRational b) { return cmp(a, b) > 0; }
     215              : inline bool operator==(GncRational a, GncRational b) { return cmp(a, b) == 0; }
     216              : inline bool operator==(GncRational a, GncInt128 b) { return cmp(a, b) == 0; }
     217              : inline bool operator==(GncInt128 a, GncRational b) { return cmp(a, b) == 0; }
     218              : inline bool operator<=(GncRational a, GncRational b) { return cmp(a, b) <= 0; }
     219              : inline bool operator<=(GncRational a, GncInt128 b) { return cmp(a, b) <= 0; }
     220              : inline bool operator<=(GncInt128 a, GncRational b) { return cmp(a, b) <= 0; }
     221              : inline bool operator>=(GncRational a, GncRational b) { return cmp(a, b) >= 0; }
     222              : inline bool operator>=(GncRational a, GncInt128 b) { return cmp(a, b) >= 0; }
     223              : inline bool operator>=(GncInt128 a, GncRational b) { return cmp(a, b) >= 0; }
     224              : inline bool operator!=(GncRational a, GncRational b) { return cmp(a, b) != 0; }
     225              : inline bool operator!=(GncRational a, GncInt128 b) { return cmp(a, b) != 0; }
     226              : inline bool operator!=(GncInt128 a, GncRational b) { return cmp(a, b) != 0; }
     227              : /** @} */
     228              : 
     229              : /**
     230              :  * \defgroup gnc_rational_arithmetic_operators
     231              :  *
     232              :  * Normal arithmetic operators. The class arithmetic operators are implemented
     233              :  * in terms of these operators.
     234              :  *
     235              :  * These operators can throw std::overflow_error, std::underflow_error, or
     236              :  * std::invalid argument as indicated in the class documentation.
     237              :  *
     238              :  * \param a The right-side operand
     239              :  * \param b The left-side operand
     240              :  * \return A GncRational computed from the operation.
     241              :  */
     242              : GncRational operator+(GncRational a, GncRational b);
     243              : inline GncRational operator+(GncRational a, GncInt128 b)
     244              : {
     245              :     return a + GncRational(b, 1);
     246              : }
     247              : inline GncRational operator+(GncInt128 a, GncRational b)
     248              : {
     249              :     return GncRational(a, 1) + b;
     250              : }
     251              : GncRational operator-(GncRational a, GncRational b);
     252              : inline GncRational operator-(GncRational a, GncInt128 b)
     253              : {
     254              :     return a - GncRational(b, 1);
     255              : }
     256              : inline GncRational operator-(GncInt128 a, GncRational b)
     257              : {
     258              :     return GncRational(a, 1) - b;
     259              : }
     260              : GncRational operator*(GncRational a, GncRational b);
     261              : inline GncRational operator*(GncRational a, GncInt128 b)
     262              : {
     263              :     return a * GncRational(b, 1);
     264              : }
     265              : inline GncRational operator*(GncInt128 a, GncRational b)
     266              : {
     267              :     return GncRational(a, 1) * b;
     268              : }
     269              : GncRational operator/(GncRational a, GncRational b);
     270              : inline GncRational operator/(GncRational a, GncInt128 b)
     271              : {
     272              :     return a / GncRational(b, 1);
     273              : }
     274              : inline GncRational operator/(GncInt128 a, GncRational b)
     275              : {
     276              :     return GncRational(a, 1) / b;
     277              : }
     278              : 
     279              : inline std::ostream& operator<<(std::ostream& stream, const GncRational& val) noexcept
     280              : {
     281              :     stream << val.num() << "/" << val.denom();
     282              :     return stream;
     283              : }
     284              : /** @} */
     285              : #endif //__GNC_RATIONAL_HPP__
        

Generated by: LCOV version 2.0-1