Branch data Line data Source code
1 : : /********************************************************************
2 : : * gnc-numeric.hpp - A rational number library for int64 *
3 : : * Copyright 2017 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_NUMERIC_HPP__
24 : : #define __GNC_NUMERIC_HPP__
25 : :
26 : : #include <string>
27 : : #include <iostream>
28 : : #include <locale>
29 : : #include <typeinfo> // For std::bad_cast exception
30 : : #include <cstdint>
31 : : #include "gnc-rational-rounding.hpp"
32 : :
33 : : class GncRational;
34 : :
35 : : /**@ingroup QOF
36 : : * @brief The primary numeric class for representing amounts and values.
37 : : *
38 : : * Calculations are generally performed in 128-bit (by converting to
39 : : * GncRational) and reducing the result. If the result would overflow a 64-bit
40 : : * representation then the GncNumeric(GncRational&) constructor will call
41 : : * GncRational::round_to_numeric() to get the value to fit. It will not raise an
42 : : * exception, so in the unlikely event that you need an error instead of
43 : : * rounding, use GncRational directly.
44 : : *
45 : : * Errors: Errors are signalled by exceptions as follows:
46 : : * * A zero denominator will raise a std::invalid_argument.
47 : : * * Division by zero will raise a std::underflow_error.
48 : : * * Overflowing 128 bits will raise a std::overflow_error.
49 : : * * Failure to convert a number as specified by the arguments to convert() will
50 : : * raise a std::domain_error.
51 : : *
52 : : * Rounding Policy: GncNumeric provides a convert() member function that object
53 : : * amount and value setters (and *only* those functions!) should call to set a
54 : : * number which is represented in the commodity's SCU. Since SCUs are seldom 18
55 : : * digits the convert may result in rounding, which will be performed in the
56 : : * method specified by the arguments passed to convert(). Overflows may result
57 : : * in internal rounding as described earlier.
58 : : */
59 : :
60 : : class GncNumeric
61 : : {
62 : : public:
63 : : /**
64 : : * Default constructor provides the zero value.
65 : : */
66 : 3199 : GncNumeric() : m_num (0), m_den(1) {}
67 : : /**
68 : : * Integer constructor.
69 : : *
70 : : * Unfortunately specifying a default for denom causes ambiguity errors with
71 : : * the other single-argument constructors on older versions of gcc, so one
72 : : * must always specify both arguments.
73 : : *
74 : : * \param num The Numerator
75 : : * \param denom The Denominator
76 : : */
77 : 505147 : GncNumeric(int64_t num, int64_t denom) :
78 : 505147 : m_num(num), m_den(denom) {
79 : 505147 : if (denom == 0)
80 : 0 : throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
81 : 505147 : }
82 : : /**
83 : : * GncRational constructor.
84 : : *
85 : : * This constructor will round rr's GncInt128s to fit into the int64_t
86 : : * members using RoundType::half-down.
87 : : *
88 : : * \param rr A GncRational.
89 : : */
90 : : GncNumeric(GncRational rr);
91 : : /**
92 : : * gnc_numeric constructor, used for interfacing old code. This function
93 : : * should not be used outside of gnc-numeric.cpp.
94 : : *
95 : : * \param in A gnc_numeric.
96 : : */
97 : 867276 : GncNumeric(gnc_numeric in) : m_num(in.num), m_den(in.denom)
98 : : {
99 : 867276 : if (in.denom == 0)
100 : 0 : throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
101 : : /* gnc_numeric has a dumb convention that a negative denominator means
102 : : * to multiply the numerator by the denominator instead of dividing.
103 : : */
104 : 867276 : if (in.denom < 0)
105 : : {
106 : 0 : m_num *= -in.denom;
107 : 0 : m_den = 1;
108 : : }
109 : 867276 : }
110 : : /**
111 : : * Double constructor.
112 : : *
113 : : * @param d The double to be converted. In order to fit in an int64_t, its
114 : : * absolute value must be < 1e18; if its absolute value is < 1e-18 it will
115 : : * be represented as 0, though for practical purposes nearly all commodities
116 : : * will round to zero at 1e-9 or larger.
117 : : */
118 : : GncNumeric(double d);
119 : :
120 : : /**
121 : : * String constructor.
122 : : *
123 : : * Accepts integer values in decimal and hexadecimal. Does not accept
124 : : * thousands separators. If the string contains a '/' it is taken to
125 : : * separate the numerator and denominator; if it contains either a '.' or a
126 : : * ',' it is taken as a decimal point and the integers on either side will
127 : : * be combined and a denominator will be the appropriate power of 10. If
128 : : * neither is present the number will be treated as an integer and m_den
129 : : * will be set to 1.
130 : : *
131 : : * Whitespace around a '/' is ignored. A correctly-formatted number will be
132 : : * extracted from a larger string.
133 : : *
134 : : * Numbers that cannot be represented with int64_ts will throw
135 : : * std::out_of_range unless a decimal point is found (see above). A 0
136 : : * denominator will cause the constructor to throw std::underflow_error. An
137 : : * empty string or one which contains no recognizable number will result in
138 : : * std::invalid_argument.
139 : : */
140 : : GncNumeric(const std::string& str, bool autoround=false);
141 : : GncNumeric(const GncNumeric& rhs) = default;
142 : : GncNumeric(GncNumeric&& rhs) = default;
143 : : GncNumeric& operator=(const GncNumeric& rhs) = default;
144 : : GncNumeric& operator=(GncNumeric&& rhs) = default;
145 : : ~GncNumeric() = default;
146 : : /**
147 : : * gnc_numeric conversion. Use static_cast<gnc_numeric>(foo)
148 : : */
149 : : operator gnc_numeric() const noexcept;
150 : : /**
151 : : * double conversion. Use static_cast<double>(foo)
152 : : */
153 : : operator double() const noexcept;
154 : :
155 : : /**
156 : : * Accessor for numerator value.
157 : : */
158 : 1106058 : int64_t num() const noexcept { return m_num; }
159 : : /**
160 : : * Accessor for denominator value.
161 : : */
162 : 519620 : int64_t denom() const noexcept { return m_den; }
163 : : /**
164 : : * @return A GncNumeric with the opposite sign.
165 : : */
166 : : GncNumeric operator-() const noexcept;
167 : : /**
168 : : * @return 0 if this == 0 else 1/this.
169 : : */
170 : : GncNumeric inv() const noexcept;
171 : : /**
172 : : * @return -this if this < 0 else this.
173 : : */
174 : : GncNumeric abs() const noexcept;
175 : : /**
176 : : * Return an equivalent fraction with all common factors between the
177 : : * numerator and the denominator removed.
178 : : *
179 : : * @return reduced GncNumeric
180 : : */
181 : : GncNumeric reduce() const noexcept;
182 : : /**
183 : : * Convert a GncNumeric to use a new denominator. If rounding is necessary
184 : : * use the indicated template specification. For example, to use half-up
185 : : * rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you
186 : : * specify RoundType::never this will throw std::domain_error if rounding is
187 : : * required.
188 : : *
189 : : * \param new_denom The new denominator to convert the fraction to.
190 : : * \return A new GncNumeric having the requested denominator.
191 : : */
192 : : template <RoundType RT>
193 : 503089 : GncNumeric convert(int64_t new_denom) const
194 : : {
195 : 503089 : auto params = prepare_conversion(new_denom);
196 : 503074 : if (new_denom == GNC_DENOM_AUTO)
197 : 327635 : new_denom = m_den;
198 : 503074 : if (params.rem == 0)
199 : 496842 : return GncNumeric(params.num, new_denom);
200 : 6232 : return GncNumeric(round(params.num, params.den,
201 : 6232 : params.rem, RT2T<RT>()), new_denom);
202 : : }
203 : :
204 : : /**
205 : : * Convert with the specified sigfigs. The resulting denominator depends on
206 : : * the value of the GncNumeric, such that the specified significant digits
207 : : * are retained in the numerator and the denominator is always a power of
208 : : * 10. This is of rather dubious benefit in an accounting program, but it's
209 : : * used in several places so it needs to be implemented.
210 : : *
211 : : * @param figs The number of digits to use for the numerator.
212 : : * @return A GncNumeric with the specified number of digits in the numerator
213 : : * and the appropriate power-of-ten denominator.
214 : : */
215 : : template <RoundType RT>
216 : 33 : GncNumeric convert_sigfigs(unsigned int figs) const
217 : : {
218 : 33 : auto new_denom(sigfigs_denom(figs));
219 : 33 : auto params = prepare_conversion(new_denom);
220 : 33 : if (new_denom == 0) //It had better not, but just in case...
221 : 0 : new_denom = 1;
222 : 33 : if (params.rem == 0)
223 : 32 : return GncNumeric(params.num, new_denom);
224 : 1 : return GncNumeric(round(params.num, params.den,
225 : 1 : params.rem, RT2T<RT>()), new_denom);
226 : : }
227 : : /**
228 : : * Return a string representation of the GncNumeric. See operator<< for
229 : : * details.
230 : : */
231 : : std::string to_string() const noexcept;
232 : : /**
233 : : * @return true if the denominator is a power of ten, false otherwise.
234 : : */
235 : : bool is_decimal() const noexcept;
236 : : /**
237 : : * Convert the numeric to have a power-of-10 denominator if possible without
238 : : * rounding. Throws a std::range_error on failure; the message will explain
239 : : * the details.
240 : : *
241 : : * @param max_places exponent of the largest permissible denominator.
242 : : * @return A GncNumeric value with the new denominator.
243 : : */
244 : : GncNumeric to_decimal(unsigned int max_places=17) const;
245 : : /**
246 : : * \defgroup gnc_numeric_mutators
247 : : *
248 : : * These are the standard mutating operators. They use GncRational's
249 : : * operators and then call the GncRational constructor, which will silently
250 : : * round half-down.
251 : : *
252 : : * @{
253 : : */
254 : : void operator+=(GncNumeric b);
255 : : void operator-=(GncNumeric b);
256 : : void operator*=(GncNumeric b);
257 : : void operator/=(GncNumeric b);
258 : : /* @} */
259 : : /** Compare function
260 : : * \defgroup gnc_numeric_comparison
261 : : * @param b GncNumeric or int to compare to.
262 : : * @return -1 if this < b, 0 if ==, 1 if this > b.
263 : : * @{
264 : : */
265 : : int cmp(GncNumeric b);
266 : : int cmp(int64_t b) { return cmp(GncNumeric(b, 1)); }
267 : : /** @} */
268 : : private:
269 : : struct round_param
270 : : {
271 : : int64_t num;
272 : : int64_t den;
273 : : int64_t rem;
274 : : };
275 : : /* Calculates the denominator required to convert to figs sigfigs. */
276 : : int64_t sigfigs_denom(unsigned figs) const noexcept;
277 : : /* Calculates a round_param struct to pass to a rounding function that will
278 : : * finish computing a GncNumeric with the new denominator.
279 : : */
280 : : round_param prepare_conversion(int64_t new_denom) const;
281 : : int64_t m_num;
282 : : int64_t m_den;
283 : : };
284 : :
285 : : /**
286 : : * \defgroup gnc_numeric_arithmetic_operators
287 : : * @{
288 : : * Normal arithmetic operators. The class arithmetic operators are implemented
289 : : * in terms of these operators. They use GncRational operators internally then
290 : : * call the GncNumeric(GncRational&) constructor which will silently round
291 : : * half-down to obtain int64_t members.
292 : : *
293 : : * These operators can throw std::overflow_error, std::underflow_error, or
294 : : * std::invalid argument as indicated in the class GncNumeric documentation.
295 : : *
296 : : * \param a The right-side operand
297 : : * \param b The left-side operand
298 : : * \return A GncNumeric computed from the operation.
299 : : */
300 : : GncNumeric operator+(GncNumeric a, GncNumeric b);
301 : : inline GncNumeric operator+(GncNumeric a, int64_t b)
302 : : {
303 : : return a + GncNumeric(b, 1);
304 : : }
305 : : inline GncNumeric operator+(int64_t a, GncNumeric b)
306 : : {
307 : : return b + GncNumeric(a, 1);
308 : : }
309 : : GncNumeric operator-(GncNumeric a, GncNumeric b);
310 : : inline GncNumeric operator-(GncNumeric a, int64_t b)
311 : : {
312 : : return a - GncNumeric(b, 1);
313 : : }
314 : : inline GncNumeric operator-(int64_t a, GncNumeric b)
315 : : {
316 : : return b - GncNumeric(a, 1);
317 : : }
318 : : GncNumeric operator*(GncNumeric a, GncNumeric b);
319 : : inline GncNumeric operator*(GncNumeric a, int64_t b)
320 : : {
321 : : return a * GncNumeric(b, 1);
322 : : }
323 : : inline GncNumeric operator*(int64_t a, GncNumeric b)
324 : : {
325 : : return b * GncNumeric(a, 1);
326 : : }
327 : : GncNumeric operator/(GncNumeric a, GncNumeric b);
328 : : inline GncNumeric operator/(GncNumeric a, int64_t b)
329 : : {
330 : : return a / GncNumeric(b, 1);
331 : : }
332 : : inline GncNumeric operator/(int64_t a, GncNumeric b)
333 : : {
334 : : return b / GncNumeric(a, 1);
335 : : }
336 : : /** @} */
337 : : /**
338 : : * std::stream output operator. Uses standard integer operator<< so should obey
339 : : * locale rules. Numbers are presented as integers if the denominator is 1, as a
340 : : * decimal if the denominator is a multiple of 10, otherwise as
341 : : * "numerator/denominator".
342 : : */
343 : : std::ostream& operator<<(std::ostream&, GncNumeric);
344 : :
345 : : /* Implementation adapted from "The C++ Standard Library, Second Edition" by
346 : : * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
347 : : */
348 : : template <typename charT, typename traits>
349 : 284 : std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, GncNumeric n)
350 : : {
351 : 284 : std::basic_ostringstream<charT, traits> ss;
352 : 284 : std::locale loc = s.getloc();
353 : 284 : ss.imbue(loc);
354 : 284 : auto dec_pt = static_cast<charT>('.');
355 : : try
356 : : {
357 : 284 : dec_pt = std::use_facet<std::numpunct<charT>>(loc).decimal_point();
358 : : }
359 : 0 : catch(const std::bad_cast& err) {} //Don't do anything, num_sep is already set.
360 : :
361 : 284 : ss.copyfmt(s);
362 : 284 : ss.width(0);
363 : 284 : if (n.denom() == 1)
364 : 4 : ss << n.num();
365 : 280 : else if (n.is_decimal())
366 : 280 : ss << n.num() / n.denom() << dec_pt
367 : 280 : << (n.num() > 0 ? n.num() : -n.num()) % n.denom();
368 : : else
369 : 0 : ss << n.num() << "/" << n.denom();
370 : 284 : s << ss.str();
371 : 284 : return s;
372 : 284 : }
373 : :
374 : : /**
375 : : * std::stream input operator.
376 : : *
377 : : * Doesn't do any special space handling, spaces in the input stream will
378 : : * delimit elements. The result will be that if a number is presented as "123 /
379 : : * 456", the resulting GncNumeric will be 123/1 and the rest will go to the next
380 : : * parameter in the stream call. The GncNumeric will be constructed with the
381 : : * string constructor with autorounding. It will throw in the event of any
382 : : * errors noted in the string constructor documentation.
383 : : */
384 : : /* Implementation adapted from "The C++ Standard Library, Second Edition" by
385 : : * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
386 : : */
387 : : template <typename charT, typename traits>
388 : : std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& s, GncNumeric& n)
389 : : {
390 : : std::basic_string<charT, traits> instr;
391 : : s >> instr;
392 : : if (s)
393 : : n = GncNumeric(instr, true);
394 : : return s;
395 : : }
396 : :
397 : : /**
398 : : * @return -1 if a < b, 0 if a == b, 1 if a > b.
399 : : */
400 : : inline int cmp(GncNumeric a, GncNumeric b) { return a.cmp(b); }
401 : : inline int cmp(GncNumeric a, int64_t b) { return a.cmp(b); }
402 : : inline int cmp(int64_t a, GncNumeric b) { return GncNumeric(a, 1).cmp(b); }
403 : :
404 : : /**
405 : : * \defgroup gnc_numeric_comparison_operators
406 : : * @{
407 : : * Standard comparison operators, which do what one would expect.
408 : : */
409 : : inline bool operator<(GncNumeric a, GncNumeric b) { return cmp(a, b) < 0; }
410 : : inline bool operator<(GncNumeric a, int64_t b) { return cmp(a, b) < 0; }
411 : : inline bool operator<(int64_t a, GncNumeric b) { return cmp(a, b) < 0; }
412 : : inline bool operator>(GncNumeric a, GncNumeric b) { return cmp(a, b) > 0; }
413 : : inline bool operator>(GncNumeric a, int64_t b) { return cmp(a, b) > 0; }
414 : : inline bool operator>(int64_t a, GncNumeric b) { return cmp(a, b) > 0; }
415 : : inline bool operator==(GncNumeric a, GncNumeric b) { return cmp(a, b) == 0; }
416 : : inline bool operator==(GncNumeric a, int64_t b) { return cmp(a, b) == 0; }
417 : : inline bool operator==(int64_t a, GncNumeric b) { return cmp(a, b) == 0; }
418 : : inline bool operator<=(GncNumeric a, GncNumeric b) { return cmp(a, b) <= 0; }
419 : : inline bool operator<=(GncNumeric a, int64_t b) { return cmp(a, b) <= 0; }
420 : : inline bool operator<=(int64_t a, GncNumeric b) { return cmp(a, b) <= 0; }
421 : : inline bool operator>=(GncNumeric a, GncNumeric b) { return cmp(a, b) >= 0; }
422 : : inline bool operator>=(GncNumeric a, int64_t b) { return cmp(a, b) >= 0; }
423 : : inline bool operator>=(int64_t a, GncNumeric b) { return cmp(a, b) >= 0; }
424 : : inline bool operator!=(GncNumeric a, GncNumeric b) { return cmp(a, b) != 0; }
425 : : inline bool operator!=(GncNumeric a, int64_t b) { return cmp(a, b) != 0; }
426 : : inline bool operator!=(int64_t a, GncNumeric b) { return cmp(a, b) != 0; }
427 : : /** @} */
428 : : /**
429 : : * Convenience function to quickly return 10**digits.
430 : : * \param digits The desired exponent. Maximum value is 17.
431 : : * \return 10**digits
432 : : */
433 : : int64_t powten(unsigned int digits);
434 : :
435 : : #endif // __GNC_NUMERIC_HPP__
|