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