Completed documentation for balance.h.

This commit is contained in:
John Wiegley 2007-05-21 20:39:05 +00:00
parent c331dcf09e
commit 8b606e7c85
8 changed files with 523 additions and 369 deletions

View file

@ -3,7 +3,7 @@
AC_PREREQ(2.61) AC_PREREQ(2.61)
AC_INIT([ledger],[3.0-git],[johnw@newartisans.com]) AC_INIT([ledger],[3.0],[johnw@newartisans.com])
AC_CONFIG_SRCDIR(ledger) AC_CONFIG_SRCDIR(ledger)
AM_INIT_AUTOMAKE([dist-bzip2]) AM_INIT_AUTOMAKE([dist-bzip2])

View file

@ -72,9 +72,9 @@ DECLARE_EXCEPTION(amount_error);
*/ */
class amount_t class amount_t
: public ordered_field_operators<amount_t, : public ordered_field_operators<amount_t,
ordered_field_operators<amount_t, long, ordered_field_operators<amount_t, double,
ordered_field_operators<amount_t, unsigned long, ordered_field_operators<amount_t, unsigned long,
ordered_field_operators<amount_t, double> > > > ordered_field_operators<amount_t, long> > > >
{ {
// jww (2007-05-03): Make this private, and then make // jww (2007-05-03): Make this private, and then make
// ledger::initialize into a member function of session_t. // ledger::initialize into a member function of session_t.
@ -165,9 +165,9 @@ public:
* precision or sign is lost in any of these conversions. The * precision or sign is lost in any of these conversions. The
* resulting commodity is always `commodity_t::null_commodity'. * resulting commodity is always `commodity_t::null_commodity'.
* *
* amount_t(string), amount_t(char*) both convert from a string * amount_t(string), amount_t(const char *) both convert from a
* representation of an amount, which may or may not include a * string representation of an amount, which may or may not include
* commodity. This is the proper way to initialize an amount like * a commodity. This is the proper way to initialize an amount like
* '$100.00'. * '$100.00'.
*/ */
amount_t() : quantity(NULL), commodity_(NULL) { amount_t() : quantity(NULL), commodity_(NULL) {
@ -322,7 +322,7 @@ public:
* *
* Further, for the sake of efficiency and avoiding temporary * Further, for the sake of efficiency and avoiding temporary
* objects, the following methods support "in-place" variants that * objects, the following methods support "in-place" variants that
* act on the value itself and return a reference to the result * act on the amount itself and return a reference to the result
* (`*this'): * (`*this'):
* *
* in_place_negate() * in_place_negate()
@ -594,7 +594,7 @@ public:
* and two arguments with default values: * and two arguments with default values:
* *
* print(ostream, bool omit_commodity = false, bool full_precision = * print(ostream, bool omit_commodity = false, bool full_precision =
* false) prits an amounts to the given output stream, using its * false) prints an amounts to the given output stream, using its
* commodity's default display characteristics. If `omit_commodity' * commodity's default display characteristics. If `omit_commodity'
* is true, the commodity will not be displayed, only the amount * is true, the commodity will not be displayed, only the amount
* (although the commodity's display precision is still used). If * (although the commodity's display precision is still used). If

View file

@ -33,111 +33,138 @@
namespace ledger { namespace ledger {
balance_t& 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;
}
balance_t& balance_t::operator+=(const amount_t& amt)
{
if (amt.is_null())
throw_(balance_error,
"Cannot add an uninitialized amount to a balance");
if (amt.is_realzero())
return *this;
amounts_map::iterator i = amounts.find(&amt.commodity());
if (i != amounts.end())
i->second += amt;
else
amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
return *this;
}
balance_t& 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;
}
balance_t& balance_t::operator-=(const amount_t& amt)
{
if (amt.is_null())
throw_(balance_error,
"Cannot subtract an uninitialized amount from a balance");
if (amt.is_realzero())
return *this;
amounts_map::iterator i = amounts.find(&amt.commodity());
if (i != amounts.end()) {
i->second -= amt;
if (i->second.is_realzero())
amounts.erase(i);
} else {
amounts.insert(amounts_map::value_type(&amt.commodity(), amt.negate()));
}
return *this;
}
balance_t& balance_t::operator*=(const amount_t& amt) balance_t& balance_t::operator*=(const amount_t& amt)
{ {
if (amt.is_null())
throw_(balance_error,
"Cannot multiply a balance by an uninitialized amount");
if (is_realzero()) { if (is_realzero()) {
return *this; ;
} }
else if (amt.is_realzero()) { else if (amt.is_realzero()) {
return *this = amt; *this = amt;
} }
else if (! amt.commodity()) { else if (! amt.commodity()) {
// Multiplying by the null commodity causes all amounts to be // Multiplying by an amount with no commodity causes all the
// increased by the same factor. // component amounts to be increased by the same factor.
for (amounts_map::iterator i = amounts.begin(); for (amounts_map::iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
(*i).second *= amt; i->second *= amt;
} }
else if (amounts.size() == 1) { else if (amounts.size() == 1) {
*this = (*amounts.begin()).second * amt; // Multiplying by a commoditized amount is only valid if the sole
// commodity in the balance is of the same kind as the amount's
// commodity.
if (*amounts.begin()->first == amt.commodity())
amounts.begin()->second *= amt;
else
throw_(balance_error,
"Cannot multiply a balance with annotated commodities by a commoditized amount");
} }
else { else {
amounts_map::iterator i = amounts.find(&amt.commodity()); assert(amounts.size() > 1);
if (i != amounts.end()) { throw_(balance_error,
(*i).second *= amt; "Cannot multiply a multi-commodity balance by a commoditized amount");
} else {
// Try stripping annotations before giving an error.
balance_t temp(strip_annotations());
if (temp.amounts.size() == 1) {
return *this = (*temp.amounts.begin()).second * amt;
} else {
i = temp.amounts.find(&amt.commodity());
if (i != temp.amounts.end())
return *this = temp * amt;
}
throw_(amount_error, "Attempt to multiply balance by a commodity" <<
" not found in that balance: " << temp << " * " << amt);
}
} }
return *this; return *this;
} }
balance_t& balance_t::operator/=(const amount_t& amt) balance_t& balance_t::operator/=(const amount_t& amt)
{ {
if (amt.is_realzero()) { if (amt.is_null())
throw_(amount_error, "Divide by zero: " << *this << " / " << amt); throw_(balance_error,
"Cannot divide a balance by an uninitialized amount");
if (is_realzero()) {
;
} }
else if (is_realzero()) { else if (amt.is_realzero()) {
return *this; throw_(balance_error, "Divide by zero");
} }
else if (! amt.commodity()) { else if (! amt.commodity()) {
// Dividing by the null commodity causes all amounts to be // Dividing by an amount with no commodity causes all the
// decreased by the same factor. // component amounts to be divided by the same factor.
for (amounts_map::iterator i = amounts.begin(); for (amounts_map::iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
(*i).second /= amt; i->second /= amt;
} }
else if (amounts.size() == 1 && else if (amounts.size() == 1) {
(*amounts.begin()).first == &amt.commodity()) { // Dividing by a commoditized amount is only valid if the sole
(*amounts.begin()).second /= amt; // commodity in the balance is of the same kind as the amount's
// commodity.
if (*amounts.begin()->first == amt.commodity())
amounts.begin()->second /= amt;
else
throw_(balance_error,
"Cannot divide a balance with annotated commodities by a commoditized amount");
} }
else { else {
amounts_map::iterator i = amounts.find(&amt.commodity()); assert(amounts.size() > 1);
if (i != amounts.end()) { throw_(balance_error,
(*i).second /= amt; "Cannot divide a multi-commodity balance by a commoditized amount");
} else {
// Try stripping annotations before giving an error.
balance_t temp(strip_annotations());
if (temp.amounts.size() == 1 &&
(*temp.amounts.begin()).first == &amt.commodity())
return *this = temp / amt;
throw_(amount_error, "Attempt to divide balance by a commodity" <<
" not found in that balance: " << temp << " * " << amt);
}
} }
return *this; return *this;
} }
optional<amount_t>
balance_t::amount(const optional<const commodity_t&>& commodity) const
{
if (! commodity) {
if (amounts.size() == 1) {
amounts_map::const_iterator i = amounts.begin();
return (*i).second;
}
else if (amounts.size() > 1) {
// Try stripping annotations before giving an error.
balance_t temp(strip_annotations());
if (temp.amounts.size() == 1)
return temp.amount(commodity);
throw_(amount_error,
"Requested amount of a balance with multiple commodities: " << temp);
}
}
else if (amounts.size() > 0) {
amounts_map::const_iterator i = amounts.find(&*commodity);
if (i != amounts.end())
return (*i).second;
}
return none;
}
optional<balance_t> optional<balance_t>
balance_t::value(const optional<moment_t>& moment) const balance_t::value(const optional<moment_t>& moment) const
{ {
@ -146,7 +173,7 @@ balance_t::value(const optional<moment_t>& moment) const
for (amounts_map::const_iterator i = amounts.begin(); for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
if (optional<amount_t> val = (*i).second.value(moment)) { if (optional<amount_t> val = i->second.value(moment)) {
if (! temp) if (! temp)
temp = balance_t(); temp = balance_t();
*temp += *val; *temp += *val;
@ -155,6 +182,33 @@ balance_t::value(const optional<moment_t>& moment) const
return temp; return temp;
} }
optional<amount_t>
balance_t::commodity_amount(const optional<const commodity_t&>& commodity) const
{
// jww (2007-05-20): Needs work
if (! commodity) {
if (amounts.size() == 1) {
amounts_map::const_iterator i = amounts.begin();
return i->second;
}
else if (amounts.size() > 1) {
// Try stripping annotations before giving an error.
balance_t temp(strip_annotations());
if (temp.amounts.size() == 1)
return temp.commodity_amount(commodity);
throw_(amount_error,
"Requested amount of a balance with multiple commodities: " << temp);
}
}
else if (amounts.size() > 0) {
amounts_map::const_iterator i = amounts.find(&*commodity);
if (i != amounts.end())
return i->second;
}
return none;
}
balance_t balance_t::strip_annotations(const bool keep_price, balance_t balance_t::strip_annotations(const bool keep_price,
const bool keep_date, const bool keep_date,
const bool keep_tag) const const bool keep_tag) const
@ -164,7 +218,7 @@ balance_t balance_t::strip_annotations(const bool keep_price,
for (amounts_map::const_iterator i = amounts.begin(); for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
temp += (*i).second.strip_annotations(keep_price, keep_date, keep_tag); temp += i->second.strip_annotations(keep_price, keep_date, keep_tag);
return temp; return temp;
} }
@ -185,8 +239,8 @@ void balance_t::print(std::ostream& out,
for (amounts_map::const_iterator i = amounts.begin(); for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
if ((*i).second) if (i->second)
sorted.push_back(&(*i).second); sorted.push_back(&i->second);
std::stable_sort(sorted.begin(), sorted.end(), std::stable_sort(sorted.begin(), sorted.end(),
compare_amount_commodities()); compare_amount_commodities());
@ -215,26 +269,4 @@ void balance_t::print(std::ostream& out,
} }
} }
#if 0
balance_t::operator amount_t() const
{
if (amounts.size() == 1) {
return (*amounts.begin()).second;
}
else if (amounts.size() == 0) {
return amount_t();
}
else {
// Try stripping annotations before giving an error.
balance_t temp(strip_annotations());
if (temp.amounts.size() == 1)
return (*temp.amounts.begin()).second;
throw_(amount_error,
"Cannot convert a balance with " <<
"multiple commodities to an amount: " << temp);
}
}
#endif
} // namespace ledger } // namespace ledger

View file

@ -29,6 +29,18 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * 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 #ifndef _BALANCE_H
#define _BALANCE_H #define _BALANCE_H
@ -36,14 +48,31 @@
namespace ledger { 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 class balance_t
: public equality_comparable<balance_t, : public equality_comparable<balance_t,
equality_comparable<balance_t, amount_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,
additive<balance_t, amount_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, amount_t,
multiplicative<balance_t, double,
multiplicative<balance_t, unsigned long, multiplicative<balance_t, unsigned long,
multiplicative<balance_t, long> > > > > > > multiplicative<balance_t, long> > > > > > > > > > > > > >
{ {
public: public:
typedef std::map<const commodity_t *, amount_t> amounts_map; typedef std::map<const commodity_t *, amount_t> amounts_map;
@ -51,194 +80,411 @@ public:
protected: protected:
amounts_map amounts; amounts_map amounts;
// jww (2007-05-20): Remove these two by adding access methods
friend class value_t; friend class value_t;
friend class entry_base_t; friend class entry_base_t;
public: 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() { balance_t() {
TRACE_CTOR(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) { balance_t(const amount_t& amt) {
TRACE_CTOR(balance_t, "const amount_t&"); 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() { ~balance_t() {
TRACE_DTOR(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) { balance_t& operator=(const balance_t& bal) {
if (this != &bal) { if (this != &bal)
amounts.clear(); amounts = bal.amounts;
for (amounts_map::const_iterator i = bal.amounts.begin(); return *this;
i != bal.amounts.end(); }
i++) balance_t& operator=(const amount_t& amt) {
*this += (*i).second; if (amt.is_null())
} throw_(balance_error,
"Cannot assign an uninitialized amount to a balance");
amounts.clear();
if (! amt.is_realzero())
amounts.insert(amounts_map::value_type(&amt.commodity(), amt));
return *this; return *this;
} }
int compare(const balance_t& bal) const; 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 { bool operator==(const balance_t& bal) const {
amounts_map::const_iterator i, j; amounts_map::const_iterator i, j;
for (i = amounts.begin(), j = bal.amounts.begin(); for (i = amounts.begin(), j = bal.amounts.begin();
i != amounts.end() && j != bal.amounts.end(); i != amounts.end() && j != bal.amounts.end();
i++, j++) { i++, j++) {
if (! ((*i).first == (*j).first && if (! (i->first == j->first && i->second == j->second))
(*i).second == (*j).second))
return false; return false;
} }
return i == amounts.end() && j == bal.amounts.end(); return i == amounts.end() && j == bal.amounts.end();
} }
bool operator==(const amount_t& amt) const { 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 template <typename T>
balance_t& operator+=(const balance_t& bal) { bool operator==(const T& val) const {
for (amounts_map::const_iterator i = bal.amounts.begin(); return *this == balance_t(val);
i != bal.amounts.end();
i++)
*this += (*i).second;
return *this;
}
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;
}
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;
}
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;
} }
/**
* 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);
balance_t& operator/=(const amount_t& amt); balance_t& operator/=(const amount_t& amt);
// unary negation /**
void in_place_negate() { * 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& in_place_negate() {
for (amounts_map::iterator i = amounts.begin(); for (amounts_map::iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
(*i).second = (*i).second.negate(); i->second.in_place_negate();
} return *this;
balance_t negate() const {
balance_t temp = *this;
temp.in_place_negate();
return temp;
} }
balance_t operator-() const { balance_t operator-() const {
return negate(); return negate();
} }
// conversion operators
operator bool() const {
for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end();
i++)
if ((*i).second)
return true;
return false;
}
bool is_realzero() const {
if (amounts.size() == 0)
return true;
for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end();
i++)
if (! (*i).second.is_realzero())
return false;
return true;
}
optional<amount_t>
amount(const optional<const commodity_t&>& commodity = none) const;
optional<balance_t> value(const optional<moment_t>& moment = none) const;
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;
void print(std::ostream& out, const int first_width,
const int latter_width = -1) const;
balance_t abs() const { balance_t abs() const {
balance_t temp = *this; balance_t temp;
for (amounts_map::iterator i = temp.amounts.begin(); for (amounts_map::const_iterator i = amounts.begin();
i != temp.amounts.end(); i != amounts.end();
i++) i++)
(*i).second = (*i).second.abs(); temp += i->second.abs();
return temp; 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 reduce() const {
balance_t temp(*this); balance_t temp(*this);
temp.in_place_reduce(); temp.in_place_reduce();
return temp; return temp;
} }
balance_t& in_place_reduce() {
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; 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(); for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
if ((*i).second.commodity()) temp += i->second.reduce();
temp += (*i).second.unround(); return *this = temp;
}
balance_t unreduce() const {
balance_t temp(*this);
temp.in_place_unreduce();
return temp; return temp;
} }
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;
}
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.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 (is_empty())
return true;
for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end();
i++)
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>
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;
/**
* 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 (first)
first = false;
else
out << ", ";
i->second.print(out);
}
out << ")";
}
bool valid() const { bool valid() const {
for (amounts_map::const_iterator i = amounts.begin(); for (amounts_map::const_iterator i = amounts.begin();
i != amounts.end(); i != amounts.end();
i++) i++)
if (! (*i).second.valid()) if (! i->second.valid())
return false; return false;
return true; return true;
} }

View file

@ -47,7 +47,7 @@ class balance_pair_t
multiplicative<balance_pair_t, long, multiplicative<balance_pair_t, long,
multiplicative<balance_pair_t, amount_t> > > > > > > > > multiplicative<balance_pair_t, amount_t> > > > > > > > >
{ {
balance_t quantity; balance_t quantity;
optional<balance_t> cost; optional<balance_t> cost;
friend class value_t; friend class value_t;
@ -158,8 +158,8 @@ public:
} }
optional<amount_t> optional<amount_t>
amount(const optional<const commodity_t&>& commodity = none) const { commodity_amount(const optional<const commodity_t&>& commodity = none) const {
return quantity.amount(commodity); return quantity.commodity_amount(commodity);
} }
optional<balance_t> value(const optional<moment_t>& moment = none) const { optional<balance_t> value(const optional<moment_t>& moment = none) const {
return quantity.value(moment); return quantity.value(moment);
@ -201,24 +201,6 @@ public:
return temp; return temp;
} }
void in_place_round() {
quantity = quantity.round();
if (cost)
cost = cost->round();
}
balance_pair_t round() const {
balance_pair_t temp(*this);
temp.in_place_round();
return temp;
}
balance_pair_t unround() const {
balance_pair_t temp(quantity.unround());
if (cost)
temp.cost = cost->unround();
return temp;
}
friend std::ostream& operator<<(std::ostream& out, friend std::ostream& operator<<(std::ostream& out,
const balance_pair_t& bal_pair); const balance_pair_t& bal_pair);
}; };

View file

@ -219,9 +219,6 @@ class entry_context : public error_context {
}; };
#endif #endif
DECLARE_EXCEPTION(balance_error);
class auto_entry_t : public entry_base_t class auto_entry_t : public entry_base_t
{ {
public: public:

View file

@ -1231,10 +1231,6 @@ value_t value_t::round() const
return *this; return *this;
case AMOUNT: case AMOUNT:
return as_amount().round(); return as_amount().round();
case BALANCE:
return as_balance().round();
case BALANCE_PAIR:
return as_balance_pair().round();
case XML_NODE: case XML_NODE:
return as_xml_node()->to_value().round(); return as_xml_node()->to_value().round();
default: default:
@ -1248,48 +1244,23 @@ value_t value_t::round() const
value_t value_t::unround() const value_t value_t::unround() const
{ {
switch (type()) { switch (type()) {
case BOOLEAN:
throw_(value_error, "Cannot un-round a boolean");
case DATETIME:
throw_(value_error, "Cannot un-round a date/time");
case INTEGER: case INTEGER:
return *this; return *this;
case AMOUNT: case AMOUNT:
return as_amount().unround(); return as_amount().unround();
case BALANCE:
return as_balance().unround();
case BALANCE_PAIR:
return as_balance_pair().unround();
case STRING:
throw_(value_error, "Cannot un-round a string");
case XML_NODE: case XML_NODE:
return as_xml_node()->to_value().unround(); return as_xml_node()->to_value().unround();
case POINTER:
throw_(value_error, "Cannot un-round a pointer");
case SEQUENCE:
throw_(value_error, "Cannot un-round a sequence");
default: default:
break; break;
} }
assert(false);
throw_(value_error, "Cannot unround " << label());
return value_t(); return value_t();
} }
value_t value_t::annotated_price() const value_t value_t::annotated_price() const
{ {
switch (type()) { switch (type()) {
case BOOLEAN:
throw_(value_error, "Cannot find the annotated price of a boolean");
case INTEGER:
return *this;
case DATETIME:
throw_(value_error, "Cannot find the annotated price of a date/time");
case AMOUNT: { case AMOUNT: {
optional<amount_t> temp = as_amount().annotation_details().price; optional<amount_t> temp = as_amount().annotation_details().price;
if (! temp) if (! temp)
@ -1297,37 +1268,20 @@ value_t value_t::annotated_price() const
return *temp; return *temp;
} }
case BALANCE:
throw_(value_error, "Cannot find the annotated price of a balance");
case BALANCE_PAIR:
throw_(value_error, "Cannot find the annotated price of a balance pair");
case STRING:
throw_(value_error, "Cannot find the annotated price of a string");
case XML_NODE: case XML_NODE:
return as_xml_node()->to_value().annotated_price(); return as_xml_node()->to_value().annotated_price();
case POINTER:
throw_(value_error, "Cannot find the annotated price of a pointer");
case SEQUENCE:
throw_(value_error, "Cannot find the annotated price of a sequence");
default: default:
assert(false);
break; break;
} }
assert(false);
throw_(value_error, "Cannot find the annotated price of " << label());
return value_t(); return value_t();
} }
value_t value_t::annotated_date() const value_t value_t::annotated_date() const
{ {
switch (type()) { switch (type()) {
case BOOLEAN:
throw_(value_error, "Cannot find the annotated date of a boolean");
case INTEGER:
throw_(value_error, "Cannot find the annotated date of an integer");
case DATETIME: case DATETIME:
return *this; return *this;
@ -1338,37 +1292,20 @@ value_t value_t::annotated_date() const
return *temp; return *temp;
} }
case BALANCE:
throw_(value_error, "Cannot find the annotated date of a balance");
case BALANCE_PAIR:
throw_(value_error, "Cannot find the annotated date of a balance pair");
case STRING:
throw_(value_error, "Cannot find the annotated date of a string");
case XML_NODE: case XML_NODE:
return as_xml_node()->to_value().annotated_date(); return as_xml_node()->to_value().annotated_date();
case POINTER:
throw_(value_error, "Cannot find the annotated date of a pointer");
case SEQUENCE:
throw_(value_error, "Cannot find the annotated date of a sequence");
default: default:
assert(false);
break; break;
} }
assert(false);
throw_(value_error, "Cannot find the annotated date of " << label());
return value_t(); return value_t();
} }
value_t value_t::annotated_tag() const value_t value_t::annotated_tag() const
{ {
switch (type()) { switch (type()) {
case BOOLEAN:
throw_(value_error, "Cannot find the annotated tag of a boolean");
case INTEGER:
throw_(value_error, "Cannot find the annotated tag of an integer");
case DATETIME: case DATETIME:
return *this; return *this;
@ -1379,26 +1316,14 @@ value_t value_t::annotated_tag() const
return value_t(*temp, true); return value_t(*temp, true);
} }
case BALANCE:
throw_(value_error, "Cannot find the annotated tag of a balance");
case BALANCE_PAIR:
throw_(value_error, "Cannot find the annotated tag of a balance pair");
case STRING:
throw_(value_error, "Cannot find the annotated tag of a string");
case XML_NODE: case XML_NODE:
return as_xml_node()->to_value().annotated_tag(); return as_xml_node()->to_value().annotated_tag();
case POINTER:
throw_(value_error, "Cannot find the annotated tag of a pointer");
case SEQUENCE:
throw_(value_error, "Cannot find the annotated tag of a sequence");
default: default:
assert(false);
break; break;
} }
assert(false);
throw_(value_error, "Cannot find the annotated tag of " << label());
return value_t(); return value_t();
} }
@ -1442,17 +1367,11 @@ value_t value_t::strip_annotations(const bool keep_price,
value_t value_t::cost() const value_t value_t::cost() const
{ {
switch (type()) { switch (type()) {
case BOOLEAN:
throw_(value_error, "Cannot find the cost of a boolean");
case INTEGER: case INTEGER:
case AMOUNT: case AMOUNT:
case BALANCE: case BALANCE:
return *this; return *this;
case DATETIME:
throw_(value_error, "Cannot find the cost of a date/time");
case BALANCE_PAIR: case BALANCE_PAIR:
assert(as_balance_pair().cost); assert(as_balance_pair().cost);
if (as_balance_pair().cost) if (as_balance_pair().cost)
@ -1460,33 +1379,20 @@ value_t value_t::cost() const
else else
return as_balance_pair().quantity; return as_balance_pair().quantity;
case STRING:
throw_(value_error, "Cannot find the cost of a string");
case XML_NODE: case XML_NODE:
return as_xml_node()->to_value().cost(); return as_xml_node()->to_value().cost();
case POINTER:
throw_(value_error, "Cannot find the cost of a pointer");
case SEQUENCE:
throw_(value_error, "Cannot find the cost of a sequence");
default: default:
assert(false);
break; break;
} }
assert(false);
throw_(value_error, "Cannot find the cost of " << label());
return value_t(); return value_t();
} }
value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost) value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost)
{ {
switch (type()) { switch (type()) {
case BOOLEAN:
throw_(value_error, "Cannot add an amount to a boolean");
case DATETIME:
throw_(value_error, "Cannot add an amount to a date/time");
case INTEGER: case INTEGER:
case AMOUNT: case AMOUNT:
if (tcost) { if (tcost) {
@ -1517,20 +1423,11 @@ value_t& value_t::add(const amount_t& amount, const optional<amount_t>& tcost)
as_balance_pair_lval().add(amount, tcost); as_balance_pair_lval().add(amount, tcost);
break; break;
case STRING:
throw_(value_error, "Cannot add an amount to a string");
case XML_NODE:
throw_(value_error, "Cannot add an amount to an XML node");
case POINTER:
throw_(value_error, "Cannot add an amount to a pointer");
case SEQUENCE:
throw_(value_error, "Cannot add an amount to a sequence");
default: default:
assert(false);
break; break;
} }
throw_(value_error, "Cannot add an amount to " << label());
return *this; return *this;
} }

View file

@ -1365,7 +1365,7 @@ bool xpath_t::op_t::print(std::ostream& out, print_context_t& context) const
break; break;
case FUNCTION: case FUNCTION:
out << '<FUNCTION>'; out << "<FUNCTION>";
break; break;
case ARG_INDEX: case ARG_INDEX: