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 565 : 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 293302 : GncNumeric(int64_t num, int64_t denom) :
78 293302 : m_num(num), m_den(denom) {
79 293302 : if (denom == 0)
80 0 : throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
81 293302 : }
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 532495 : GncNumeric(gnc_numeric in) : m_num(in.num), m_den(in.denom)
98 : {
99 532495 : 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 532495 : if (in.denom < 0)
105 : {
106 0 : m_num *= -in.denom;
107 0 : m_den = 1;
108 : }
109 532495 : }
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 824179 : int64_t num() const noexcept { return m_num; }
159 : /**
160 : * Accessor for denominator value.
161 : */
162 397429 : 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 289391 : GncNumeric convert(int64_t new_denom) const
194 : {
195 289391 : auto params = prepare_conversion(new_denom);
196 289376 : if (new_denom == GNC_DENOM_AUTO)
197 229002 : new_denom = m_den;
198 289376 : if (params.rem == 0)
199 283771 : return GncNumeric(params.num, new_denom);
200 5605 : return GncNumeric(round(params.num, params.den,
201 11210 : 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 31 : GncNumeric convert_sigfigs(unsigned int figs) const
217 : {
218 31 : auto new_denom(sigfigs_denom(figs));
219 31 : auto params = prepare_conversion(new_denom);
220 31 : if (new_denom == 0) //It had better not, but just in case...
221 0 : new_denom = 1;
222 31 : if (params.rem == 0)
223 30 : return GncNumeric(params.num, new_denom);
224 1 : return GncNumeric(round(params.num, params.den,
225 2 : 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 : {
367 : /* Use the magnitude so that values between -1 and 0 keep their sign,
368 : * and write each fractional digit so that leading zeroes are kept. */
369 280 : auto num = n.num();
370 280 : auto mag = num < 0 ? 0 - static_cast<uint64_t>(num)
371 : : static_cast<uint64_t>(num);
372 280 : auto den = static_cast<uint64_t>(n.denom());
373 280 : if (num < 0)
374 78 : ss << static_cast<charT>('-');
375 280 : ss << mag / den << dec_pt;
376 840 : for (auto place = den / 10; place > 0; place /= 10)
377 560 : ss << static_cast<charT>('0' + mag % den / place % 10);
378 : }
379 : else
380 0 : ss << n.num() << "/" << n.denom();
381 284 : s << ss.str();
382 284 : return s;
383 284 : }
384 :
385 : /**
386 : * std::stream input operator.
387 : *
388 : * Doesn't do any special space handling, spaces in the input stream will
389 : * delimit elements. The result will be that if a number is presented as "123 /
390 : * 456", the resulting GncNumeric will be 123/1 and the rest will go to the next
391 : * parameter in the stream call. The GncNumeric will be constructed with the
392 : * string constructor with autorounding. It will throw in the event of any
393 : * errors noted in the string constructor documentation.
394 : */
395 : /* Implementation adapted from "The C++ Standard Library, Second Edition" by
396 : * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
397 : */
398 : template <typename charT, typename traits>
399 : std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& s, GncNumeric& n)
400 : {
401 : std::basic_string<charT, traits> instr;
402 : s >> instr;
403 : if (s)
404 : n = GncNumeric(instr, true);
405 : return s;
406 : }
407 :
408 : /**
409 : * @return -1 if a < b, 0 if a == b, 1 if a > b.
410 : */
411 : inline int cmp(GncNumeric a, GncNumeric b) { return a.cmp(b); }
412 : inline int cmp(GncNumeric a, int64_t b) { return a.cmp(b); }
413 : inline int cmp(int64_t a, GncNumeric b) { return GncNumeric(a, 1).cmp(b); }
414 :
415 : /**
416 : * \defgroup gnc_numeric_comparison_operators
417 : * @{
418 : * Standard comparison operators, which do what one would expect.
419 : */
420 : inline bool operator<(GncNumeric a, GncNumeric b) { return cmp(a, b) < 0; }
421 : inline bool operator<(GncNumeric a, int64_t b) { return cmp(a, b) < 0; }
422 : inline bool operator<(int64_t a, GncNumeric b) { return cmp(a, b) < 0; }
423 : inline bool operator>(GncNumeric a, GncNumeric b) { return cmp(a, b) > 0; }
424 : inline bool operator>(GncNumeric a, int64_t b) { return cmp(a, b) > 0; }
425 : inline bool operator>(int64_t a, GncNumeric b) { return cmp(a, b) > 0; }
426 : inline bool operator==(GncNumeric a, GncNumeric b) { return cmp(a, b) == 0; }
427 : inline bool operator==(GncNumeric a, int64_t b) { return cmp(a, b) == 0; }
428 : inline bool operator==(int64_t a, GncNumeric b) { return cmp(a, b) == 0; }
429 : inline bool operator<=(GncNumeric a, GncNumeric b) { return cmp(a, b) <= 0; }
430 : inline bool operator<=(GncNumeric a, int64_t b) { return cmp(a, b) <= 0; }
431 : inline bool operator<=(int64_t a, GncNumeric b) { return cmp(a, b) <= 0; }
432 : inline bool operator>=(GncNumeric a, GncNumeric b) { return cmp(a, b) >= 0; }
433 : inline bool operator>=(GncNumeric a, int64_t b) { return cmp(a, b) >= 0; }
434 : inline bool operator>=(int64_t a, GncNumeric b) { return cmp(a, b) >= 0; }
435 : inline bool operator!=(GncNumeric a, GncNumeric b) { return cmp(a, b) != 0; }
436 : inline bool operator!=(GncNumeric a, int64_t b) { return cmp(a, b) != 0; }
437 : inline bool operator!=(int64_t a, GncNumeric b) { return cmp(a, b) != 0; }
438 : /** @} */
439 : /**
440 : * Convenience function to quickly return 10**digits.
441 : * \param digits The desired exponent. Maximum value is 17.
442 : * \return 10**digits
443 : */
444 : int64_t powten(unsigned int digits);
445 :
446 : #endif // __GNC_NUMERIC_HPP__
|