/******************************************************************** * gnc-numeric.hpp - A rational number library for int64 * * Copyright 2017 John Ralls * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * * * *******************************************************************/ #ifndef __GNC_NUMERIC_HPP__ #define __GNC_NUMERIC_HPP__ #include #include #include #include // For std::bad_cast exception #include "gnc-rational-rounding.hpp" class GncRational; /**@ingroup QOF * @brief The primary numeric class for representing amounts and values. * * Calculations are generally performed in 128-bit (by converting to * GncRational) and reducing the result. If the result would overflow a 64-bit * representation then the GncNumeric(GncRational&) constructor will call * GncRational::round_to_numeric() to get the value to fit. It will not raise an * exception, so in the unlikely event that you need an error instead of * rounding, use GncRational directly. * * Errors: Errors are signalled by exceptions as follows: * * A zero denominator will raise a std::invalid_argument. * * Division by zero will raise a std::underflow_error. * * Overflowing 128 bits will raise a std::overflow_error. * * Failure to convert a number as specified by the arguments to convert() will * raise a std::domain_error. * * Rounding Policy: GncNumeric provides a convert() member function that object * amount and value setters (and *only* those functions!) should call to set a * number which is represented in the commodity's SCU. Since SCUs are seldom 18 * digits the convert may result in rounding, which will be performed in the * method specified by the arguments passed to convert(). Overflows may result * in internal rounding as described earlier. */ class GncNumeric { public: /** * Default constructor provides the zero value. */ GncNumeric() : m_num (0), m_den(1) {} /** * Integer constructor. * * Unfortunately specifying a default for denom causes ambiguity errors with * the other single-argument constructors on older versions of gcc, so one * must always specify both arguments. * * \param num The Numerator * \param denom The Denominator */ GncNumeric(int64_t num, int64_t denom) : m_num(num), m_den(denom) { if (denom == 0) throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator."); } /** * GncRational constructor. * * This constructor will round rr's GncInt128s to fit into the int64_t * members using RoundType::half-down. * * \param rr A GncRational. */ GncNumeric(GncRational rr); /** * gnc_numeric constructor, used for interfacing old code. This function * should not be used outside of gnc-numeric.cpp. * * \param in A gnc_numeric. */ GncNumeric(gnc_numeric in) : m_num(in.num), m_den(in.denom) { if (in.denom == 0) throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator."); /* gnc_numeric has a dumb convention that a negative denominator means * to multiply the numerator by the denominator instead of dividing. */ if (in.denom < 0) { m_num *= -in.denom; m_den = 1; } } /** * Double constructor. * * @param d The double to be converted. In order to fit in an int64_t, its * absolute value must be < 1e18; if its absolute value is < 1e-18 it will * be represented as 0, though for practical purposes nearly all commodities * will round to zero at 1e-9 or larger. */ GncNumeric(double d); /** * String constructor. * * Accepts integer values in decimal and hexadecimal. Does not accept * thousands separators. If the string contains a '/' it is taken to * separate the numerator and denominator; if it contains either a '.' or a * ',' it is taken as a decimal point and the integers on either side will * be combined and a denominator will be the appropriate power of 10. If * neither is present the number will be treated as an integer and m_den * will be set to 1. * * Whitespace around a '/' is ignored. A correctly-formatted number will be * extracted from a larger string. * * Numbers that cannot be represented with int64_ts will throw * std::out_of_range unless a decimal point is found (see above). A 0 * denominator will cause the constructor to throw std::underflow_error. An * empty string or one which contains no recognizable number will result in * std::invalid_argument. */ GncNumeric(const std::string& str, bool autoround=false); GncNumeric(const GncNumeric& rhs) = default; GncNumeric(GncNumeric&& rhs) = default; GncNumeric& operator=(const GncNumeric& rhs) = default; GncNumeric& operator=(GncNumeric&& rhs) = default; ~GncNumeric() = default; /** * gnc_numeric conversion. Use static_cast(foo) */ operator gnc_numeric() const noexcept; /** * double conversion. Use static_cast(foo) */ operator double() const noexcept; /** * Accessor for numerator value. */ int64_t num() const noexcept { return m_num; } /** * Accessor for denominator value. */ int64_t denom() const noexcept { return m_den; } /** * @return A GncNumeric with the opposite sign. */ GncNumeric operator-() const noexcept; /** * @return 0 if this == 0 else 1/this. */ GncNumeric inv() const noexcept; /** * @return -this if this < 0 else this. */ GncNumeric abs() const noexcept; /** * Return an equivalent fraction with all common factors between the * numerator and the denominator removed. * * @return reduced GncNumeric */ GncNumeric reduce() const noexcept; /** * Convert a GncNumeric to use a new denominator. If rounding is necessary * use the indicated template specification. For example, to use half-up * rounding you'd call bar = foo.convert(1000). If you * specify RoundType::never this will throw std::domain_error if rounding is * required. * * \param new_denom The new denominator to convert the fraction to. * \return A new GncNumeric having the requested denominator. */ template GncNumeric convert(int64_t new_denom) const { auto params = prepare_conversion(new_denom); if (new_denom == GNC_DENOM_AUTO) new_denom = m_den; if (params.rem == 0) return GncNumeric(params.num, new_denom); return GncNumeric(round(params.num, params.den, params.rem, RT2T()), new_denom); } /** * Convert with the specified sigfigs. The resulting denominator depends on * the value of the GncNumeric, such that the specified significant digits * are retained in the numerator and the denominator is always a power of * 10. This is of rather dubious benefit in an accounting program, but it's * used in several places so it needs to be implemented. * * @param figs The number of digits to use for the numerator. * @return A GncNumeric with the specified number of digits in the numerator * and the appropriate power-of-ten denominator. */ template GncNumeric convert_sigfigs(unsigned int figs) const { auto new_denom(sigfigs_denom(figs)); auto params = prepare_conversion(new_denom); if (new_denom == 0) //It had better not, but just in case... new_denom = 1; if (params.rem == 0) return GncNumeric(params.num, new_denom); return GncNumeric(round(params.num, params.den, params.rem, RT2T()), new_denom); } /** * Return a string representation of the GncNumeric. See operator<< for * details. */ std::string to_string() const noexcept; /** * @return true if the denominator is a power of ten, false otherwise. */ bool is_decimal() const noexcept; /** * Convert the numeric to have a power-of-10 denominator if possible without * rounding. Throws a std::range_error on failure; the message will explain * the details. * * @param max_places exponent of the largest permissible denominator. * @return A GncNumeric value with the new denominator. */ GncNumeric to_decimal(unsigned int max_places=17) const; /** * \defgroup gnc_numeric_mutators * * These are the standard mutating operators. They use GncRational's * operators and then call the GncRational constructor, which will silently * round half-down. * * @{ */ void operator+=(GncNumeric b); void operator-=(GncNumeric b); void operator*=(GncNumeric b); void operator/=(GncNumeric b); /* @} */ /** Compare function * \defgroup gnc_numeric_comparison * @param b GncNumeric or int to compare to. * @return -1 if this < b, 0 if ==, 1 if this > b. * @{ */ int cmp(GncNumeric b); int cmp(int64_t b) { return cmp(GncNumeric(b, 1)); } /** @} */ private: struct round_param { int64_t num; int64_t den; int64_t rem; }; /* Calculates the denominator required to convert to figs sigfigs. */ int64_t sigfigs_denom(unsigned figs) const noexcept; /* Calculates a round_param struct to pass to a rounding function that will * finish computing a GncNumeric with the new denominator. */ round_param prepare_conversion(int64_t new_denom) const; int64_t m_num; int64_t m_den; }; /** * \defgroup gnc_numeric_arithmetic_operators * @{ * Normal arithmetic operators. The class arithmetic operators are implemented * in terms of these operators. They use GncRational operators internally then * call the GncNumeric(GncRational&) constructor which will silently round * half-down to obtain int64_t members. * * These operators can throw std::overflow_error, std::underflow_error, or * std::invalid argument as indicated in the class GncNumeric documentation. * * \param a The right-side operand * \param b The left-side operand * \return A GncNumeric computed from the operation. */ GncNumeric operator+(GncNumeric a, GncNumeric b); inline GncNumeric operator+(GncNumeric a, int64_t b) { return a + GncNumeric(b, 1); } inline GncNumeric operator+(int64_t a, GncNumeric b) { return b + GncNumeric(a, 1); } GncNumeric operator-(GncNumeric a, GncNumeric b); inline GncNumeric operator-(GncNumeric a, int64_t b) { return a - GncNumeric(b, 1); } inline GncNumeric operator-(int64_t a, GncNumeric b) { return b - GncNumeric(a, 1); } GncNumeric operator*(GncNumeric a, GncNumeric b); inline GncNumeric operator*(GncNumeric a, int64_t b) { return a * GncNumeric(b, 1); } inline GncNumeric operator*(int64_t a, GncNumeric b) { return b * GncNumeric(a, 1); } GncNumeric operator/(GncNumeric a, GncNumeric b); inline GncNumeric operator/(GncNumeric a, int64_t b) { return a / GncNumeric(b, 1); } inline GncNumeric operator/(int64_t a, GncNumeric b) { return b / GncNumeric(a, 1); } /** @} */ /** * std::stream output operator. Uses standard integer operator<< so should obey * locale rules. Numbers are presented as integers if the denominator is 1, as a * decimal if the denominator is a multiple of 10, otherwise as * "numerator/denominator". */ std::ostream& operator<<(std::ostream&, GncNumeric); /* Implementation adapted from "The C++ Standard Library, Second Edition" by * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8. */ template std::basic_ostream& operator<<(std::basic_ostream& s, GncNumeric n) { std::basic_ostringstream ss; std::locale loc = s.getloc(); ss.imbue(loc); auto dec_pt = static_cast('.'); try { dec_pt = std::use_facet>(loc).decimal_point(); } catch(const std::bad_cast& err) {} //Don't do anything, num_sep is already set. ss.copyfmt(s); ss.width(0); if (n.denom() == 1) ss << n.num(); else if (n.is_decimal()) ss << n.num() / n.denom() << dec_pt << (n.num() > 0 ? n.num() : -n.num()) % n.denom(); else ss << n.num() << "/" << n.denom(); s << ss.str(); return s; } /** * std::stream input operator. * * Doesn't do any special space handling, spaces in the input stream will * delimit elements. The result will be that if a number is presented as "123 / * 456", the resulting GncNumeric will be 123/1 and the rest will go to the next * parameter in the stream call. The GncNumeric will be constructed with the * string constructor with autorounding. It will throw in the event of any * errors noted in the string constructor documentation. */ /* Implementation adapted from "The C++ Standard Library, Second Edition" by * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8. */ template std::basic_istream& operator>>(std::basic_istream& s, GncNumeric& n) { std::basic_string instr; s >> instr; if (s) n = GncNumeric(instr, true); return s; } /** * @return -1 if a < b, 0 if a == b, 1 if a > b. */ inline int cmp(GncNumeric a, GncNumeric b) { return a.cmp(b); } inline int cmp(GncNumeric a, int64_t b) { return a.cmp(b); } inline int cmp(int64_t a, GncNumeric b) { return GncNumeric(a, 1).cmp(b); } /** * \defgroup gnc_numeric_comparison_operators * @{ * Standard comparison operators, which do what one would expect. */ inline bool operator<(GncNumeric a, GncNumeric b) { return cmp(a, b) < 0; } inline bool operator<(GncNumeric a, int64_t b) { return cmp(a, b) < 0; } inline bool operator<(int64_t a, GncNumeric b) { return cmp(a, b) < 0; } inline bool operator>(GncNumeric a, GncNumeric b) { return cmp(a, b) > 0; } inline bool operator>(GncNumeric a, int64_t b) { return cmp(a, b) > 0; } inline bool operator>(int64_t a, GncNumeric b) { return cmp(a, b) > 0; } inline bool operator==(GncNumeric a, GncNumeric b) { return cmp(a, b) == 0; } inline bool operator==(GncNumeric a, int64_t b) { return cmp(a, b) == 0; } inline bool operator==(int64_t a, GncNumeric b) { return cmp(a, b) == 0; } inline bool operator<=(GncNumeric a, GncNumeric b) { return cmp(a, b) <= 0; } inline bool operator<=(GncNumeric a, int64_t b) { return cmp(a, b) <= 0; } inline bool operator<=(int64_t a, GncNumeric b) { return cmp(a, b) <= 0; } inline bool operator>=(GncNumeric a, GncNumeric b) { return cmp(a, b) >= 0; } inline bool operator>=(GncNumeric a, int64_t b) { return cmp(a, b) >= 0; } inline bool operator>=(int64_t a, GncNumeric b) { return cmp(a, b) >= 0; } inline bool operator!=(GncNumeric a, GncNumeric b) { return cmp(a, b) != 0; } inline bool operator!=(GncNumeric a, int64_t b) { return cmp(a, b) != 0; } inline bool operator!=(int64_t a, GncNumeric b) { return cmp(a, b) != 0; } /** @} */ /** * Convenience function to quickly return 10**digits. * \param digits The desired exponent. Maximum value is 17. * \return 10**digits */ int64_t powten(unsigned int digits); #endif // __GNC_NUMERIC_HPP__