LCOV - code coverage report
Current view: top level - libgnucash/engine - gnc-rational.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 63.4 % 175 111
Test Date: 2025-02-07 16:25:45 Functions: 66.7 % 21 14
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     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                 :      515742 : GncRational::GncRational(GncNumeric n) noexcept :
      30                 :      515742 :     m_num(n.num()), m_den(n.denom())
      31                 :             : {
      32                 :      515742 :     if (m_den.isNeg())
      33                 :             :     {
      34                 :           0 :         m_num *= -m_den;
      35                 :           0 :         m_den = 1;
      36                 :             :     }
      37                 :      515742 : }
      38                 :             : 
      39                 :       23443 : GncRational::GncRational (gnc_numeric n) noexcept :
      40                 :       23443 :     m_num (n.num), m_den (n.denom)
      41                 :             : {
      42                 :       23443 :     if (m_den.isNeg())
      43                 :             :     {
      44                 :           0 :           m_num *= -m_den;
      45                 :           0 :           m_den = 1;
      46                 :             :     }
      47                 :       23443 : }
      48                 :             : 
      49                 :             : bool
      50                 :      544904 : GncRational::valid() const noexcept
      51                 :             : {
      52                 :      544904 :     if (m_num.valid() && m_den.valid())
      53                 :      544904 :         return true;
      54                 :           0 :     return false;
      55                 :             : }
      56                 :             : 
      57                 :             : bool
      58                 :        5062 : GncRational::is_big() const noexcept
      59                 :             : {
      60                 :        5062 :     if (m_num.isBig() || m_den.isBig())
      61                 :          33 :         return true;
      62                 :        5029 :     return false;
      63                 :             : }
      64                 :             : 
      65                 :       11938 : GncRational::operator gnc_numeric () const noexcept
      66                 :             : {
      67                 :       11938 :     if (!valid())
      68                 :           0 :         return gnc_numeric_error(GNC_ERROR_OVERFLOW);
      69                 :             :     try
      70                 :             :     {
      71                 :       11938 :         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                 :        2670 : GncRational::cmp(GncRational b)
     133                 :             : {
     134                 :        2670 :     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                 :        2670 :     auto gcd = m_den.gcd(b.denom());
     140                 :        2670 :     GncInt128 a_num(m_num * b.denom() / gcd), b_num(b.num() * m_den / gcd);
     141                 :        2670 :     return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
     142                 :             : }
     143                 :             : 
     144                 :             : GncRational::round_param
     145                 :        6141 : GncRational::prepare_conversion (GncInt128 new_denom) const
     146                 :             : {
     147                 :        6141 :     if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
     148                 :         518 :         return {m_num, m_den, 0};
     149                 :        5623 :     GncRational conversion(new_denom, m_den);
     150                 :        5623 :     auto red_conv = conversion.reduce();
     151                 :        5623 :     GncInt128 old_num(m_num);
     152                 :        5623 :     auto new_num = old_num * red_conv.num();
     153                 :        5623 :     if (new_num.isOverflow())
     154                 :           0 :         throw std::overflow_error("Conversion overflow");
     155                 :        5623 :     auto rem = new_num % red_conv.denom();
     156                 :        5623 :     new_num /= red_conv.denom();
     157                 :        5623 :     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                 :             :     return not_frac ? 
     176                 :           0 :             powten(digits < figs ? figs - digits - 1 : 0) : 
     177                 :           0 :             powten(figs + digits);
     178                 :             : }
     179                 :             : 
     180                 :             : GncRational
     181                 :      101611 : GncRational::reduce() const
     182                 :             : {
     183                 :      101611 :     auto gcd = m_den.gcd(m_num);
     184                 :      101611 :     if (gcd.isNan() || gcd.isOverflow())
     185                 :           0 :         throw std::overflow_error("Reduce failed, calculation of gcd overflowed.");
     186                 :      203222 :     return GncRational(m_num / gcd, m_den / gcd);
     187                 :             : }
     188                 :             : 
     189                 :             : GncRational
     190                 :        6564 : GncRational::round_to_numeric() const
     191                 :             : {
     192                 :        6564 :     unsigned int ll_bits = GncInt128::legbits;
     193                 :        6564 :     if (m_num.isZero())
     194                 :        3250 :         return GncRational(); //Default constructor makes 0/1
     195                 :        3314 :     if (!(m_num.isBig() || m_den.isBig()))
     196                 :        3258 :         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                 :      257107 : operator+(GncRational a, GncRational b)
     259                 :             : {
     260                 :      257107 :     if (!(a.valid() && b.valid()))
     261                 :           0 :         throw std::range_error("Operator+ called with out-of-range operand.");
     262                 :      257107 :     GncInt128 lcm = a.denom().lcm(b.denom());
     263                 :      257107 :     GncInt128 num(a.num() * lcm / a.denom() + b.num() * lcm / b.denom());
     264                 :      257107 :     if (!(lcm.valid() && num.valid()))
     265                 :           0 :         throw std::overflow_error("Operator+ overflowed.");
     266                 :      257107 :     GncRational retval(num, lcm);
     267                 :      514214 :     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                 :        5104 : operator*(GncRational a, GncRational b)
     279                 :             : {
     280                 :        5104 :     if (!(a.valid() && b.valid()))
     281                 :           0 :         throw std::range_error("Operator* called with out-of-range operand.");
     282                 :        5104 :     GncInt128 num (a.num() * b.num()), den(a.denom() * b.denom());
     283                 :        5104 :     if (!(num.valid() && den.valid()))
     284                 :           0 :         throw std::overflow_error("Operator* overflowed.");
     285                 :        5104 :     GncRational retval(num, den);
     286                 :       10208 :     return retval;
     287                 :             : }
     288                 :             : 
     289                 :             : GncRational
     290                 :        1770 : operator/(GncRational a, GncRational b)
     291                 :             : {
     292                 :        1770 :     if (!(a.valid() && b.valid()))
     293                 :           0 :         throw std::range_error("Operator/ called with out-of-range operand.");
     294                 :        1770 :     auto a_num = a.num(), b_num = b.num(), a_den = a.denom(), b_den = b.denom();
     295                 :        1770 :     if (b_num == 0)
     296                 :           1 :         throw std::underflow_error("Divide by 0.");
     297                 :        1769 :     if (b_num.isNeg())
     298                 :             :     {
     299                 :         142 :         a_num = -a_num;
     300                 :         142 :         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                 :        1769 :     if (a_den == b_den)
     307                 :        1369 :         return GncRational(a_num, b_num);
     308                 :             : 
     309                 :             :     /* Protect against possibly preventable overflow: */
     310                 :        1200 :     if (a_num.isBig() || a_den.isBig() ||
     311                 :        1200 :         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                 :         400 :     GncInt128 num(a_num * b_den), den(a_den * b_num);
     319                 :         400 :     if (!(num.valid() && den.valid()))
     320                 :           0 :         throw std::overflow_error("Operator/ overflowed.");
     321                 :         400 :     return GncRational(num, den);
     322                 :             : }
        

Generated by: LCOV version 2.0-1