LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-rational.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 63.1 % 176 111
Test Date: 2026-11-02 18:19:43 Functions: 66.7 % 21 14
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              : #include <sstream>
      24              : #include <cstdint>
      25              : #include "gnc-rational.hpp"
      26              : #include "gnc-numeric.hpp"
      27              : 
      28              : 
      29       394068 : GncRational::GncRational(GncNumeric n) noexcept :
      30       394068 :     m_num(n.num()), m_den(n.denom())
      31              : {
      32       394068 :     if (m_den.isNeg())
      33              :     {
      34            0 :         m_num *= -m_den;
      35            0 :         m_den = 1;
      36              :     }
      37       394068 : }
      38              : 
      39        19462 : GncRational::GncRational (gnc_numeric n) noexcept :
      40        19462 :     m_num (n.num), m_den (n.denom)
      41              : {
      42        19462 :     if (m_den.isNeg())
      43              :     {
      44            0 :           m_num *= -m_den;
      45            0 :           m_den = 1;
      46              :     }
      47        19462 : }
      48              : 
      49              : bool
      50       417197 : GncRational::valid() const noexcept
      51              : {
      52       417197 :     if (m_num.valid() && m_den.valid())
      53       417197 :         return true;
      54            0 :     return false;
      55              : }
      56              : 
      57              : bool
      58         2800 : GncRational::is_big() const noexcept
      59              : {
      60         2800 :     if (m_num.isBig() || m_den.isBig())
      61           33 :         return true;
      62         2767 :     return false;
      63              : }
      64              : 
      65         9977 : GncRational::operator gnc_numeric () const noexcept
      66              : {
      67         9977 :     if (!valid())
      68            0 :         return gnc_numeric_error(GNC_ERROR_OVERFLOW);
      69              :     try
      70              :     {
      71         9977 :         return {static_cast<int64_t>(m_num), static_cast<int64_t>(m_den)};
      72              :     }
      73            0 :     catch (std::overflow_error&)
      74              :     {
      75            0 :         return gnc_numeric_error (GNC_ERROR_OVERFLOW);
      76            0 :     }
      77              : }
      78              : 
      79              : GncRational
      80            7 : GncRational::operator-() const noexcept
      81              : {
      82            7 :     return GncRational(-m_num, m_den);
      83              : }
      84              : 
      85              : GncRational
      86            0 : GncRational::inv () const noexcept
      87              : {
      88            0 :     if (m_num == 0)
      89            0 :         return *this;
      90            0 :     if (m_num < 0)
      91            0 :         return GncRational(-m_den, -m_num);
      92            0 :     return GncRational(m_den, m_num);
      93              : }
      94              : 
      95              : GncRational
      96            0 : GncRational::abs() const noexcept
      97              : {
      98            0 :     if (m_num < 0)
      99            0 :         return -*this;
     100            0 :     return *this;
     101              : }
     102              : 
     103              : void
     104            0 : GncRational::operator+=(GncRational b)
     105              : {
     106            0 :     GncRational new_val = *this + b;
     107            0 :     *this = std::move(new_val);
     108            0 : }
     109              : 
     110              : void
     111            0 : GncRational::operator-=(GncRational b)
     112              : {
     113            0 :     GncRational new_val = *this - b;
     114            0 :     *this = std::move(new_val);
     115            0 : }
     116              : 
     117              : void
     118            0 : GncRational::operator*=(GncRational b)
     119              : {
     120            0 :     GncRational new_val = *this * b;
     121            0 :     *this = std::move(new_val);
     122            0 : }
     123              : 
     124              : void
     125            0 : GncRational::operator/=(GncRational b)
     126              : {
     127            0 :     GncRational new_val = *this / b;
     128            0 :     *this = std::move(new_val);
     129            0 : }
     130              : 
     131              : int
     132         2612 : GncRational::cmp(GncRational b)
     133              : {
     134         2612 :     if (m_den == b.denom())
     135              :     {
     136            0 :         auto b_num = b.num();
     137            0 :         return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
     138              :     }
     139         2612 :     auto gcd = m_den.gcd(b.denom());
     140         2612 :     GncInt128 a_num(m_num * b.denom() / gcd), b_num(b.num() * m_den / gcd);
     141         2612 :     return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
     142              : }
     143              : 
     144              : GncRational::round_param
     145         3477 : GncRational::prepare_conversion (GncInt128 new_denom) const
     146              : {
     147         3477 :     if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
     148          574 :         return {m_num, m_den, 0};
     149         2903 :     GncRational conversion(new_denom, m_den);
     150         2903 :     auto red_conv = conversion.reduce();
     151         2903 :     GncInt128 old_num(m_num);
     152         2903 :     auto new_num = old_num * red_conv.num();
     153         2903 :     if (new_num.isOverflow())
     154            0 :         throw std::overflow_error("Conversion overflow");
     155         2903 :     auto rem = new_num % red_conv.denom();
     156         2903 :     new_num /= red_conv.denom();
     157         2903 :     return {new_num, red_conv.denom(), rem};
     158              : }
     159              : 
     160              : GncInt128
     161            0 : GncRational::sigfigs_denom(unsigned figs) const noexcept
     162              : {
     163            0 :     if (m_num == 0)
     164            0 :         return 1;
     165              : 
     166            0 :     auto num_abs = m_num.abs();
     167            0 :     bool not_frac = num_abs > m_den;
     168            0 :     int64_t val{ not_frac ? num_abs / m_den : m_den / num_abs };
     169            0 :     unsigned digits{};
     170            0 :     while (val >= 10)
     171              :     {
     172            0 :         ++digits;
     173            0 :         val /= 10;
     174              :     }
     175            0 :     return not_frac ? 
     176            0 :             powten(digits < figs ? figs - digits - 1 : 0) : 
     177            0 :             powten(figs + digits);
     178              : }
     179              : 
     180              : GncRational
     181        34970 : GncRational::reduce() const
     182              : {
     183        34970 :     auto gcd = m_den.gcd(m_num);
     184        34970 :     if (gcd.isNan() || gcd.isOverflow())
     185            0 :         throw std::overflow_error("Reduce failed, calculation of gcd overflowed.");
     186        69940 :     return GncRational(m_num / gcd, m_den / gcd);
     187              : }
     188              : 
     189              : GncRational
     190         6806 : GncRational::round_to_numeric() const
     191              : {
     192         6806 :     unsigned int ll_bits = GncInt128::legbits;
     193         6806 :     if (m_num.isZero())
     194         3360 :         return GncRational(); //Default constructor makes 0/1
     195         3446 :     if (!(m_num.isBig() || m_den.isBig()))
     196         3390 :         return *this;
     197           56 :     if (m_num.abs() > m_den)
     198              :     {
     199           51 :         auto quot(m_num / m_den);
     200           51 :         if (quot.isBig())
     201              :         {
     202           28 :             std::ostringstream msg;
     203              :             msg << " Cannot be represented as a "
     204           28 :                 << "GncNumeric. Its integer value is too large.\n";
     205           28 :             throw std::overflow_error(msg.str());
     206           28 :         }
     207           23 :         GncRational new_v;
     208           75 :         while (new_v.num().isZero())
     209              :         {
     210              :             try
     211              :             {
     212           52 :                 new_v = convert<RoundType::half_down>(m_den / (m_num.abs() >> ll_bits));
     213           52 :                 if (new_v.is_big())
     214              :                 {
     215           29 :                     --ll_bits;
     216           29 :                     new_v = GncRational();
     217              :                 }
     218              :             }
     219            0 :             catch(const std::overflow_error& err)
     220              :             {
     221            0 :                 --ll_bits;
     222            0 :             }
     223              :         }
     224           23 :         return new_v;
     225              :     }
     226            5 :     auto quot(m_den / m_num);
     227            5 :     if (quot.isBig())
     228            0 :         return GncRational(); //Smaller than can be represented as a GncNumeric
     229            5 :     GncRational new_v;
     230           17 :     while (new_v.num().isZero())
     231              :     {
     232           15 :         auto divisor = m_den >> ll_bits;
     233           15 :         if (m_num.isBig())
     234              :         {
     235            9 :             GncInt128 oldnum(m_num), num, rem;
     236            9 :             oldnum.div(divisor, num, rem);
     237            9 :             auto den = m_den / divisor;
     238            9 :             num += rem * 2 >= den ? 1 : 0;
     239            9 :             if (num.isBig() || den.isBig())
     240              :             {
     241            6 :                 --ll_bits;
     242            6 :                 continue;
     243              :             }
     244            3 :             GncRational new_rational(num, den);
     245            3 :             return new_rational;
     246              :         }
     247            6 :         new_v = convert<RoundType::half_down>(m_den / divisor);
     248            6 :         if (new_v.is_big())
     249              :         {
     250            4 :             --ll_bits;
     251            4 :             new_v = GncRational();
     252              :         }
     253              :     }
     254            2 :     return new_v;
     255              : }
     256              : 
     257              : GncRational
     258       197247 : operator+(GncRational a, GncRational b)
     259              : {
     260       197247 :     if (!(a.valid() && b.valid()))
     261            0 :         throw std::range_error("Operator+ called with out-of-range operand.");
     262       197247 :     GncInt128 lcm = a.denom().lcm(b.denom());
     263       197247 :     GncInt128 num(a.num() * lcm / a.denom() + b.num() * lcm / b.denom());
     264       197247 :     if (!(lcm.valid() && num.valid()))
     265            0 :         throw std::overflow_error("Operator+ overflowed.");
     266       197247 :     GncRational retval(num, lcm);
     267       394494 :     return retval;
     268              : }
     269              : 
     270              : GncRational
     271            7 : operator-(GncRational a, GncRational b)
     272              : {
     273            7 :     GncRational retval = a + (-b);
     274            7 :     return retval;
     275              : }
     276              : 
     277              : GncRational
     278         3171 : operator*(GncRational a, GncRational b)
     279              : {
     280         3171 :     if (!(a.valid() && b.valid()))
     281            0 :         throw std::range_error("Operator* called with out-of-range operand.");
     282         3171 :     GncInt128 num (a.num() * b.num()), den(a.denom() * b.denom());
     283         3171 :     if (!(num.valid() && den.valid()))
     284            0 :         throw std::overflow_error("Operator* overflowed.");
     285         3171 :     GncRational retval(num, den);
     286         6342 :     return retval;
     287              : }
     288              : 
     289              : GncRational
     290         1821 : operator/(GncRational a, GncRational b)
     291              : {
     292         1821 :     if (!(a.valid() && b.valid()))
     293            0 :         throw std::range_error("Operator/ called with out-of-range operand.");
     294         1821 :     auto a_num = a.num(), b_num = b.num(), a_den = a.denom(), b_den = b.denom();
     295         1821 :     if (b_num == 0)
     296            1 :         throw std::underflow_error("Divide by 0.");
     297         1820 :     if (b_num.isNeg())
     298              :     {
     299          100 :         a_num = -a_num;
     300          100 :         b_num = -b_num;
     301              :     }
     302              : 
     303              :    /* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
     304              :      * and it's just a_num/b_num.
     305              :      */
     306         1820 :     if (a_den == b_den)
     307         1382 :         return GncRational(a_num, b_num);
     308              : 
     309              :     /* Protect against possibly preventable overflow: */
     310         1314 :     if (a_num.isBig() || a_den.isBig() ||
     311         1314 :         b_num.isBig() || b_den.isBig())
     312              :     {
     313            0 :         GncInt128 gcd = b_den.gcd(a_den);
     314            0 :         b_den /= gcd;
     315            0 :         a_den /= gcd;
     316              :     }
     317              : 
     318          438 :     GncInt128 num(a_num * b_den), den(a_den * b_num);
     319          438 :     if (!(num.valid() && den.valid()))
     320            0 :         throw std::overflow_error("Operator/ overflowed.");
     321          438 :     return GncRational(num, den);
     322              : }
        

Generated by: LCOV version 2.0-1