GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-numeric.hpp
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
33class GncRational;
34
61{
62public:
66 GncNumeric() : m_num (0), m_den(1) {}
77 GncNumeric(int64_t num, int64_t denom) :
78 m_num(num), m_den(denom) {
79 if (denom == 0)
80 throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
81 }
97 GncNumeric(gnc_numeric in) : m_num(in.num), m_den(in.denom)
98 {
99 if (in.denom == 0)
100 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 if (in.denom < 0)
105 {
106 m_num *= -in.denom;
107 m_den = 1;
108 }
109 }
118 GncNumeric(double d);
119
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;
149 operator gnc_numeric() const noexcept;
153 operator double() const noexcept;
154
158 int64_t num() const noexcept { return m_num; }
162 int64_t denom() const noexcept { return m_den; }
166 GncNumeric operator-() const noexcept;
170 GncNumeric inv() const noexcept;
174 GncNumeric abs() const noexcept;
181 GncNumeric reduce() const noexcept;
192 template <RoundType RT>
193 GncNumeric convert(int64_t new_denom) const
194 {
195 auto params = prepare_conversion(new_denom);
196 if (new_denom == GNC_DENOM_AUTO)
197 new_denom = m_den;
198 if (params.rem == 0)
199 return GncNumeric(params.num, new_denom);
200 return GncNumeric(round(params.num, params.den,
201 params.rem, RT2T<RT>()), new_denom);
202 }
203
215 template <RoundType RT>
216 GncNumeric convert_sigfigs(unsigned int figs) const
217 {
218 auto new_denom(sigfigs_denom(figs));
219 auto params = prepare_conversion(new_denom);
220 if (new_denom == 0) //It had better not, but just in case...
221 new_denom = 1;
222 if (params.rem == 0)
223 return GncNumeric(params.num, new_denom);
224 return GncNumeric(round(params.num, params.den,
225 params.rem, RT2T<RT>()), new_denom);
226 }
231 std::string to_string() const noexcept;
235 bool is_decimal() const noexcept;
244 GncNumeric to_decimal(unsigned int max_places=17) const;
254 void operator+=(GncNumeric b);
255 void operator-=(GncNumeric b);
256 void operator*=(GncNumeric b);
257 void operator/=(GncNumeric b);
258 /* @} */
265 int cmp(GncNumeric b);
266 int cmp(int64_t b) { return cmp(GncNumeric(b, 1)); }
268private:
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
300GncNumeric operator+(GncNumeric a, GncNumeric b);
301inline GncNumeric operator+(GncNumeric a, int64_t b)
302{
303 return a + GncNumeric(b, 1);
304}
305inline GncNumeric operator+(int64_t a, GncNumeric b)
306{
307 return b + GncNumeric(a, 1);
308}
309GncNumeric operator-(GncNumeric a, GncNumeric b);
310inline GncNumeric operator-(GncNumeric a, int64_t b)
311{
312 return a - GncNumeric(b, 1);
313}
314inline GncNumeric operator-(int64_t a, GncNumeric b)
315{
316 return b - GncNumeric(a, 1);
317}
318GncNumeric operator*(GncNumeric a, GncNumeric b);
319inline GncNumeric operator*(GncNumeric a, int64_t b)
320{
321 return a * GncNumeric(b, 1);
322}
323inline GncNumeric operator*(int64_t a, GncNumeric b)
324{
325 return b * GncNumeric(a, 1);
326}
327GncNumeric operator/(GncNumeric a, GncNumeric b);
328inline GncNumeric operator/(GncNumeric a, int64_t b)
329{
330 return a / GncNumeric(b, 1);
331}
332inline GncNumeric operator/(int64_t a, GncNumeric b)
333{
334 return b / GncNumeric(a, 1);
335}
343std::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 */
348template <typename charT, typename traits>
349std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, GncNumeric n)
350{
351 std::basic_ostringstream<charT, traits> ss;
352 std::locale loc = s.getloc();
353 ss.imbue(loc);
354 auto dec_pt = static_cast<charT>('.');
355 try
356 {
357 dec_pt = std::use_facet<std::numpunct<charT>>(loc).decimal_point();
358 }
359 catch(const std::bad_cast& err) {} //Don't do anything, num_sep is already set.
360
361 ss.copyfmt(s);
362 ss.width(0);
363 if (n.denom() == 1)
364 ss << n.num();
365 else if (n.is_decimal())
366 ss << n.num() / n.denom() << dec_pt
367 << (n.num() > 0 ? n.num() : -n.num()) % n.denom();
368 else
369 ss << n.num() << "/" << n.denom();
370 s << ss.str();
371 return s;
372}
373
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 */
387template <typename charT, typename traits>
388std::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
400inline int cmp(GncNumeric a, GncNumeric b) { return a.cmp(b); }
401inline int cmp(GncNumeric a, int64_t b) { return a.cmp(b); }
402inline int cmp(int64_t a, GncNumeric b) { return GncNumeric(a, 1).cmp(b); }
403
409inline bool operator<(GncNumeric a, GncNumeric b) { return cmp(a, b) < 0; }
410inline bool operator<(GncNumeric a, int64_t b) { return cmp(a, b) < 0; }
411inline bool operator<(int64_t a, GncNumeric b) { return cmp(a, b) < 0; }
412inline bool operator>(GncNumeric a, GncNumeric b) { return cmp(a, b) > 0; }
413inline bool operator>(GncNumeric a, int64_t b) { return cmp(a, b) > 0; }
414inline bool operator>(int64_t a, GncNumeric b) { return cmp(a, b) > 0; }
415inline bool operator==(GncNumeric a, GncNumeric b) { return cmp(a, b) == 0; }
416inline bool operator==(GncNumeric a, int64_t b) { return cmp(a, b) == 0; }
417inline bool operator==(int64_t a, GncNumeric b) { return cmp(a, b) == 0; }
418inline bool operator<=(GncNumeric a, GncNumeric b) { return cmp(a, b) <= 0; }
419inline bool operator<=(GncNumeric a, int64_t b) { return cmp(a, b) <= 0; }
420inline bool operator<=(int64_t a, GncNumeric b) { return cmp(a, b) <= 0; }
421inline bool operator>=(GncNumeric a, GncNumeric b) { return cmp(a, b) >= 0; }
422inline bool operator>=(GncNumeric a, int64_t b) { return cmp(a, b) >= 0; }
423inline bool operator>=(int64_t a, GncNumeric b) { return cmp(a, b) >= 0; }
424inline bool operator!=(GncNumeric a, GncNumeric b) { return cmp(a, b) != 0; }
425inline bool operator!=(GncNumeric a, int64_t b) { return cmp(a, b) != 0; }
426inline bool operator!=(int64_t a, GncNumeric b) { return cmp(a, b) != 0; }
433int64_t powten(unsigned int digits);
434
435#endif // __GNC_NUMERIC_HPP__
The primary numeric class for representing amounts and values.
GncNumeric to_decimal(unsigned int max_places=17) const
Convert the numeric to have a power-of-10 denominator if possible without rounding.
GncNumeric reduce() const noexcept
Return an equivalent fraction with all common factors between the numerator and the denominator remov...
GncNumeric operator-() const noexcept
bool is_decimal() const noexcept
GncNumeric inv() const noexcept
GncNumeric(gnc_numeric in)
gnc_numeric constructor, used for interfacing old code.
int64_t denom() const noexcept
Accessor for denominator value.
GncNumeric abs() const noexcept
GncNumeric()
Default constructor provides the zero value.
GncNumeric convert_sigfigs(unsigned int figs) const
Convert with the specified sigfigs.
GncNumeric(int64_t num, int64_t denom)
Integer constructor.
int64_t num() const noexcept
Accessor for numerator value.
std::string to_string() const noexcept
Return a string representation of the GncNumeric.
GncNumeric convert(int64_t new_denom) const
Convert a GncNumeric to use a new denominator.
Rational number class using GncInt128 for the numerator and denominator.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.