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 3421 : 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 272860 : GncRational (GncInt128 num, GncInt128 den) noexcept
69 272860 : : 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 3477 : GncRational convert (GncInt128 new_denom) const
118 : {
119 3477 : auto params = prepare_conversion(new_denom);
120 3477 : if (new_denom == GNC_DENOM_AUTO)
121 494 : new_denom = m_den;
122 3477 : if (params.rem == 0)
123 2292 : return GncRational(params.num, new_denom);
124 : return GncRational(round(params.num, params.den,
125 1185 : 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 : return GncRational(round(params.num, params.den,
149 0 : params.rem, RT2T<RT>()), new_denom);
150 : }
151 :
152 : /** Numerator accessor */
153 1221745 : GncInt128 num() const noexcept { return m_num; }
154 : /** Denominator accessor */
155 1696148 : 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__
|