GnuCash c935c2f+
Loading...
Searching...
No Matches
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
39GncRational::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
49bool
50GncRational::valid() const noexcept
51{
52 if (m_num.valid() && m_den.valid())
53 return true;
54 return false;
55}
56
57bool
58GncRational::is_big() const noexcept
59{
60 if (m_num.isBig() || m_den.isBig())
61 return true;
62 return false;
63}
64
65GncRational::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
80GncRational::operator-() const noexcept
81{
82 return GncRational(-m_num, m_den);
83}
84
86GncRational::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
96GncRational::abs() const noexcept
97{
98 if (m_num < 0)
99 return -*this;
100 return *this;
101}
102
103void
104GncRational::operator+=(GncRational b)
105{
106 GncRational new_val = *this + b;
107 *this = std::move(new_val);
108}
109
110void
111GncRational::operator-=(GncRational b)
112{
113 GncRational new_val = *this - b;
114 *this = std::move(new_val);
115}
116
117void
118GncRational::operator*=(GncRational b)
119{
120 GncRational new_val = *this * b;
121 *this = std::move(new_val);
122}
123
124void
125GncRational::operator/=(GncRational b)
126{
127 GncRational new_val = *this / b;
128 *this = std::move(new_val);
129}
130
131int
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
144GncRational::round_param
145GncRational::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
161GncRational::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
258operator+(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
271operator-(GncRational a, GncRational b)
272{
273 GncRational retval = a + (-b);
274 return retval;
275}
276
278operator*(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
290operator/(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}
void div(const GncInt128 &d, GncInt128 &q, GncInt128 &r) const noexcept
Computes a quotient and a remainder, passed as reference parameters.
bool isOverflow() const noexcept
bool isZero() const noexcept
bool valid() const noexcept
bool isBig() const noexcept
GncInt128 gcd(GncInt128 b) const noexcept
Computes the Greatest Common Divisor between the object and parameter.
bool isNan() const noexcept
The primary numeric class for representing amounts and values.
Rational number class using GncInt128 for the numerator and denominator.
bool valid() const noexcept
Report if both members are valid numbers.
GncRational abs() const noexcept
Absolute value; return value is always >= 0 and of same magnitude.
GncRational inv() const noexcept
Inverts the number, equivalent of /= {1, 1}.
GncInt128 denom() const noexcept
Denominator accessor.
GncInt128 num() const noexcept
Numerator accessor.
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.
GncRational round_to_numeric() const
Round to fit an int64_t, finding the closest possible approximation.
int cmp(GncRational b)
Compare function.
GncRational operator-() const noexcept
Make a new GncRational with the opposite sign.
GncRational reduce() const
Return an equivalent fraction with all common factors between the numerator and the denominator remov...
GncInt128 lcm(int64_t a, int64_t b)
Compute the least common multiple of two integers.
GncInt128 gcd(int64_t a, int64_t b)
Compute the greatest common denominator of two integers.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.
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...
@ GNC_ERROR_OVERFLOW
Intermediate result overflow.