diff options
Diffstat (limited to 'src/balance.h')
-rw-r--r-- | src/balance.h | 462 |
1 files changed, 354 insertions, 108 deletions
diff --git a/src/balance.h b/src/balance.h index e1fa9883..eec4716d 100644 --- a/src/balance.h +++ b/src/balance.h @@ -29,6 +29,18 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +/** + * @file balance.h + * @author John Wiegley + * @date Sun May 20 15:28:44 2007 + * + * @brief Basic type for adding multiple commodities together. + * + * Unlike the amount_t class, which throws an exception if amounts of + * differing commodities are added or subtracted, the balance_t class + * is designed to allow this, tracking the amounts of each component + * commodity separately. + */ #ifndef _BALANCE_H #define _BALANCE_H @@ -36,14 +48,31 @@ namespace ledger { +DECLARE_EXCEPTION(balance_error); + +/** + * @class balance_t + * + * @brief A wrapper around amount_t allowing addition of multiple commodities. + * + * The balance_t class is appopriate for keeping a running balance + * where amounts of multiple commodities may be involved. + */ class balance_t : public equality_comparable<balance_t, equality_comparable<balance_t, amount_t, + equality_comparable<balance_t, double, + equality_comparable<balance_t, unsigned long, + equality_comparable<balance_t, long, additive<balance_t, additive<balance_t, amount_t, + additive<balance_t, double, + additive<balance_t, unsigned long, + additive<balance_t, long, multiplicative<balance_t, amount_t, + multiplicative<balance_t, double, multiplicative<balance_t, unsigned long, - multiplicative<balance_t, long> > > > > > > + multiplicative<balance_t, long> > > > > > > > > > > > > > { public: typedef std::map<const commodity_t *, amount_t> amounts_map; @@ -51,194 +80,411 @@ public: protected: amounts_map amounts; + // jww (2007-05-20): Remove these two by adding access methods friend class value_t; friend class entry_base_t; public: - // constructors + /** + * Constructors. balance_t supports similar forms of construction + * to amount_t. + * + * balance_t() creates an empty balance to which amounts or other + * balances may be added or subtracted. + * + * balance_t(amount_t) constructs a balance whose starting value is + * equal to the given amount. + * + * balance_t(double), balance_t(unsigned long) and balance_t(long) + * will construct an amount from their arguments and then construct + * a balance whose starting value is equal to that amount. This + * initial balance will have no commodity. + * + * balance_t(string) and balance_t(const char *) both convert from a + * string representation of an amount to a balance whose initial + * value is that amount. This is the proper way to initialize a + * balance like '$100.00'. + */ balance_t() { TRACE_CTOR(balance_t, ""); } - balance_t(const balance_t& bal) { - TRACE_CTOR(balance_t, "copy"); - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; - } balance_t(const amount_t& amt) { TRACE_CTOR(balance_t, "const amount_t&"); - amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + if (amt.is_null()) + throw_(balance_error, + "Cannot initialize a balance from an uninitialized amount"); + if (! amt.is_realzero()) + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + } + balance_t(const double val) { + TRACE_CTOR(balance_t, "const double"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + } + balance_t(const unsigned long val) { + TRACE_CTOR(balance_t, "const unsigned long"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); + } + balance_t(const long val) { + TRACE_CTOR(balance_t, "const long"); + amounts.insert + (amounts_map::value_type(amount_t::current_pool->null_commodity, val)); } + + explicit balance_t(const string& val) { + TRACE_CTOR(balance_t, "const string&"); + amount_t temp(val); + amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); + } + explicit balance_t(const char * val) { + TRACE_CTOR(balance_t, "const char *"); + amount_t temp(val); + amounts.insert(amounts_map::value_type(&temp.commodity(), temp)); + } + + /** + * Destructor. Destroys all of the accumulated amounts in the + * balance. + */ ~balance_t() { TRACE_DTOR(balance_t); } - // assignment operator + /** + * Assignment and copy operators. An balance may be assigned or copied. + */ + balance_t(const balance_t& bal) : amounts(bal.amounts) { + TRACE_CTOR(balance_t, "copy"); + } + balance_t& operator=(const balance_t& bal) { - if (this != &bal) { - amounts.clear(); - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; - } + if (this != &bal) + amounts = bal.amounts; return *this; } + balance_t& operator=(const amount_t& amt) { + if (amt.is_null()) + throw_(balance_error, + "Cannot assign an uninitialized amount to a balance"); - int compare(const balance_t& bal) const; + amounts.clear(); + if (! amt.is_realzero()) + amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); + + return *this; + } + balance_t& operator=(const string& str) { + return *this = balance_t(str); + } + balance_t& operator=(const char * str) { + return *this = balance_t(str); + } + + /** + * Comparison operators. Balances are fairly restrictive in terms + * of how they may be compared. They may be compared for equality + * or inequality, but this is all, since the concept of "less than" + * or "greater than" makes no sense when amounts of multiple + * commodities are involved. + * + * Balances may also be compared to amounts, in which case the sum + * of the balance must equal the amount exactly. + * + * If a comparison between balances is desired, the balances must + * first be rendered to value equivalent amounts using the `value' + * method, to determine a market valuation at some specific moment + * in time. + */ bool operator==(const balance_t& bal) const { amounts_map::const_iterator i, j; for (i = amounts.begin(), j = bal.amounts.begin(); i != amounts.end() && j != bal.amounts.end(); i++, j++) { - if (! ((*i).first == (*j).first && - (*i).second == (*j).second)) + if (! (i->first == j->first && i->second == j->second)) return false; } return i == amounts.end() && j == bal.amounts.end(); } bool operator==(const amount_t& amt) const { - return amounts.size() == 1 && amounts.begin()->second == amt; + if (amt.is_null()) + throw_(balance_error, + "Cannot compare a balance to an uninitialized amount"); + + if (amt.is_realzero()) + return amounts.empty(); + else + return amounts.size() == 1 && amounts.begin()->second == amt; } - // in-place arithmetic - balance_t& operator+=(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); - i++) - *this += (*i).second; - return *this; + template <typename T> + bool operator==(const T& val) const { + return *this == balance_t(val); } - balance_t& operator+=(const amount_t& amt) { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) - (*i).second += amt; - else if (! amt.is_realzero()) - amounts.insert(amounts_map::value_type(&amt.commodity(), amt)); - return *this; + + /** + * Binary arithmetic operators. Balances support addition and + * subtraction of other balances or amounts, but multiplication and + * division are restricted to uncommoditized amounts only. + */ + balance_t& operator+=(const balance_t& bal); + balance_t& operator+=(const amount_t& amt); + balance_t& operator-=(const balance_t& bal); + balance_t& operator-=(const amount_t& amt); + + balance_t& operator*=(const amount_t& amt); + balance_t& operator/=(const amount_t& amt); + + /** + * Unary arithmetic operators. There are only a few unary methods + * support on balance: + * + * negate(), also unary minus (- x), returns a balance all of whose + * component amounts have been negated. In order words, it inverts + * the sign of all member amounts. + * + * abs() returns a balance where no component amount is negative. + * + * reduce() reduces the values in a balance to their most basic + * commodity forms, for amounts that utilize "scaling commodities". + * For example, a balance of 1h and 1m after reduction will be + * 3660s. + * + * unreduce(), if used with amounts that use "scaling commodities", + * yields the most compact form greater than 1.0 for each component + * amount. That is, a balance of 10m and 1799s will unreduce to + * 39.98m. + * + * value(optional<moment_t>) returns the total historical value for + * a balance -- the default moment returns a value based on the most + * recently known price -- based on the price history of its + * component commodities. See amount_t::value for an example. + * + * Further, for the sake of efficiency and avoiding temporary + * objects, the following methods support "in-place" variants act on + * the balance itself and return a reference to the result + * (`*this'): + * + * in_place_negate() + * in_place_reduce() + * in_place_unreduce() + */ + balance_t negate() const { + balance_t temp(*this); + temp.in_place_negate(); + return temp; } - balance_t& operator-=(const balance_t& bal) { - for (amounts_map::const_iterator i = bal.amounts.begin(); - i != bal.amounts.end(); + balance_t& in_place_negate() { + for (amounts_map::iterator i = amounts.begin(); + i != amounts.end(); i++) - *this -= (*i).second; + i->second.in_place_negate(); return *this; } - balance_t& operator-=(const amount_t& amt) { - amounts_map::iterator i = amounts.find(&amt.commodity()); - if (i != amounts.end()) { - (*i).second -= amt; - if ((*i).second.is_realzero()) - amounts.erase(i); - } - else if (! amt.is_realzero()) { - amounts.insert(amounts_map::value_type(&amt.commodity(), - amt)); - } - return *this; + balance_t operator-() const { + return negate(); } - balance_t& operator*=(const amount_t& amt); - balance_t& operator/=(const amount_t& amt); + balance_t abs() const { + balance_t temp; + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += i->second.abs(); + return temp; + } - // unary negation - void in_place_negate() { - for (amounts_map::iterator i = amounts.begin(); + balance_t reduce() const { + balance_t temp(*this); + temp.in_place_reduce(); + return temp; + } + balance_t& in_place_reduce() { + balance_t temp; + // A temporary must be used here because reduction may cause + // multiple component amounts to collapse to the same commodity. + for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - (*i).second = (*i).second.negate(); + temp += i->second.reduce(); + return *this = temp; } - balance_t negate() const { - balance_t temp = *this; - temp.in_place_negate(); + + balance_t unreduce() const { + balance_t temp(*this); + temp.in_place_unreduce(); return temp; } - balance_t operator-() const { - return negate(); + balance_t& in_place_unreduce() { + balance_t temp; + // A temporary must be used here because unreduction may cause + // multiple component amounts to collapse to the same commodity. + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + temp += i->second.unreduce(); + return *this = temp; } - // conversion operators + optional<balance_t> value(const optional<moment_t>& moment = none) const; + + /** + * Truth tests. An balance may be truth test in two ways: + * + * is_nonzero(), or operator bool, returns true if a balance's + * display value is not zero. + * + * is_zero() returns true if an balance's display value is zero. + * Thus, a balance containing $0.0001 is considered zero if the + * current display precision for dollars is two decimal places. + * + * is_realzero() returns true if an balance's actual value is zero. + * Thus, a balance containing $0.0001 is never considered realzero. + * + * is_empty() returns true if a balance has no amounts within it. + * This can occur after a balance has been default initialized, or + * if the exact amount it contains is subsequently subtracted from + * it. + */ operator bool() const { for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if ((*i).second) + if (i->second.is_nonzero()) return true; return false; } + bool is_zero() const { + if (is_empty()) + return true; + + for (amounts_map::const_iterator i = amounts.begin(); + i != amounts.end(); + i++) + if (! i->second.is_zero()) + return false; + return true; + } + bool is_realzero() const { - if (amounts.size() == 0) + if (is_empty()) return true; + for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if (! (*i).second.is_realzero()) + if (! i->second.is_realzero()) return false; return true; } + bool is_empty() const { + return amounts.size() == 0; + } + + /** + * Conversion methods. A balance can be converted to an amount, but + * only if contains a single component amount. + */ + amount_t to_amount() const { + if (is_empty()) + throw_(balance_error, "Cannot convert an empty balance to an amount"); + else if (amounts.size() == 1) + return amounts.begin()->second; + else + throw_(balance_error, + "Cannot convert a balance with multiple commodities to an amount"); + } + + /** + * Commodity-related methods. Balances support two + * commodity-related methods: + * + * commodity_count() returns the number of different commodities + * stored in the balance. + * + * commodity_amount(optional<commodity_t>) returns an (optional) + * amount for the given commodity within the balance; if no + * commodity is specified, it returns the (optional) uncommoditized + * component of the balance. If no matching element can be found, + * boost::none is returned. + */ + std::size_t commodity_count() const { + return amounts.size(); + } + optional<amount_t> - amount(const optional<const commodity_t&>& commodity = none) const; - optional<balance_t> value(const optional<moment_t>& moment = none) const; + commodity_amount(const optional<const commodity_t&>& commodity = none) const; + /** + * Annotated commodity methods. The amounts contained by a balance + * may use annotated commodities. The `strip_annotations' method + * will return a balance all of whose component amount have had + * their commodity annotations likewise stripped. See + * amount_t::strip_annotations for more details. + */ balance_t strip_annotations(const bool keep_price = amount_t::keep_price, const bool keep_date = amount_t::keep_date, const bool keep_tag = amount_t::keep_tag) const; + /** + * Printing methods. A balance may be output to a stream using the + * `print' method. There is also a global operator<< defined which + * simply calls print for a balance on the given stream. There is + * one form of the print method, which takes two required arguments + * and one arguments with a default value: + * + * print(ostream, int first_width, int latter_width) prints a + * balance to the given output stream, using each commodity's + * default display characteristics. The first_width parameter + * specifies the width that should be used for printing amounts + * (since they are likely to vary in width). The latter_width, if + * specified, gives the width to be used for each line after the + * first. This is useful when printing in a column which falls at + * the right-hand side of the screen. + * + * In addition to the width constraints, balances will also print + * with commodities in alphabetized order, regardless of the + * relative amounts of those commodities. There is no option to + * change this behavior. + */ void print(std::ostream& out, const int first_width, const int latter_width = -1) const; - balance_t abs() const { - balance_t temp = *this; - for (amounts_map::iterator i = temp.amounts.begin(); - i != temp.amounts.end(); - i++) - (*i).second = (*i).second.abs(); - return temp; - } - - void in_place_reduce() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second.in_place_reduce(); - } - balance_t reduce() const { - balance_t temp(*this); - temp.in_place_reduce(); - return temp; - } - - void in_place_round() { - for (amounts_map::iterator i = amounts.begin(); - i != amounts.end(); - i++) - (*i).second = (*i).second.round(); - } - balance_t round() const { - balance_t temp(*this); - temp.in_place_round(); - return temp; - } - - balance_t unround() const { - balance_t temp; + /** + * Debugging methods. There are two methods defined to help with + * debugging: + * + * dump(ostream) dumps a balance to an output stream. There is + * little different from print(), it simply surrounds the display + * value with a marker, for example "BALANCE($1.00, DM 12.00)". + * This code is used by other dumping code elsewhere in Ledger. + * + * valid() returns true if the amounts within the balance are valid. + */ + void dump(std::ostream& out) const { + out << "BALANCE("; + bool first = true; for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); - i++) - if ((*i).second.commodity()) - temp += (*i).second.unround(); - return temp; + i++) { + if (first) + first = false; + else + out << ", "; + i->second.print(out); + } + out << ")"; } bool valid() const { for (amounts_map::const_iterator i = amounts.begin(); i != amounts.end(); i++) - if (! (*i).second.valid()) + if (! i->second.valid()) return false; return true; } |