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 : : #ifndef __GNC_RATIONAL_HPP__
24 : : #define __GNC_RATIONAL_HPP__
25 : :
26 : : #include "gnc-numeric.h"
27 : : #include "gnc-int128.hpp"
28 : : #include "gnc-rational-rounding.hpp"
29 : :
30 : : class GncNumeric;
31 : : enum class RoundType;
32 : : enum class DenomType;
33 : :
34 : : /** @ingroup QOF
35 : : * @brief Rational number class using GncInt128 for the numerator
36 : : * and denominator.
37 : : *
38 : : * This class provides far greater overflow protection compared to GncNumeric at
39 : : * the expense of doubling the size, so GncNumeric is preferred for storage into
40 : : * objects. Furthermore the backends are not able to store GncRational numbers;
41 : : * storage in SQL would require using BLOBs which would preclude calculations in
42 : : * queries. GncRational exists *primarily* as a more overflow-resistant
43 : : * calculation facility for GncNumeric. It's available for cases where one needs
44 : : * an error instead of an automatically rounded value for a calculation that
45 : : * produces a result that won't fit into an int64 without rounding.
46 : :
47 : : * Errors: Errors are signalled by exceptions as follows:
48 : : * * A zero denominator will raise a std::invalid_argument.
49 : : * * Division by zero will raise a std::underflow_error.
50 : : * * Overflowing 128 bits will raise a std::overflow_error.
51 : : * * Failure to convert a number as specified by the arguments to convert() will
52 : : * raise a std::domain_error.
53 : : *
54 : : */
55 : :
56 : :
57 : : class GncRational
58 : : {
59 : : public:
60 : : /**
61 : : * Default constructor provides the zero value.
62 : : */
63 : 3311 : GncRational() : m_num(0), m_den(1) {}
64 : : /**
65 : : * GncInt128 constructor. This will take any flavor of built-in integer
66 : : * thanks to implicit construction of the GncInt128s.
67 : : */
68 : 468834 : GncRational (GncInt128 num, GncInt128 den) noexcept
69 : 468834 : : m_num(num), m_den(den) {}
70 : : /** Convenience constructor from the C API's gnc_numeric. */
71 : : GncRational (gnc_numeric n) noexcept;
72 : : /** GncNumeric constructor. */
73 : : GncRational(GncNumeric n) noexcept;
74 : : GncRational(const GncRational& rhs) = default;
75 : : GncRational(GncRational&& rhs) = default;
76 : : GncRational& operator=(const GncRational& rhs) = default;
77 : : GncRational& operator=(GncRational&& rhs) = default;
78 : : ~GncRational() = default;
79 : : /** Report if both members are valid numbers.
80 : : * \return true if neither numerator nor denominator are Nan or Overflowed.
81 : : */
82 : : bool valid() const noexcept;
83 : : /** Report if either numerator or denominator are too big to fit in an
84 : : * int64_t.
85 : : * \return true if either is too big.
86 : : */
87 : : bool is_big() const noexcept;
88 : : /** Conversion operator; use static_cast<gnc_numeric>(foo). */
89 : : operator gnc_numeric() const noexcept;
90 : : /** Make a new GncRational with the opposite sign. */
91 : : GncRational operator-() const noexcept;
92 : : /**
93 : : * Return an equivalent fraction with all common factors between the
94 : : * numerator and the denominator removed.
95 : : *
96 : : * @return reduced GncRational
97 : : */
98 : : GncRational reduce() const;
99 : : /**
100 : : * Round to fit an int64_t, finding the closest possible approximation.
101 : : *
102 : : * Throws std::overflow_error if m_den is 1 and m_num is big.
103 : : * @return rounded GncRational
104 : : */
105 : : GncRational round_to_numeric() const;
106 : : /**
107 : : * Convert a GncRational to use a new denominator. If rounding is necessary
108 : : * use the indicated template specification. For example, to use half-up
109 : : * rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you
110 : : * specify RoundType::never this will throw std::domain_error if rounding is
111 : : * required.
112 : : *
113 : : * \param new_denom The new denominator to convert the fraction to.
114 : : * \return A new GncRational having the requested denominator.
115 : : */
116 : : template <RoundType RT>
117 : 6141 : GncRational convert (GncInt128 new_denom) const
118 : : {
119 : 6141 : auto params = prepare_conversion(new_denom);
120 : 6141 : if (new_denom == GNC_DENOM_AUTO)
121 : 443 : new_denom = m_den;
122 : 6141 : if (params.rem == 0)
123 : 4959 : return GncRational(params.num, new_denom);
124 : 1182 : return GncRational(round(params.num, params.den,
125 : 1182 : params.rem, RT2T<RT>()), new_denom);
126 : : }
127 : :
128 : : /**
129 : : * Convert with the specified sigfigs. The resulting denominator depends on
130 : : * the value of the GncRational, such that the specified significant digits
131 : : * are retained in the numerator and the denominator is always a power of
132 : : * 10. This is of rather dubious benefit in an accounting program, but it's
133 : : * used in several places so it needs to be implemented.
134 : : *
135 : : * @param figs The number of digits to use for the numerator.
136 : : * @return A GncRational with the specified number of digits in the
137 : : * numerator and the appropriate power-of-ten denominator.
138 : : */
139 : : template <RoundType RT>
140 : 0 : GncRational convert_sigfigs(unsigned int figs) const
141 : : {
142 : 0 : auto new_denom(sigfigs_denom(figs));
143 : 0 : auto params = prepare_conversion(new_denom);
144 : 0 : if (new_denom == 0) //It had better not, but just in case...
145 : 0 : new_denom = 1;
146 : 0 : if (params.rem == 0)
147 : 0 : return GncRational(params.num, new_denom);
148 : 0 : return GncRational(round(params.num, params.den,
149 : 0 : params.rem, RT2T<RT>()), new_denom);
150 : : }
151 : :
152 : : /** Numerator accessor */
153 : 1656352 : GncInt128 num() const noexcept { return m_num; }
154 : : /** Denominator accessor */
155 : 2387649 : GncInt128 denom() const noexcept { return m_den; }
156 : : /** @defgroup gnc_rational_mutators
157 : : * @{
158 : : * Standard mutating arithmetic operators.
159 : : */
160 : : void operator+=(GncRational b);
161 : : void operator-=(GncRational b);
162 : : void operator*=(GncRational b);
163 : : void operator/=(GncRational b);
164 : : /** @} */
165 : : /** Inverts the number, equivalent of /= {1, 1} */
166 : : GncRational inv() const noexcept;
167 : : /** Absolute value; return value is always >= 0 and of same magnitude. */
168 : : GncRational abs() const noexcept;
169 : : /** Compare function
170 : : *
171 : : * @param b GncNumeric or integer value to compare to.
172 : : * @return -1 if < b, 0 if equal, 1 if > b.
173 : : */
174 : : int cmp(GncRational b);
175 : : int cmp(GncInt128 b) { return cmp(GncRational(b, 1)); }
176 : :
177 : : private:
178 : : struct round_param
179 : : {
180 : : GncInt128 num;
181 : : GncInt128 den;
182 : : GncInt128 rem;
183 : : };
184 : : /* Calculates the denominator required to convert to figs sigfigs. Note that
185 : : * it uses the same powten function that the GncNumeric version does because
186 : : * 17 significant figures should be plenty.
187 : : */
188 : : GncInt128 sigfigs_denom(unsigned figs) const noexcept;
189 : : /* Calculates a round_param struct to pass to a rounding function that will
190 : : * finish computing a GncNumeric with the new denominator.
191 : : */
192 : : round_param prepare_conversion(GncInt128 new_denom) const;
193 : : GncInt128 m_num;
194 : : GncInt128 m_den;
195 : : };
196 : :
197 : : /**
198 : : * @return -1 if a < b, 0 if a == b, 1 if a > b.
199 : : */
200 : : inline int cmp(GncRational a, GncRational b) { return a.cmp(b); }
201 : : inline int cmp(GncRational a, GncInt128 b) { return a.cmp(b); }
202 : : inline int cmp(GncInt128 a, GncRational b) { return GncRational(a, 1).cmp(b); }
203 : :
204 : : /**
205 : : * \defgroup gnc_rational_comparison_operators
206 : : * @{
207 : : * Standard comparison operators, which do what one would expect.
208 : : */
209 : : inline bool operator<(GncRational a, GncRational b) { return cmp(a, b) < 0; }
210 : : inline bool operator<(GncRational a, GncInt128 b) { return cmp(a, b) < 0; }
211 : : inline bool operator<(GncInt128 a, GncRational b) { return cmp(a, b) < 0; }
212 : : inline bool operator>(GncRational a, GncRational b) { return cmp(a, b) > 0; }
213 : : inline bool operator>(GncRational a, GncInt128 b) { return cmp(a, b) > 0; }
214 : : inline bool operator>(GncInt128 a, GncRational b) { return cmp(a, b) > 0; }
215 : : inline bool operator==(GncRational a, GncRational b) { return cmp(a, b) == 0; }
216 : : inline bool operator==(GncRational a, GncInt128 b) { return cmp(a, b) == 0; }
217 : : inline bool operator==(GncInt128 a, GncRational b) { return cmp(a, b) == 0; }
218 : : inline bool operator<=(GncRational a, GncRational b) { return cmp(a, b) <= 0; }
219 : : inline bool operator<=(GncRational a, GncInt128 b) { return cmp(a, b) <= 0; }
220 : : inline bool operator<=(GncInt128 a, GncRational b) { return cmp(a, b) <= 0; }
221 : : inline bool operator>=(GncRational a, GncRational b) { return cmp(a, b) >= 0; }
222 : : inline bool operator>=(GncRational a, GncInt128 b) { return cmp(a, b) >= 0; }
223 : : inline bool operator>=(GncInt128 a, GncRational b) { return cmp(a, b) >= 0; }
224 : : inline bool operator!=(GncRational a, GncRational b) { return cmp(a, b) != 0; }
225 : : inline bool operator!=(GncRational a, GncInt128 b) { return cmp(a, b) != 0; }
226 : : inline bool operator!=(GncInt128 a, GncRational b) { return cmp(a, b) != 0; }
227 : : /** @} */
228 : :
229 : : /**
230 : : * \defgroup gnc_rational_arithmetic_operators
231 : : *
232 : : * Normal arithmetic operators. The class arithmetic operators are implemented
233 : : * in terms of these operators.
234 : : *
235 : : * These operators can throw std::overflow_error, std::underflow_error, or
236 : : * std::invalid argument as indicated in the class documentation.
237 : : *
238 : : * \param a The right-side operand
239 : : * \param b The left-side operand
240 : : * \return A GncRational computed from the operation.
241 : : */
242 : : GncRational operator+(GncRational a, GncRational b);
243 : : inline GncRational operator+(GncRational a, GncInt128 b)
244 : : {
245 : : return a + GncRational(b, 1);
246 : : }
247 : : inline GncRational operator+(GncInt128 a, GncRational b)
248 : : {
249 : : return GncRational(a, 1) + b;
250 : : }
251 : : GncRational operator-(GncRational a, GncRational b);
252 : : inline GncRational operator-(GncRational a, GncInt128 b)
253 : : {
254 : : return a - GncRational(b, 1);
255 : : }
256 : : inline GncRational operator-(GncInt128 a, GncRational b)
257 : : {
258 : : return GncRational(a, 1) - b;
259 : : }
260 : : GncRational operator*(GncRational a, GncRational b);
261 : : inline GncRational operator*(GncRational a, GncInt128 b)
262 : : {
263 : : return a * GncRational(b, 1);
264 : : }
265 : : inline GncRational operator*(GncInt128 a, GncRational b)
266 : : {
267 : : return GncRational(a, 1) * b;
268 : : }
269 : : GncRational operator/(GncRational a, GncRational b);
270 : : inline GncRational operator/(GncRational a, GncInt128 b)
271 : : {
272 : : return a / GncRational(b, 1);
273 : : }
274 : : inline GncRational operator/(GncInt128 a, GncRational b)
275 : : {
276 : : return GncRational(a, 1) / b;
277 : : }
278 : :
279 : : inline std::ostream& operator<<(std::ostream& stream, const GncRational& val) noexcept
280 : : {
281 : : stream << val.num() << "/" << val.denom();
282 : : return stream;
283 : : }
284 : : /** @} */
285 : : #endif //__GNC_RATIONAL_HPP__
|