GnuCash  5.6-150-g038405b370+
gnc-rational.cpp
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 "gnc-rational.hpp"
25 #include "gnc-numeric.hpp"
26 
27 
29  m_num(n.num()), m_den(n.denom())
30 {
31  if (m_den.isNeg())
32  {
33  m_num *= -m_den;
34  m_den = 1;
35  }
36 }
37 
38 GncRational::GncRational (gnc_numeric n) noexcept :
39  m_num (n.num), m_den (n.denom)
40 {
41  if (m_den.isNeg())
42  {
43  m_num *= -m_den;
44  m_den = 1;
45  }
46 }
47 
48 bool
49 GncRational::valid() const noexcept
50 {
51  if (m_num.valid() && m_den.valid())
52  return true;
53  return false;
54 }
55 
56 bool
57 GncRational::is_big() const noexcept
58 {
59  if (m_num.isBig() || m_den.isBig())
60  return true;
61  return false;
62 }
63 
64 GncRational::operator gnc_numeric () const noexcept
65 {
66  if (!valid())
68  try
69  {
70  return {static_cast<int64_t>(m_num), static_cast<int64_t>(m_den)};
71  }
72  catch (std::overflow_error&)
73  {
75  }
76 }
77 
79 GncRational::operator-() const noexcept
80 {
81  return GncRational(-m_num, m_den);
82 }
83 
85 GncRational::inv () const noexcept
86 {
87  if (m_num == 0)
88  return *this;
89  if (m_num < 0)
90  return GncRational(-m_den, -m_num);
91  return GncRational(m_den, m_num);
92 }
93 
95 GncRational::abs() const noexcept
96 {
97  if (m_num < 0)
98  return -*this;
99  return *this;
100 }
101 
102 void
103 GncRational::operator+=(GncRational b)
104 {
105  GncRational new_val = *this + b;
106  *this = std::move(new_val);
107 }
108 
109 void
110 GncRational::operator-=(GncRational b)
111 {
112  GncRational new_val = *this - b;
113  *this = std::move(new_val);
114 }
115 
116 void
117 GncRational::operator*=(GncRational b)
118 {
119  GncRational new_val = *this * b;
120  *this = std::move(new_val);
121 }
122 
123 void
124 GncRational::operator/=(GncRational b)
125 {
126  GncRational new_val = *this / b;
127  *this = std::move(new_val);
128 }
129 
130 int
132 {
133  if (m_den == b.denom())
134  {
135  auto b_num = b.num();
136  return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
137  }
138  auto gcd = m_den.gcd(b.denom());
139  GncInt128 a_num(m_num * b.denom() / gcd), b_num(b.num() * m_den / gcd);
140  return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
141 }
142 
143 GncRational::round_param
144 GncRational::prepare_conversion (GncInt128 new_denom) const
145 {
146  if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
147  return {m_num, m_den, 0};
148  GncRational conversion(new_denom, m_den);
149  auto red_conv = conversion.reduce();
150  GncInt128 old_num(m_num);
151  auto new_num = old_num * red_conv.num();
152  if (new_num.isOverflow())
153  throw std::overflow_error("Conversion overflow");
154  auto rem = new_num % red_conv.denom();
155  new_num /= red_conv.denom();
156  return {new_num, red_conv.denom(), rem};
157 }
158 
159 GncInt128
160 GncRational::sigfigs_denom(unsigned figs) const noexcept
161 {
162  if (m_num == 0)
163  return 1;
164 
165  auto num_abs = m_num.abs();
166  bool not_frac = num_abs > m_den;
167  int64_t val{ not_frac ? num_abs / m_den : m_den / num_abs };
168  unsigned digits{};
169  while (val >= 10)
170  {
171  ++digits;
172  val /= 10;
173  }
174  return not_frac ?
175  powten(digits < figs ? figs - digits - 1 : 0) :
176  powten(figs + digits);
177 }
178 
181 {
182  auto gcd = m_den.gcd(m_num);
183  if (gcd.isNan() || gcd.isOverflow())
184  throw std::overflow_error("Reduce failed, calculation of gcd overflowed.");
185  return GncRational(m_num / gcd, m_den / gcd);
186 }
187 
190 {
191  unsigned int ll_bits = GncInt128::legbits;
192  if (m_num.isZero())
193  return GncRational(); //Default constructor makes 0/1
194  if (!(m_num.isBig() || m_den.isBig()))
195  return *this;
196  if (m_num.abs() > m_den)
197  {
198  auto quot(m_num / m_den);
199  if (quot.isBig())
200  {
201  std::ostringstream msg;
202  msg << " Cannot be represented as a "
203  << "GncNumeric. Its integer value is too large.\n";
204  throw std::overflow_error(msg.str());
205  }
206  GncRational new_v;
207  while (new_v.num().isZero())
208  {
209  try
210  {
211  new_v = convert<RoundType::half_down>(m_den / (m_num.abs() >> ll_bits));
212  if (new_v.is_big())
213  {
214  --ll_bits;
215  new_v = GncRational();
216  }
217  }
218  catch(const std::overflow_error& err)
219  {
220  --ll_bits;
221  }
222  }
223  return new_v;
224  }
225  auto quot(m_den / m_num);
226  if (quot.isBig())
227  return GncRational(); //Smaller than can be represented as a GncNumeric
228  GncRational new_v;
229  while (new_v.num().isZero())
230  {
231  auto divisor = m_den >> ll_bits;
232  if (m_num.isBig())
233  {
234  GncInt128 oldnum(m_num), num, rem;
235  oldnum.div(divisor, num, rem);
236  auto den = m_den / divisor;
237  num += rem * 2 >= den ? 1 : 0;
238  if (num.isBig() || den.isBig())
239  {
240  --ll_bits;
241  continue;
242  }
243  GncRational new_rational(num, den);
244  return new_rational;
245  }
246  new_v = convert<RoundType::half_down>(m_den / divisor);
247  if (new_v.is_big())
248  {
249  --ll_bits;
250  new_v = GncRational();
251  }
252  }
253  return new_v;
254 }
255 
257 operator+(GncRational a, GncRational b)
258 {
259  if (!(a.valid() && b.valid()))
260  throw std::range_error("Operator+ called with out-of-range operand.");
261  GncInt128 lcm = a.denom().lcm(b.denom());
262  GncInt128 num(a.num() * lcm / a.denom() + b.num() * lcm / b.denom());
263  if (!(lcm.valid() && num.valid()))
264  throw std::overflow_error("Operator+ overflowed.");
265  GncRational retval(num, lcm);
266  return retval;
267 }
268 
270 operator-(GncRational a, GncRational b)
271 {
272  GncRational retval = a + (-b);
273  return retval;
274 }
275 
277 operator*(GncRational a, GncRational b)
278 {
279  if (!(a.valid() && b.valid()))
280  throw std::range_error("Operator* called with out-of-range operand.");
281  GncInt128 num (a.num() * b.num()), den(a.denom() * b.denom());
282  if (!(num.valid() && den.valid()))
283  throw std::overflow_error("Operator* overflowed.");
284  GncRational retval(num, den);
285  return retval;
286 }
287 
289 operator/(GncRational a, GncRational b)
290 {
291  if (!(a.valid() && b.valid()))
292  throw std::range_error("Operator/ called with out-of-range operand.");
293  auto a_num = a.num(), b_num = b.num(), a_den = a.denom(), b_den = b.denom();
294  if (b_num == 0)
295  throw std::underflow_error("Divide by 0.");
296  if (b_num.isNeg())
297  {
298  a_num = -a_num;
299  b_num = -b_num;
300  }
301 
302  /* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
303  * and it's just a_num/b_num.
304  */
305  if (a_den == b_den)
306  return GncRational(a_num, b_num);
307 
308  /* Protect against possibly preventable overflow: */
309  if (a_num.isBig() || a_den.isBig() ||
310  b_num.isBig() || b_den.isBig())
311  {
312  GncInt128 gcd = b_den.gcd(a_den);
313  b_den /= gcd;
314  a_den /= gcd;
315  }
316 
317  GncInt128 num(a_num * b_den), den(a_den * b_num);
318  if (!(num.valid() && den.valid()))
319  throw std::overflow_error("Operator/ overflowed.");
320  return GncRational(num, den);
321 }
bool isBig() const noexcept
Definition: gnc-int128.cpp:253
GncInt128 gcd(GncInt128 b) const noexcept
Computes the Greatest Common Divisor between the object and parameter.
Definition: gnc-int128.cpp:182
GncInt128 denom() const noexcept
Denominator accessor.
GncRational inv() const noexcept
Inverts the number, equivalent of /= {1, 1}.
GncRational reduce() const
Return an equivalent fraction with all common factors between the numerator and the denominator remov...
The primary numeric class for representing amounts and values.
Definition: gnc-numeric.hpp:59
Intermediate result overflow.
Definition: gnc-numeric.h:225
GncInt128 gcd(int64_t a, int64_t b)
Compute the greatest common denominator of two integers.
bool valid() const noexcept
Definition: gnc-int128.cpp:271
GncRational abs() const noexcept
Absolute value; return value is always >= 0 and of same magnitude.
gnc_numeric gnc_numeric_error(GNCNumericErrorCode error_code)
Create a gnc_numeric object that signals the error condition noted by error_code, rather than a numbe...
bool isNan() const noexcept
Definition: gnc-int128.cpp:265
Rational number class using GncInt128 for the numerator and denominator.
bool isZero() const noexcept
Definition: gnc-int128.cpp:277
GncRational round_to_numeric() const
Round to fit an int64_t, finding the closest possible approximation.
GncInt128 lcm(int64_t a, int64_t b)
Compute the least common multiple of two integers.
GncRational()
Default constructor provides the zero value.
bool is_big() const noexcept
Report if either numerator or denominator are too big to fit in an int64_t.
GncInt128 num() const noexcept
Numerator accessor.
int cmp(GncRational b)
Compare function.
bool valid() const noexcept
Report if both members are valid numbers.
GncRational operator-() const noexcept
Make a new GncRational with the opposite sign.
void div(const GncInt128 &d, GncInt128 &q, GncInt128 &r) const noexcept
Computes a quotient and a remainder, passed as reference parameters.
Definition: gnc-int128.cpp:723
#define GNC_DENOM_AUTO
Values that can be passed as the &#39;denom&#39; argument.
Definition: gnc-numeric.h:245
bool isOverflow() const noexcept
Definition: gnc-int128.cpp:259