summaryrefslogtreecommitdiff
path: root/src/amount.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/amount.cc')
-rw-r--r--src/amount.cc2041
1 files changed, 2041 insertions, 0 deletions
diff --git a/src/amount.cc b/src/amount.cc
new file mode 100644
index 00000000..ac7bbdb7
--- /dev/null
+++ b/src/amount.cc
@@ -0,0 +1,2041 @@
+/**
+ * @file amount.cc
+ * @author John Wiegley
+ * @date Thu Apr 26 15:19:46 2007
+ *
+ * @brief Types for handling commoditized math.
+ *
+ * This file defines member functions for amount_t and the various
+ * flavors of commodity_t.
+ */
+
+/*
+ * Copyright (c) 2003-2007, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "amount.h"
+#include "binary.h"
+
+namespace ledger {
+
+bool do_cleanup = true;
+
+bool amount_t::keep_price = false;
+bool amount_t::keep_date = false;
+bool amount_t::keep_tag = false;
+bool amount_t::keep_base = false;
+bool amount_t::full_strings = false;
+
+#define BIGINT_BULK_ALLOC 0x0001
+#define BIGINT_KEEP_PREC 0x0002
+
+class amount_t::bigint_t
+{
+ public:
+ mpz_t val;
+ unsigned char prec;
+ unsigned char flags;
+ unsigned int ref;
+ unsigned int index;
+
+ bigint_t() : prec(0), flags(0), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "");
+ mpz_init(val);
+ }
+ bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "mpz_t");
+ mpz_init_set(val, _val);
+ }
+ bigint_t(const bigint_t& other)
+ : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC),
+ ref(1), index(0) {
+ TRACE_CTOR(bigint_t, "copy");
+ mpz_init_set(val, other.val);
+ }
+ ~bigint_t();
+};
+
+unsigned int sizeof_bigint_t() {
+ return sizeof(amount_t::bigint_t);
+}
+
+#define MPZ(x) ((x)->val)
+
+#ifndef THREADSAFE
+static mpz_t temp; // these are the global temp variables
+static mpz_t divisor;
+#endif
+
+static amount_t::bigint_t * true_value = NULL;
+
+inline amount_t::bigint_t::~bigint_t() {
+ TRACE_DTOR(bigint_t);
+ assert(ref == 0 || (! do_cleanup && this == true_value));
+ mpz_clear(val);
+}
+
+#ifndef THREADSAFE
+base_commodities_map commodity_base_t::commodities;
+
+commodity_base_t::updater_t * commodity_base_t::updater = NULL;
+
+commodities_map commodity_t::commodities;
+commodities_array * commodity_t::commodities_by_ident;
+bool commodity_t::commodities_sorted = false;
+commodity_t * commodity_t::null_commodity;
+commodity_t * commodity_t::default_commodity = NULL;
+#endif
+
+void amount_t::initialize()
+{
+ mpz_init(temp);
+ mpz_init(divisor);
+
+ true_value = new amount_t::bigint_t;
+ mpz_set_ui(true_value->val, 1);
+
+ commodity_base_t::updater = NULL;
+
+ commodity_t::commodities_by_ident = new commodities_array;
+
+ commodity_t::default_commodity = NULL;
+ commodity_t::null_commodity = commodity_t::create("");
+ commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
+ COMMODITY_STYLE_BUILTIN);
+
+ // Add time commodity conversions, so that timelog's may be parsed
+ // in terms of seconds, but reported as minutes or hours.
+ commodity_t * commodity = commodity_t::create("s");
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
+
+ parse_conversion("1.0m", "60s");
+ parse_conversion("1.0h", "60m");
+}
+
+void amount_t::shutdown()
+{
+ mpz_clear(temp);
+ mpz_clear(divisor);
+
+ if (commodity_base_t::updater) {
+ delete commodity_base_t::updater;
+ commodity_base_t::updater = NULL;
+ }
+
+ for (base_commodities_map::iterator i = commodity_base_t::commodities.begin();
+ i != commodity_base_t::commodities.end();
+ i++)
+ delete (*i).second;
+
+ for (commodities_map::iterator i = commodity_t::commodities.begin();
+ i != commodity_t::commodities.end();
+ i++)
+ delete (*i).second;
+
+ commodity_base_t::commodities.clear();
+ commodity_t::commodities.clear();
+
+ delete commodity_t::commodities_by_ident;
+ commodity_t::commodities_by_ident = NULL;
+
+ commodity_t::null_commodity = NULL;
+ commodity_t::default_commodity = NULL;
+
+ true_value->ref--;
+ assert(true_value->ref == 0);
+ delete true_value;
+ true_value = NULL;
+}
+
+static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec)
+{
+ // Round `value', with an encoding precision of `value_prec', to a
+ // rounded value with precision `round_prec'. Result is stored in
+ // `out'.
+
+ assert(value_prec > round_prec);
+
+ mpz_t quotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(remainder);
+
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_qr(quotient, remainder, value, divisor);
+ mpz_divexact_ui(divisor, divisor, 10);
+ mpz_mul_ui(divisor, divisor, 5);
+
+ if (mpz_sgn(remainder) < 0) {
+ mpz_neg(divisor, divisor);
+ if (mpz_cmp(remainder, divisor) < 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_add(remainder, divisor, remainder);
+ mpz_ui_sub(remainder, 0, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ } else {
+ if (mpz_cmp(remainder, divisor) >= 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_sub(remainder, divisor, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ }
+ mpz_clear(quotient);
+ mpz_clear(remainder);
+
+ // chop off the rounded bits
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_q(out, out, divisor);
+}
+
+amount_t::amount_t(const long val)
+{
+ TRACE_CTOR(amount_t, "const long");
+ if (val != 0) {
+ quantity = new bigint_t;
+ mpz_set_si(MPZ(quantity), val);
+ } else {
+ quantity = NULL;
+ }
+ commodity_ = NULL;
+}
+
+amount_t::amount_t(const unsigned long val)
+{
+ TRACE_CTOR(amount_t, "const unsigned long");
+ if (val != 0) {
+ quantity = new bigint_t;
+ mpz_set_ui(MPZ(quantity), val);
+ } else {
+ quantity = NULL;
+ }
+ commodity_ = NULL;
+}
+
+namespace {
+ unsigned char convert_double(mpz_t dest, double val)
+ {
+#ifndef HAVE_GDTOA
+ // This code is far too imprecise to be worthwhile.
+
+ mpf_t temp;
+ mpf_init_set_d(temp, val);
+
+ mp_exp_t exp;
+ char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp);
+
+ int len = std::strlen(buf);
+ if (len > 0 && buf[0] == '-')
+ exp++;
+
+ if (exp <= len) {
+ exp = len - exp;
+ } else {
+ // There were trailing zeros, which we have to put back on in
+ // order to convert this buffer into an integer.
+
+ int zeroes = exp - len;
+
+ char * newbuf = (char *)std::malloc(len + zeroes);
+ std::strcpy(newbuf, buf);
+
+ int i;
+ for (i = 0; i < zeroes; i++)
+ newbuf[len + i] = '0';
+ newbuf[len + i] = '\0';
+
+ free(buf);
+ buf = newbuf;
+
+ exp = (len - exp) + zeroes;
+ }
+
+ mpz_set_str(dest, buf, 10);
+ free(buf);
+
+ return (unsigned char)exp;
+#else
+ int decpt, sign;
+ char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL);
+ char * result;
+ int len = std::strlen(buf);
+
+ if (decpt <= len) {
+ decpt = len - decpt;
+ result = NULL;
+ } else {
+ // There were trailing zeros, which we have to put back on in
+ // order to convert this buffer into an integer.
+
+ int zeroes = decpt - len;
+ result = new char[len + zeroes];
+
+ std::strcpy(result, buf);
+ int i;
+ for (i = 0; i < zeroes; i++)
+ result[len + i] = '0';
+ result[len + i] = '\0';
+
+ decpt = (len - decpt) + zeroes;
+ }
+
+ if (sign) {
+ char * newbuf = new char[std::strlen(result ? result : buf) + 1];
+ newbuf[0] = '-';
+ std::strcpy(&newbuf[1], result ? result : buf);
+ mpz_set_str(dest, newbuf, 10);
+ delete[] newbuf;
+ } else {
+ mpz_set_str(dest, result ? result : buf, 10);
+ }
+
+ if (result)
+ delete[] result;
+ freedtoa(buf);
+
+ return decpt;
+#endif
+ }
+}
+
+amount_t::amount_t(const double val)
+{
+ TRACE_CTOR(amount_t, "const double");
+ quantity = new bigint_t;
+ quantity->prec = convert_double(MPZ(quantity), val);
+ commodity_ = NULL;
+}
+
+void amount_t::_release()
+{
+ DEBUG_("amounts.refs",
+ quantity << " ref--, now " << (quantity->ref - 1));
+ if (--quantity->ref == 0) {
+ if (! (quantity->flags & BIGINT_BULK_ALLOC))
+ delete quantity;
+ else
+ quantity->~bigint_t();
+ }
+}
+
+void amount_t::_init()
+{
+ if (! quantity) {
+ quantity = new bigint_t;
+ }
+ else if (quantity->ref > 1) {
+ _release();
+ quantity = new bigint_t;
+ }
+}
+
+void amount_t::_dup()
+{
+ if (quantity->ref > 1) {
+ bigint_t * q = new bigint_t(*quantity);
+ _release();
+ quantity = q;
+ }
+}
+
+void amount_t::_copy(const amount_t& amt)
+{
+ if (quantity != amt.quantity) {
+ if (quantity)
+ _release();
+
+ // Never maintain a pointer into a bulk allocation pool; such
+ // pointers are not guaranteed to remain.
+ if (amt.quantity->flags & BIGINT_BULK_ALLOC) {
+ quantity = new bigint_t(*amt.quantity);
+ } else {
+ quantity = amt.quantity;
+ DEBUG_("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+ }
+ commodity_ = amt.commodity_;
+}
+
+amount_t& amount_t::operator=(const string& val)
+{
+ std::istringstream str(val);
+ parse(str);
+ return *this;
+}
+
+amount_t& amount_t::operator=(const char * val)
+{
+ string valstr(val);
+ std::istringstream str(valstr);
+ parse(str);
+ return *this;
+}
+
+// assignment operator
+amount_t& amount_t::operator=(const amount_t& amt)
+{
+ if (this != &amt) {
+ if (amt.quantity)
+ _copy(amt);
+ else if (quantity)
+ _clear();
+ }
+ return *this;
+}
+
+amount_t& amount_t::operator=(const long val)
+{
+ if (val == 0) {
+ if (quantity)
+ _clear();
+ } else {
+ commodity_ = NULL;
+ _init();
+ mpz_set_si(MPZ(quantity), val);
+ }
+ return *this;
+}
+
+amount_t& amount_t::operator=(const unsigned long val)
+{
+ if (val == 0) {
+ if (quantity)
+ _clear();
+ } else {
+ commodity_ = NULL;
+ _init();
+ mpz_set_ui(MPZ(quantity), val);
+ }
+ return *this;
+}
+
+amount_t& amount_t::operator=(const double val)
+{
+ commodity_ = NULL;
+ _init();
+ quantity->prec = convert_double(MPZ(quantity), val);
+ return *this;
+}
+
+
+void amount_t::_resize(unsigned int prec)
+{
+ assert(prec < 256);
+
+ if (! quantity || prec == quantity->prec)
+ return;
+
+ _dup();
+
+ if (prec < quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, quantity->prec - prec);
+ mpz_tdiv_q(MPZ(quantity), MPZ(quantity), divisor);
+ } else {
+ mpz_ui_pow_ui(divisor, 10, prec - quantity->prec);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+ }
+
+ quantity->prec = prec;
+}
+
+
+void amount_t::_clear()
+{
+ if (quantity) {
+ _release();
+ quantity = NULL;
+ commodity_ = NULL;
+ } else {
+ assert(! commodity_);
+ }
+}
+
+
+amount_t& amount_t::operator+=(const amount_t& amt)
+{
+ if (commodity() != amt.commodity()) {
+ throw amount_exception
+ (string("Adding amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+ }
+
+ if (! amt.quantity)
+ return *this;
+
+ if (! quantity) {
+ _copy(amt);
+ return *this;
+ }
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator-=(const amount_t& amt)
+{
+ if (commodity() != amt.commodity())
+ throw amount_exception
+ (string("Subtracting amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+
+ if (! amt.quantity)
+ return *this;
+
+ if (! quantity) {
+ quantity = new bigint_t(*amt.quantity);
+ commodity_ = amt.commodity_;
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+ return *this;
+ }
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator*=(const amount_t& amt)
+{
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity()) {
+ throw amount_exception
+ (string("Multiplying amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+ }
+
+ if (! amt.quantity) {
+ *this = *this - *this; // preserve our commodity
+ goto finish;
+ }
+ else if (! quantity) {
+ *this = amt;
+ *this = *this - *this; // preserve the foreign commodity
+ goto finish;
+ }
+
+ _dup();
+
+ mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec;
+
+ finish:
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_PREC)) {
+ unsigned int comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + 6U) {
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U);
+ quantity->prec = comm_prec + 6U;
+ }
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator/=(const amount_t& amt)
+{
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity()) {
+ throw amount_exception
+ (string("Dividing amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+ }
+
+ if (! amt.quantity || ! amt) {
+ throw amount_exception("Divide by zero", context());
+ }
+ else if (! quantity) {
+ *this = amt;
+ *this = *this - *this; // preserve the foreign commodity
+ goto finish;
+ }
+
+ _dup();
+
+ // Increase the value's precision, to capture fractional parts after
+ // the divide. Round up in the last position.
+
+ mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+ mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec + quantity->prec + 7U;
+
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1);
+ quantity->prec -= 1;
+
+ finish:
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ // If this amount has a commodity, and we're not dealing with plain
+ // numbers, or internal numbers (which keep full precision at all
+ // times), then round the number to within the commodity's precision
+ // plus six places.
+
+ if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_PREC)) {
+ unsigned int comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + 6U) {
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U);
+ quantity->prec = comm_prec + 6U;
+ }
+ }
+
+ return *this;
+}
+
+// unary negation
+void amount_t::in_place_negate()
+{
+ if (quantity) {
+ _dup();
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+ }
+}
+
+int amount_t::sign() const
+{
+ return quantity ? mpz_sgn(MPZ(quantity)) : 0;
+}
+
+int amount_t::compare(const amount_t& amt) const
+{
+ if (! quantity) {
+ if (! amt.quantity)
+ return 0;
+ return - amt.sign();
+ }
+ if (! amt.quantity)
+ return sign();
+
+ if (has_commodity() && amt.commodity() && commodity() != amt.commodity())
+ throw amount_exception
+ (string("Cannot compare amounts with different commodities: ") +
+ commodity().symbol() + " and " + amt.commodity().symbol(),
+ context());
+
+ if (quantity->prec == amt.quantity->prec) {
+ return mpz_cmp(MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ amount_t t = *this;
+ t._resize(amt.quantity->prec);
+ return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ return mpz_cmp(MPZ(quantity), MPZ(t.quantity));
+ }
+}
+
+bool amount_t::operator==(const amount_t& amt) const
+{
+ if (commodity() != amt.commodity())
+ return false;
+ return compare(amt) == 0;
+}
+
+bool amount_t::operator!=(const amount_t& amt) const
+{
+ if (commodity() != amt.commodity())
+ return true;
+ return compare(amt) != 0;
+}
+
+bool amount_t::zero() const
+{
+ if (! quantity)
+ return true;
+
+ if (has_commodity()) {
+ if (quantity->prec <= commodity().precision())
+ return realzero();
+ else
+ return round(commodity().precision()).sign() == 0;
+ }
+ return realzero();
+}
+
+amount_t::operator long() const
+{
+ if (! quantity)
+ return 0;
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_q(temp, temp, divisor);
+ return mpz_get_si(temp);
+}
+
+amount_t::operator double() const
+{
+ if (! quantity)
+ return 0.0;
+
+ mpz_t remainder;
+ mpz_init(remainder);
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(temp, remainder, temp, divisor);
+
+ char * quotient_s = mpz_get_str(NULL, 10, temp);
+ char * remainder_s = mpz_get_str(NULL, 10, remainder);
+
+ std::ostringstream num;
+ num << quotient_s << '.' << remainder_s;
+
+ std::free(quotient_s);
+ std::free(remainder_s);
+
+ mpz_clear(remainder);
+
+ return std::atof(num.str().c_str());
+}
+
+amount_t amount_t::value(const moment_t& moment) const
+{
+ if (quantity) {
+ amount_t amt(commodity().value(moment));
+ if (! amt.realzero())
+ return (amt * number()).round();
+ }
+ return *this;
+}
+
+amount_t amount_t::round(unsigned int prec) const
+{
+ amount_t t = *this;
+
+ if (! quantity || quantity->prec <= prec) {
+ if (quantity && quantity->flags & BIGINT_KEEP_PREC) {
+ t._dup();
+ t.quantity->flags &= ~BIGINT_KEEP_PREC;
+ }
+ return t;
+ }
+
+ t._dup();
+
+ mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, prec);
+
+ t.quantity->prec = prec;
+ t.quantity->flags &= ~BIGINT_KEEP_PREC;
+
+ return t;
+}
+
+amount_t amount_t::unround() const
+{
+ if (! quantity) {
+ amount_t t(0L);
+ assert(t.quantity);
+ t.quantity->flags |= BIGINT_KEEP_PREC;
+ return t;
+ }
+ else if (quantity->flags & BIGINT_KEEP_PREC) {
+ return *this;
+ }
+
+ amount_t t = *this;
+ t._dup();
+ t.quantity->flags |= BIGINT_KEEP_PREC;
+
+ return t;
+}
+
+void amount_t::print_quantity(std::ostream& out) const
+{
+ if (! quantity) {
+ out << "0";
+ return;
+ }
+
+ mpz_t quotient;
+ mpz_t rquotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(rquotient);
+ mpz_init(remainder);
+
+ bool negative = false;
+
+ // Ensure the value is rounded to the commodity's precision before
+ // outputting it. NOTE: `rquotient' is used here as a temp variable!
+
+ commodity_t& comm(commodity());
+ unsigned char precision;
+
+ if (! comm || quantity->flags & BIGINT_KEEP_PREC) {
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor);
+ precision = quantity->prec;
+ }
+ else if (comm.precision() < quantity->prec) {
+ mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision());
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (comm.precision() > quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec);
+ mpz_mul(rquotient, MPZ(quantity), divisor);
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor);
+ precision = quantity->prec;
+ }
+ else {
+ mpz_set(quotient, MPZ(quantity));
+ mpz_set_ui(remainder, 0);
+ precision = 0;
+ }
+
+ if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
+ negative = true;
+
+ mpz_abs(quotient, quotient);
+ mpz_abs(remainder, remainder);
+ }
+ mpz_set(rquotient, remainder);
+
+ if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) {
+ out << "0";
+ return;
+ }
+
+ if (negative)
+ out << "-";
+
+ if (mpz_sgn(quotient) == 0) {
+ out << '0';
+ } else {
+ char * p = mpz_get_str(NULL, 10, quotient);
+ out << p;
+ std::free(p);
+ }
+
+ if (precision) {
+ out << '.';
+
+ out.width(precision);
+ out.fill('0');
+
+ char * p = mpz_get_str(NULL, 10, rquotient);
+ out << p;
+ std::free(p);
+ }
+
+ mpz_clear(quotient);
+ mpz_clear(rquotient);
+ mpz_clear(remainder);
+}
+
+void amount_t::print(std::ostream& _out, bool omit_commodity,
+ bool full_precision) const
+{
+ amount_t base(*this);
+ if (! amount_t::keep_base && commodity().larger()) {
+ amount_t last(*this);
+ while (last.commodity().larger()) {
+ last /= last.commodity().larger()->number();
+ last.commodity_ = last.commodity().larger()->commodity_;
+ if (last.abs() < 1)
+ break;
+ base = last.round();
+ }
+ }
+
+ std::ostringstream out;
+
+ mpz_t quotient;
+ mpz_t rquotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(rquotient);
+ mpz_init(remainder);
+
+ bool negative = false;
+
+ // Ensure the value is rounded to the commodity's precision before
+ // outputting it. NOTE: `rquotient' is used here as a temp variable!
+
+ commodity_t& comm(base.commodity());
+ unsigned char precision = 0;
+
+ if (quantity) {
+ if (! comm || full_precision || base.quantity->flags & BIGINT_KEEP_PREC) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else if (comm.precision() < base.quantity->prec) {
+ mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec,
+ comm.precision());
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (comm.precision() > base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec);
+ mpz_mul(rquotient, MPZ(base.quantity), divisor);
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else {
+ mpz_set(quotient, MPZ(base.quantity));
+ mpz_set_ui(remainder, 0);
+ precision = 0;
+ }
+
+ if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
+ negative = true;
+
+ mpz_abs(quotient, quotient);
+ mpz_abs(remainder, remainder);
+ }
+ mpz_set(rquotient, remainder);
+ }
+
+ if (! omit_commodity && ! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) {
+ comm.write(out);
+
+ if (comm.flags() & COMMODITY_STYLE_SEPARATED)
+ out << " ";
+ }
+
+ if (negative)
+ out << "-";
+
+ if (! quantity || mpz_sgn(quotient) == 0) {
+ out << '0';
+ }
+ else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) {
+ char * p = mpz_get_str(NULL, 10, quotient);
+ out << p;
+ std::free(p);
+ }
+ else {
+ std::list<string> strs;
+ char buf[4];
+
+ for (int powers = 0; true; powers += 3) {
+ if (powers > 0) {
+ mpz_ui_pow_ui(divisor, 10, powers);
+ mpz_tdiv_q(temp, quotient, divisor);
+ if (mpz_sgn(temp) == 0)
+ break;
+ mpz_tdiv_r_ui(temp, temp, 1000);
+ } else {
+ mpz_tdiv_r_ui(temp, quotient, 1000);
+ }
+ mpz_get_str(buf, 10, temp);
+ strs.push_back(buf);
+ }
+
+ bool printed = false;
+
+ for (std::list<string>::reverse_iterator i = strs.rbegin();
+ i != strs.rend();
+ i++) {
+ if (printed) {
+ out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ',');
+ out.width(3);
+ out.fill('0');
+ }
+ out << *i;
+
+ printed = true;
+ }
+ }
+
+ if (quantity && precision) {
+ std::ostringstream final;
+ final.width(precision);
+ final.fill('0');
+ char * p = mpz_get_str(NULL, 10, rquotient);
+ final << p;
+ std::free(p);
+
+ const string& str(final.str());
+ int i, len = str.length();
+ const char * q = str.c_str();
+ for (i = len; i > 0; i--)
+ if (q[i - 1] != '0')
+ break;
+
+ string ender;
+ if (i == len)
+ ender = str;
+ else if (i < comm.precision())
+ ender = string(str, 0, comm.precision());
+ else
+ ender = string(str, 0, i);
+
+ if (! ender.empty()) {
+ out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
+ out << ender;
+ }
+ }
+
+ if (! omit_commodity && comm.flags() & COMMODITY_STYLE_SUFFIXED) {
+ if (comm.flags() & COMMODITY_STYLE_SEPARATED)
+ out << " ";
+
+ comm.write(out);
+ }
+
+ mpz_clear(quotient);
+ mpz_clear(rquotient);
+ mpz_clear(remainder);
+
+ // If there are any annotations associated with this commodity,
+ // output them now.
+
+ if (! omit_commodity && comm.annotated) {
+ annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
+ assert(&ann.price != this);
+ ann.write_annotations(out);
+ }
+
+ // Things are output to a string first, so that if anyone has
+ // specified a width or fill for _out, it will be applied to the
+ // entire amount string, and not just the first part.
+
+ _out << out.str();
+
+ return;
+}
+
+static void parse_quantity(std::istream& in, string& value)
+{
+ char buf[256];
+ char c = peek_next_nonws(in);
+ READ_INTO(in, buf, 255, c,
+ std::isdigit(c) || c == '-' || c == '.' || c == ',');
+
+ int len = std::strlen(buf);
+ while (len > 0 && ! std::isdigit(buf[len - 1])) {
+ buf[--len] = '\0';
+ in.unget();
+ }
+
+ value = buf;
+}
+
+// Invalid commodity characters:
+// SPACE, TAB, NEWLINE, RETURN
+// 0-9 . , ; - + * / ^ ? : & | ! =
+// < > { } [ ] ( ) @
+
+int invalid_chars[256] = {
+ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
+/* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+/* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+/* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void parse_commodity(std::istream& in, string& symbol)
+{
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '"') {
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '"');
+ if (c == '"')
+ in.get(c);
+ else
+ throw amount_exception("Quoted commodity symbol lacks closing quote",
+ context());
+ } else {
+ READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]);
+ }
+ symbol = buf;
+}
+
+bool parse_annotations(std::istream& in, amount_t& price,
+ moment_t& date, string& tag)
+{
+ bool has_date = false;
+
+ do {
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '{') {
+ if (price)
+ throw amount_exception("Commodity specifies more than one price",
+ context());
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '}');
+ if (c == '}')
+ in.get(c);
+ else
+ throw amount_exception("Commodity price lacks closing brace", context());
+
+ price.parse(buf, AMOUNT_PARSE_NO_MIGRATE);
+ price.in_place_reduce();
+
+ // Since this price will maintain its own precision, make sure
+ // it is at least as large as the base commodity, since the user
+ // may have only specified {$1} or something similar.
+
+ if (price.has_commodity() &&
+ price.quantity->prec < price.commodity().precision())
+ price = price.round(); // no need to retain individual precision
+ }
+ else if (c == '[') {
+ if (is_valid_moment(date))
+ throw amount_exception("Commodity specifies more than one date",
+ context());
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ']');
+ if (c == ']')
+ in.get(c);
+ else
+ throw amount_exception("Commodity date lacks closing bracket",
+ context());
+
+ date = parse_datetime(buf);
+ has_date = true;
+ }
+ else if (c == '(') {
+ if (! tag.empty())
+ throw amount_exception("Commodity specifies more than one tag",
+ context());
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')')
+ in.get(c);
+ else
+ throw amount_exception("Commodity tag lacks closing parenthesis",
+ context());
+
+ tag = buf;
+ }
+ else {
+ break;
+ }
+ } while (true);
+
+ DEBUG_("amounts.commodities",
+ "Parsed commodity annotations: "
+ << " price " << price << " "
+ << " date " << date << " "
+ << " tag " << tag);
+
+ return has_date;
+}
+
+void amount_t::parse(std::istream& in, unsigned char flags)
+{
+ // The possible syntax for an amount is:
+ //
+ // [-]NUM[ ]SYM [@ AMOUNT]
+ // SYM[ ][-]NUM [@ AMOUNT]
+
+ string symbol;
+ string quant;
+ amount_t tprice;
+ moment_t tdate;
+ bool had_date = false;
+ string tag;
+ unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS;
+ bool negative = false;
+
+ char c = peek_next_nonws(in);
+ if (c == '-') {
+ negative = true;
+ in.get(c);
+ c = peek_next_nonws(in);
+ }
+
+ char n;
+ if (std::isdigit(c)) {
+ parse_quantity(in, quant);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(n))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_commodity(in, symbol);
+
+ if (! symbol.empty())
+ comm_flags |= COMMODITY_STYLE_SUFFIXED;
+
+ if (! in.eof() && ((n = in.peek()) != '\n'))
+ had_date = parse_annotations(in, tprice, tdate, tag);
+ }
+ } else {
+ parse_commodity(in, symbol);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(in.peek()))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_quantity(in, quant);
+
+ if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n'))
+ had_date = parse_annotations(in, tprice, tdate, tag);
+ }
+ }
+
+ if (quant.empty())
+ throw amount_exception("No quantity specified for amount",
+ context());
+
+ _init();
+
+ // Create the commodity if has not already been seen, and update the
+ // precision if something greater was used for the quantity.
+
+ bool newly_created = false;
+
+ if (symbol.empty()) {
+ commodity_ = NULL;
+ } else {
+ commodity_ = commodity_t::find(symbol);
+ if (! commodity_) {
+ commodity_ = commodity_t::create(symbol);
+ newly_created = true;
+ }
+ assert(commodity_);
+
+ if (! tprice.realzero() || had_date || ! tag.empty())
+ commodity_ =
+ annotated_commodity_t::find_or_create(*commodity_, tprice, tdate, tag);
+ }
+
+ // Determine the precision of the amount, based on the usage of
+ // comma or period.
+
+ string::size_type last_comma = quant.rfind(',');
+ string::size_type last_period = quant.rfind('.');
+
+ if (last_comma != string::npos && last_period != string::npos) {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ if (last_comma > last_period) {
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
+ quantity->prec = quant.length() - last_comma - 1;
+ } else {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ }
+ else if (last_comma != string::npos &&
+ commodity().flags() & COMMODITY_STYLE_EUROPEAN) {
+ quantity->prec = quant.length() - last_comma - 1;
+ }
+ else if (last_period != string::npos &&
+ ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ else {
+ quantity->prec = 0;
+ }
+
+ // Set the commodity's flags and precision accordingly
+
+ if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) {
+ commodity().add_flags(comm_flags);
+ if (quantity->prec > commodity().precision())
+ commodity().set_precision(quantity->prec);
+ }
+
+ if (flags & AMOUNT_PARSE_NO_MIGRATE)
+ quantity->flags |= BIGINT_KEEP_PREC;
+
+ // Now we have the final number. Remove commas and periods, if
+ // necessary.
+
+ if (last_comma != string::npos || last_period != string::npos) {
+ int len = quant.length();
+ char * buf = new char[len + 1];
+ const char * p = quant.c_str();
+ char * t = buf;
+
+ while (*p) {
+ if (*p == ',' || *p == '.')
+ p++;
+ *t++ = *p++;
+ }
+ *t = '\0';
+
+ mpz_set_str(MPZ(quantity), buf, 10);
+ delete[] buf;
+ } else {
+ mpz_set_str(MPZ(quantity), quant.c_str(), 10);
+ }
+
+ if (negative)
+ in_place_negate();
+
+ if (! (flags & AMOUNT_PARSE_NO_REDUCE))
+ in_place_reduce();
+}
+
+void amount_t::in_place_reduce()
+{
+ while (commodity_ && commodity().smaller()) {
+ *this *= commodity().smaller()->number();
+ commodity_ = commodity().smaller()->commodity_;
+ }
+}
+
+void parse_conversion(const string& larger_str,
+ const string& smaller_str)
+{
+ amount_t larger, smaller;
+
+ larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE);
+ smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE);
+
+ larger *= smaller.number();
+
+ if (larger.commodity()) {
+ larger.commodity().set_smaller(smaller);
+ larger.commodity().add_flags(smaller.commodity().flags() |
+ COMMODITY_STYLE_NOMARKET);
+ }
+ if (smaller.commodity())
+ smaller.commodity().set_larger(larger);
+}
+
+void amount_t::read(std::istream& in)
+{
+ commodity_t::ident_t ident;
+ read_binary_long(in, ident);
+ if (ident == 0xffffffff)
+ commodity_ = NULL;
+ else if (ident == 0)
+ commodity_ = commodity_t::null_commodity;
+ else
+ commodity_ = (*commodity_t::commodities_by_ident)[ident - 1];
+
+ read_quantity(in);
+}
+
+void amount_t::read(char *& data)
+{
+ commodity_t::ident_t ident;
+ read_binary_long(data, ident);
+ if (ident == 0xffffffff)
+ commodity_ = NULL;
+ else if (ident == 0)
+ commodity_ = commodity_t::null_commodity;
+ else
+ commodity_ = (*commodity_t::commodities_by_ident)[ident - 1];
+
+ read_quantity(data);
+}
+
+void amount_t::write(std::ostream& out) const
+{
+ if (commodity_)
+ write_binary_long(out, commodity_->ident);
+ else
+ write_binary_long<commodity_t::ident_t>(out, 0xffffffff);
+
+ write_quantity(out);
+}
+
+
+#ifndef THREADSAFE
+static char * bigints;
+static char * bigints_next;
+static unsigned int bigints_index;
+static unsigned int bigints_count;
+#endif
+
+void amount_t::read_quantity(char *& data)
+{
+ char byte = *data++;;
+
+ if (byte == 0) {
+ quantity = NULL;
+ }
+ else if (byte == 1) {
+ quantity = new((bigint_t *)bigints_next) bigint_t;
+ bigints_next += sizeof(bigint_t);
+
+ unsigned short len = *((unsigned short *) data);
+ data += sizeof(unsigned short);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, data);
+ data += len;
+
+ char negative = *data++;
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ quantity->prec = *((unsigned char *) data);
+ data += sizeof(unsigned char);
+ quantity->flags = *((unsigned char *) data);
+ data += sizeof(unsigned char);
+ quantity->flags |= BIGINT_BULK_ALLOC;
+ } else {
+ unsigned int index = *((unsigned int *) data);
+ data += sizeof(unsigned int);
+
+ quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t));
+ DEBUG_("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+}
+
+#ifndef THREADSAFE
+static char buf[4096];
+#endif
+
+void amount_t::read_quantity(std::istream& in)
+{
+ char byte;
+ in.read(&byte, sizeof(byte));
+
+ if (byte == 0) {
+ quantity = NULL;
+ }
+ else if (byte == 1) {
+ quantity = new bigint_t;
+
+ unsigned short len;
+ in.read((char *)&len, sizeof(len));
+ assert(len < 4096);
+ in.read(buf, len);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, buf);
+
+ char negative;
+ in.read(&negative, sizeof(negative));
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ in.read((char *)&quantity->prec, sizeof(quantity->prec));
+ in.read((char *)&quantity->flags, sizeof(quantity->flags));
+ }
+ else {
+ assert(0);
+ }
+}
+
+void amount_t::write_quantity(std::ostream& out) const
+{
+ char byte;
+
+ if (! quantity) {
+ byte = 0;
+ out.write(&byte, sizeof(byte));
+ return;
+ }
+
+ if (quantity->index == 0) {
+ quantity->index = ++bigints_index;
+ bigints_count++;
+
+ byte = 1;
+ out.write(&byte, sizeof(byte));
+
+ std::size_t size;
+ mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity));
+ unsigned short len = size * sizeof(short);
+ out.write((char *)&len, sizeof(len));
+ if (len) {
+ assert(len < 4096);
+ out.write(buf, len);
+ }
+
+ byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0;
+ out.write(&byte, sizeof(byte));
+
+ out.write((char *)&quantity->prec, sizeof(quantity->prec));
+ unsigned char flags = quantity->flags & ~BIGINT_BULK_ALLOC;
+ assert(sizeof(flags) == sizeof(quantity->flags));
+ out.write((char *)&flags, sizeof(flags));
+ } else {
+ assert(quantity->ref > 1);
+
+ // Since this value has already been written, we simply write
+ // out a reference to which one it was.
+ byte = 2;
+ out.write(&byte, sizeof(byte));
+ out.write((char *)&quantity->index, sizeof(quantity->index));
+ }
+}
+
+bool amount_t::valid() const
+{
+ if (quantity) {
+ if (quantity->ref == 0) {
+ DEBUG_("ledger.validate", "amount_t: quantity->ref == 0");
+ return false;
+ }
+ }
+ else if (commodity_) {
+ DEBUG_("ledger.validate", "amount_t: commodity_ != NULL");
+ return false;
+ }
+ return true;
+}
+
+void amount_t::annotate_commodity(const amount_t& tprice,
+ const moment_t& tdate,
+ const string& tag)
+{
+ const commodity_t * this_base;
+ annotated_commodity_t * this_ann = NULL;
+
+ if (commodity().annotated) {
+ this_ann = &static_cast<annotated_commodity_t&>(commodity());
+ this_base = this_ann->ptr;
+ } else {
+ this_base = &commodity();
+ }
+ assert(this_base);
+
+ DEBUG_("amounts.commodities", "Annotating commodity for amount "
+ << *this << std::endl
+ << " price " << tprice << " "
+ << " date " << tdate << " "
+ << " tag " << tag);
+
+ commodity_t * ann_comm =
+ annotated_commodity_t::find_or_create
+ (*this_base, ! tprice && this_ann ? this_ann->price : tprice,
+ ! is_valid_moment(tdate) && this_ann ? this_ann->date : tdate,
+ tag.empty() && this_ann ? this_ann->tag : tag);
+ if (ann_comm)
+ set_commodity(*ann_comm);
+
+ DEBUG_("amounts.commodities", " Annotated amount is " << *this);
+}
+
+amount_t amount_t::strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag) const
+{
+ if (! commodity().annotated ||
+ (_keep_price && _keep_date && _keep_tag))
+ return *this;
+
+ DEBUG_("amounts.commodities", "Reducing commodity for amount "
+ << *this << std::endl
+ << " keep price " << _keep_price << " "
+ << " keep date " << _keep_date << " "
+ << " keep tag " << _keep_tag);
+
+ annotated_commodity_t&
+ ann_comm(static_cast<annotated_commodity_t&>(commodity()));
+ assert(ann_comm.base);
+
+ commodity_t * new_comm;
+
+ if ((_keep_price && ann_comm.price) ||
+ (_keep_date && is_valid_moment(ann_comm.date)) ||
+ (_keep_tag && ! ann_comm.tag.empty()))
+ {
+ new_comm = annotated_commodity_t::find_or_create
+ (*ann_comm.ptr, _keep_price ? ann_comm.price : amount_t(),
+ _keep_date ? ann_comm.date : moment_t(),
+ _keep_tag ? ann_comm.tag : "");
+ } else {
+ new_comm = commodity_t::find_or_create(ann_comm.base_symbol());
+ }
+ assert(new_comm);
+
+ amount_t t(*this);
+ t.set_commodity(*new_comm);
+ DEBUG_("amounts.commodities", " Reduced amount is " << t);
+
+ return t;
+}
+
+amount_t amount_t::price() const
+{
+ if (commodity_ && commodity_->annotated) {
+ amount_t t(((annotated_commodity_t *)commodity_)->price);
+ t *= number();
+ DEBUG_("amounts.commodities",
+ "Returning price of " << *this << " = " << t);
+ return t;
+ }
+ return *this;
+}
+
+moment_t amount_t::date() const
+{
+ if (commodity_ && commodity_->annotated) {
+ DEBUG_("amounts.commodities",
+ "Returning date of " << *this << " = "
+ << ((annotated_commodity_t *)commodity_)->date);
+ return ((annotated_commodity_t *)commodity_)->date;
+ }
+ return moment_t();
+}
+
+
+void commodity_base_t::add_price(const moment_t& date,
+ const amount_t& price)
+{
+ if (! history)
+ history = new history_t;
+
+ history_map::iterator i = history->prices.find(date);
+ if (i != history->prices.end()) {
+ (*i).second = price;
+ } else {
+ std::pair<history_map::iterator, bool> result
+ = history->prices.insert(history_pair(date, price));
+ assert(result.second);
+ }
+}
+
+bool commodity_base_t::remove_price(const moment_t& date)
+{
+ if (history) {
+ history_map::size_type n = history->prices.erase(date);
+ if (n > 0) {
+ if (history->prices.empty())
+ history = NULL;
+ return true;
+ }
+ }
+ return false;
+}
+
+commodity_base_t * commodity_base_t::create(const string& symbol)
+{
+ commodity_base_t * commodity = new commodity_base_t(symbol);
+
+ DEBUG_("amounts.commodities", "Creating base commodity " << symbol);
+
+ std::pair<base_commodities_map::iterator, bool> result
+ = commodities.insert(base_commodities_pair(symbol, commodity));
+ assert(result.second);
+
+ return commodity;
+}
+
+bool commodity_t::needs_quotes(const string& symbol)
+{
+ for (const char * p = symbol.c_str(); *p; p++)
+ if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.')
+ return true;
+
+ return false;
+}
+
+bool commodity_t::valid() const
+{
+ if (symbol().empty() && this != null_commodity) {
+ DEBUG_("ledger.validate",
+ "commodity_t: symbol().empty() && this != null_commodity");
+ return false;
+ }
+
+ if (annotated && ! base) {
+ DEBUG_("ledger.validate", "commodity_t: annotated && ! base");
+ return false;
+ }
+
+ if (precision() > 16) {
+ DEBUG_("ledger.validate", "commodity_t: precision() > 16");
+ return false;
+ }
+
+ return true;
+}
+
+commodity_t * commodity_t::create(const string& symbol)
+{
+ std::auto_ptr<commodity_t> commodity(new commodity_t);
+
+ commodity->base = commodity_base_t::create(symbol);
+
+ if (needs_quotes(symbol)) {
+ commodity->qualified_symbol = "\"";
+ commodity->qualified_symbol += symbol;
+ commodity->qualified_symbol += "\"";
+ } else {
+ commodity->qualified_symbol = symbol;
+ }
+
+ DEBUG_("amounts.commodities",
+ "Creating commodity " << commodity->qualified_symbol);
+
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_pair(symbol, commodity.get()));
+ if (! result.second)
+ return NULL;
+
+ commodity->ident = commodities_by_ident->size();
+ commodities_by_ident->push_back(commodity.get());
+
+ // Start out the new commodity with the default commodity's flags
+ // and precision, if one has been defined.
+ if (default_commodity)
+ commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
+ COMMODITY_STYLE_NOMARKET);
+
+ return commodity.release();
+}
+
+commodity_t * commodity_t::find_or_create(const string& symbol)
+{
+ DEBUG_("amounts.commodities", "Find-or-create commodity " << symbol);
+
+ commodity_t * commodity = find(symbol);
+ if (commodity)
+ return commodity;
+ return create(symbol);
+}
+
+commodity_t * commodity_t::find(const string& symbol)
+{
+ DEBUG_("amounts.commodities", "Find commodity " << symbol);
+
+ commodities_map::const_iterator i = commodities.find(symbol);
+ if (i != commodities.end())
+ return (*i).second;
+ return NULL;
+}
+
+amount_t commodity_base_t::value(const moment_t& moment)
+{
+ moment_t age;
+ amount_t price;
+
+ if (history) {
+ assert(history->prices.size() > 0);
+
+ if (! is_valid_moment(moment)) {
+ history_map::reverse_iterator r = history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ history_map::iterator i = history->prices.lower_bound(moment);
+ if (i == history->prices.end()) {
+ history_map::reverse_iterator r = history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ age = (*i).first;
+ if (moment != age) {
+ if (i != history->prices.begin()) {
+ --i;
+ age = (*i).first;
+ price = (*i).second;
+ } else {
+ age = moment_t();
+ }
+ } else {
+ price = (*i).second;
+ }
+ }
+ }
+ }
+
+ if (updater && ! (flags & COMMODITY_STYLE_NOMARKET))
+ (*updater)(*this, moment, age,
+ (history && history->prices.size() > 0 ?
+ (*history->prices.rbegin()).first : moment_t()), price);
+
+ return price;
+}
+
+bool annotated_commodity_t::operator==(const commodity_t& comm) const
+{
+ // If the base commodities don't match, the game's up.
+ if (base != comm.base)
+ return false;
+
+ if (price &&
+ (! comm.annotated ||
+ price != static_cast<const annotated_commodity_t&>(comm).price))
+ return false;
+
+ if (is_valid_moment(date) &&
+ (! comm.annotated ||
+ date != static_cast<const annotated_commodity_t&>(comm).date))
+ return false;
+
+ if (! tag.empty() &&
+ (! comm.annotated ||
+ tag != static_cast<const annotated_commodity_t&>(comm).tag))
+ return false;
+
+ return true;
+}
+
+void
+annotated_commodity_t::write_annotations(std::ostream& out,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag)
+{
+ if (price)
+ out << " {" << price << '}';
+
+ if (is_valid_moment(date))
+ out << " [" << date << ']';
+
+ if (! tag.empty())
+ out << " (" << tag << ')';
+}
+
+commodity_t *
+annotated_commodity_t::create(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag,
+ const string& mapping_key)
+{
+ std::auto_ptr<annotated_commodity_t> commodity(new annotated_commodity_t);
+
+ // Set the annotated bits
+ commodity->price = price;
+ commodity->date = date;
+ commodity->tag = tag;
+
+ commodity->ptr = &comm;
+ assert(commodity->ptr);
+ commodity->base = comm.base;
+ assert(commodity->base);
+
+ commodity->qualified_symbol = comm.symbol();
+
+ DEBUG_("amounts.commodities", "Creating annotated commodity "
+ << "symbol " << commodity->symbol()
+ << " key " << mapping_key << std::endl
+ << " price " << price << " "
+ << " date " << date << " "
+ << " tag " << tag);
+
+ // Add the fully annotated name to the map, so that this symbol may
+ // quickly be found again.
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_pair(mapping_key, commodity.get()));
+ if (! result.second)
+ return NULL;
+
+ commodity->ident = commodities_by_ident->size();
+ commodities_by_ident->push_back(commodity.get());
+
+ return commodity.release();
+}
+
+namespace {
+ string make_qualified_name(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag)
+ {
+ if (price < 0)
+ throw amount_exception("A commodity's price may not be negative",
+ context());
+
+ std::ostringstream name;
+
+ comm.write(name);
+ annotated_commodity_t::write_annotations(name, price, date, tag);
+
+ DEBUG_("amounts.commodities", "make_qualified_name for "
+ << comm.qualified_symbol << std::endl
+ << " price " << price << " "
+ << " date " << date << " "
+ << " tag " << tag);
+
+ DEBUG_("amounts.commodities", "qualified_name is " << name.str());
+
+ return name.str();
+ }
+}
+
+commodity_t *
+annotated_commodity_t::find_or_create(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag)
+{
+ string name = make_qualified_name(comm, price, date, tag);
+
+ commodity_t * ann_comm = commodity_t::find(name);
+ if (ann_comm) {
+ assert(ann_comm->annotated);
+ return ann_comm;
+ }
+ return create(comm, price, date, tag, name);
+}
+
+bool compare_amount_commodities::operator()(const amount_t * left,
+ const amount_t * right) const
+{
+ commodity_t& leftcomm(left->commodity());
+ commodity_t& rightcomm(right->commodity());
+
+ int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
+ if (cmp != 0)
+ return cmp < 0;
+
+ if (! leftcomm.annotated) {
+ assert(rightcomm.annotated);
+ return true;
+ }
+ else if (! rightcomm.annotated) {
+ assert(leftcomm.annotated);
+ return false;
+ }
+ else {
+ annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
+ annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
+
+ if (! aleftcomm.price && arightcomm.price)
+ return true;
+ if (aleftcomm.price && ! arightcomm.price)
+ return false;
+
+ if (aleftcomm.price && arightcomm.price) {
+ amount_t leftprice(aleftcomm.price);
+ leftprice.in_place_reduce();
+ amount_t rightprice(arightcomm.price);
+ rightprice.in_place_reduce();
+
+ if (leftprice.commodity() == rightprice.commodity()) {
+ amount_t val = leftprice - rightprice;
+ if (val)
+ return val < 0;
+ } else {
+ // Since we have two different amounts, there's really no way
+ // to establish a true sorting order; we'll just do it based
+ // on the numerical values.
+ leftprice.clear_commodity();
+ rightprice.clear_commodity();
+
+ amount_t val = leftprice - rightprice;
+ if (val)
+ return val < 0;
+ }
+ }
+
+ if (! is_valid_moment(aleftcomm.date) &&
+ is_valid_moment(arightcomm.date))
+ return true;
+ if (is_valid_moment(aleftcomm.date) &&
+ ! is_valid_moment(arightcomm.date))
+ return false;
+
+ if (is_valid_moment(aleftcomm.date) &&
+ is_valid_moment(arightcomm.date)) {
+ duration_t diff = aleftcomm.date - arightcomm.date;
+ return diff.is_negative();
+ }
+
+ if (aleftcomm.tag.empty() && ! arightcomm.tag.empty())
+ return true;
+ if (! aleftcomm.tag.empty() && arightcomm.tag.empty())
+ return false;
+
+ if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty())
+ return aleftcomm.tag < arightcomm.tag;
+
+ assert(0);
+ return true;
+ }
+}
+
+} // namespace ledger