summaryrefslogtreecommitdiff
path: root/src/amount.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/amount.cc')
-rw-r--r--src/amount.cc1278
1 files changed, 1278 insertions, 0 deletions
diff --git a/src/amount.cc b/src/amount.cc
new file mode 100644
index 00000000..eddbca18
--- /dev/null
+++ b/src/amount.cc
@@ -0,0 +1,1278 @@
+/*
+ * Copyright (c) 2003-2009, 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 <system.hh>
+
+#include "amount.h"
+#include "commodity.h"
+#include "annotate.h"
+#include "pool.h"
+
+namespace ledger {
+
+bool amount_t::stream_fullstrings = false;
+
+#if !defined(THREADSAFE)
+// These global temporaries are pre-initialized for the sake of
+// efficiency, and are reused over and over again.
+static mpz_t temp;
+static mpq_t tempq;
+static mpfr_t tempf;
+static mpfr_t tempfb;
+#endif
+
+struct amount_t::bigint_t : public supports_flags<>
+{
+#define BIGINT_BULK_ALLOC 0x01
+#define BIGINT_KEEP_PREC 0x02
+
+ mpq_t val;
+ precision_t prec;
+ uint_least32_t refc;
+
+#define MP(bigint) ((bigint)->val)
+
+ bigint_t() : prec(0), refc(1) {
+ TRACE_CTOR(bigint_t, "");
+ mpq_init(val);
+ }
+ bigint_t(const bigint_t& other)
+ : supports_flags<>(static_cast<uint_least8_t>
+ (other.flags() & ~BIGINT_BULK_ALLOC)),
+ prec(other.prec), refc(1) {
+ TRACE_CTOR(bigint_t, "copy");
+ mpq_init(val);
+ mpq_set(val, other.val);
+ }
+ ~bigint_t() {
+ TRACE_DTOR(bigint_t);
+ assert(refc == 0);
+ mpq_clear(val);
+ }
+
+ bool valid() const {
+ if (prec > 1024) {
+ DEBUG("ledger.validate", "amount_t::bigint_t: prec > 1024");
+ return false;
+ }
+ if (flags() & ~(BIGINT_BULK_ALLOC | BIGINT_KEEP_PREC)) {
+ DEBUG("ledger.validate",
+ "amount_t::bigint_t: flags() & ~(BULK_ALLOC | KEEP_PREC)");
+ return false;
+ }
+ return true;
+ }
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+private:
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /* version */)
+ {
+ ar & boost::serialization::base_object<supports_flags<> >(*this);
+ ar & val;
+ ar & prec;
+ ar & refc;
+ }
+#endif // HAVE_BOOST_SERIALIZATION
+};
+
+bool amount_t::is_initialized = false;
+
+namespace {
+ void stream_out_mpq(std::ostream& out,
+ mpq_t quant,
+ amount_t::precision_t prec,
+ int zeros_prec = -1,
+ const optional<commodity_t&>& comm = none)
+ {
+ char * buf = NULL;
+ try {
+ IF_DEBUG("amount.convert") {
+ char * tbuf = mpq_get_str(NULL, 10, quant);
+ DEBUG("amount.convert", "Rational to convert = " << tbuf);
+ std::free(tbuf);
+ }
+
+ // Convert the rational number to a floating-point, extending the
+ // floating-point to a large enough size to get a precise answer.
+ const std::size_t bits = (mpz_sizeinbase(mpq_numref(quant), 2) +
+ mpz_sizeinbase(mpq_denref(quant), 2));
+ mpfr_set_prec(tempfb, bits + amount_t::extend_by_digits*8);
+ mpfr_set_q(tempfb, quant, GMP_RNDN);
+
+ mpfr_asprintf(&buf, "%.*Rf", prec, tempfb);
+ DEBUG("amount.convert",
+ "mpfr_print = " << buf << " (precision " << prec << ")");
+
+ if (zeros_prec >= 0) {
+ string::size_type index = std::strlen(buf);
+ string::size_type point = 0;
+ for (string::size_type i = 0; i < index; i++) {
+ if (buf[i] == '.') {
+ point = i;
+ break;
+ }
+ }
+ if (point > 0) {
+ while (--index >= (point + 1 + zeros_prec) && buf[index] == '0')
+ buf[index] = '\0';
+ if (index >= (point + zeros_prec) && buf[index] == '.')
+ buf[index] = '\0';
+ }
+ }
+
+ if (comm) {
+ int integer_digits = 0;
+ if (comm && comm->has_flags(COMMODITY_STYLE_THOUSANDS)) {
+ // Count the number of integer digits
+ for (const char * p = buf; *p; p++) {
+ if (*p == '.')
+ break;
+ else if (*p != '-')
+ integer_digits++;
+ }
+ }
+
+ for (const char * p = buf; *p; p++) {
+ if (*p == '.') {
+ if (commodity_t::european_by_default ||
+ (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
+ out << ',';
+ else
+ out << *p;
+ assert(integer_digits <= 3);
+ }
+ else if (*p == '-') {
+ out << *p;
+ }
+ else {
+ out << *p;
+
+ if (integer_digits > 3 && --integer_digits % 3 == 0) {
+ if (commodity_t::european_by_default ||
+ (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)))
+ out << '.';
+ else
+ out << ',';
+ }
+ }
+ }
+ } else {
+ out << buf;
+ }
+ }
+ catch (...) {
+ if (buf != NULL)
+ mpfr_free_str(buf);
+ throw;
+ }
+ if (buf != NULL)
+ mpfr_free_str(buf);
+ }
+}
+
+void amount_t::initialize()
+{
+ if (! is_initialized) {
+ mpz_init(temp);
+ mpq_init(tempq);
+ mpfr_init(tempf);
+ mpfr_init(tempfb);
+
+ commodity_pool_t::current_pool.reset(new commodity_pool_t);
+
+ // Add time commodity conversions, so that timelog's may be parsed
+ // in terms of seconds, but reported as minutes or hours.
+ if (commodity_t * commodity = commodity_pool_t::current_pool->create("s"))
+ commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+ else
+ assert(false);
+
+ // Add a "percentile" commodity
+ if (commodity_t * commodity = commodity_pool_t::current_pool->create("%"))
+ commodity->add_flags(COMMODITY_BUILTIN | COMMODITY_NOMARKET);
+ else
+ assert(false);
+
+ is_initialized = true;
+ }
+}
+
+void amount_t::shutdown()
+{
+ if (is_initialized) {
+ mpz_clear(temp);
+ mpq_clear(tempq);
+ mpfr_clear(tempf);
+ mpfr_clear(tempfb);
+
+ commodity_pool_t::current_pool.reset();
+
+ is_initialized = false;
+ }
+}
+
+void amount_t::_copy(const amount_t& amt)
+{
+ VERIFY(amt.valid());
+
+ 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->has_flags(BIGINT_BULK_ALLOC)) {
+ quantity = new bigint_t(*amt.quantity);
+ } else {
+ quantity = amt.quantity;
+ DEBUG("amounts.refs",
+ quantity << " refc++, now " << (quantity->refc + 1));
+ quantity->refc++;
+ }
+ }
+ commodity_ = amt.commodity_;
+
+ VERIFY(valid());
+}
+
+void amount_t::_dup()
+{
+ VERIFY(valid());
+
+ if (quantity->refc > 1) {
+ bigint_t * q = new bigint_t(*quantity);
+ _release();
+ quantity = q;
+ }
+
+ VERIFY(valid());
+}
+
+void amount_t::_clear()
+{
+ if (quantity) {
+ _release();
+ quantity = NULL;
+ commodity_ = NULL;
+ } else {
+ assert(! commodity_);
+ }
+}
+
+void amount_t::_release()
+{
+ VERIFY(valid());
+
+ DEBUG("amounts.refs", quantity << " refc--, now " << (quantity->refc - 1));
+
+ if (--quantity->refc == 0) {
+ if (quantity->has_flags(BIGINT_BULK_ALLOC))
+ quantity->~bigint_t();
+ else
+ checked_delete(quantity);
+ quantity = NULL;
+ commodity_ = NULL;
+ }
+
+ VERIFY(valid());
+}
+
+
+amount_t::amount_t(const double val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const double");
+ quantity = new bigint_t;
+ mpq_set_d(MP(quantity), val);
+ quantity->prec = extend_by_digits; // an approximation
+}
+
+amount_t::amount_t(const unsigned long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const unsigned long");
+ quantity = new bigint_t;
+ mpq_set_ui(MP(quantity), val, 1);
+}
+
+amount_t::amount_t(const long val) : commodity_(NULL)
+{
+ TRACE_CTOR(amount_t, "const long");
+ quantity = new bigint_t;
+ mpq_set_si(MP(quantity), val, 1);
+}
+
+
+amount_t& amount_t::operator=(const amount_t& amt)
+{
+ if (this != &amt) {
+ if (amt.quantity)
+ _copy(amt);
+ else if (quantity)
+ _clear();
+ }
+ return *this;
+}
+
+
+int amount_t::compare(const amount_t& amt) const
+{
+ VERIFY(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, _("Cannot compare an amount to an uninitialized amount"));
+ else if (amt.quantity)
+ throw_(amount_error, _("Cannot compare an uninitialized amount to an amount"));
+ else
+ throw_(amount_error, _("Cannot compare two uninitialized amounts"));
+ }
+
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ _("Cannot compare amounts with different commodities: %1 and %2")
+ << commodity().symbol() << amt.commodity().symbol());
+
+ return mpq_cmp(MP(quantity), MP(amt.quantity));
+}
+
+bool amount_t::operator==(const amount_t& amt) const
+{
+ if ((quantity && ! amt.quantity) || (! quantity && amt.quantity))
+ return false;
+ else if (! quantity && ! amt.quantity)
+ return true;
+ else if (commodity() != amt.commodity())
+ return false;
+
+ return mpq_equal(MP(quantity), MP(amt.quantity));
+}
+
+
+amount_t& amount_t::operator+=(const amount_t& amt)
+{
+ VERIFY(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, _("Cannot add an uninitialized amount to an amount"));
+ else if (amt.quantity)
+ throw_(amount_error, _("Cannot add an amount to an uninitialized amount"));
+ else
+ throw_(amount_error, _("Cannot add two uninitialized amounts"));
+ }
+
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ _("Adding amounts with different commodities: %1 != %2")
+ << (has_commodity() ? commodity().symbol() : _("NONE"))
+ << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE")));
+
+ _dup();
+
+ mpq_add(MP(quantity), MP(quantity), MP(amt.quantity));
+
+ if (has_commodity() == amt.has_commodity())
+ if (quantity->prec < amt.quantity->prec)
+ quantity->prec = amt.quantity->prec;
+
+ return *this;
+}
+
+amount_t& amount_t::operator-=(const amount_t& amt)
+{
+ VERIFY(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, _("Cannot subtract an amount from an uninitialized amount"));
+ else if (amt.quantity)
+ throw_(amount_error, _("Cannot subtract an uninitialized amount from an amount"));
+ else
+ throw_(amount_error, _("Cannot subtract two uninitialized amounts"));
+ }
+
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity())
+ throw_(amount_error,
+ _("Subtracting amounts with different commodities: %1 != %2")
+ << (has_commodity() ? commodity().symbol() : _("NONE"))
+ << (amt.has_commodity() ? amt.commodity().symbol() : _("NONE")));
+
+ _dup();
+
+ mpq_sub(MP(quantity), MP(quantity), MP(amt.quantity));
+
+ if (has_commodity() == amt.has_commodity())
+ if (quantity->prec < amt.quantity->prec)
+ quantity->prec = amt.quantity->prec;
+
+ return *this;
+}
+
+amount_t& amount_t::operator*=(const amount_t& amt)
+{
+ VERIFY(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, _("Cannot multiply an amount by an uninitialized amount"));
+ else if (amt.quantity)
+ throw_(amount_error, _("Cannot multiply an uninitialized amount by an amount"));
+ else
+ throw_(amount_error, _("Cannot multiply two uninitialized amounts"));
+ }
+
+ _dup();
+
+ mpq_mul(MP(quantity), MP(quantity), MP(amt.quantity));
+ quantity->prec =
+ static_cast<precision_t>(quantity->prec + amt.quantity->prec);
+
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ if (has_commodity() && ! keep_precision()) {
+ precision_t comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + extend_by_digits)
+ quantity->prec = static_cast<precision_t>(comm_prec + extend_by_digits);
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator/=(const amount_t& amt)
+{
+ VERIFY(amt.valid());
+
+ if (! quantity || ! amt.quantity) {
+ if (quantity)
+ throw_(amount_error, _("Cannot divide an amount by an uninitialized amount"));
+ else if (amt.quantity)
+ throw_(amount_error, _("Cannot divide an uninitialized amount by an amount"));
+ else
+ throw_(amount_error, _("Cannot divide two uninitialized amounts"));
+ }
+
+ if (! amt)
+ throw_(amount_error, _("Divide by zero"));
+
+ _dup();
+
+ // Increase the value's precision, to capture fractional parts after
+ // the divide. Round up in the last position.
+
+ mpq_div(MP(quantity), MP(quantity), MP(amt.quantity));
+ quantity->prec =
+ static_cast<precision_t>(quantity->prec + amt.quantity->prec +
+ extend_by_digits);
+
+ 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() && ! keep_precision()) {
+ precision_t comm_prec = commodity().precision();
+ if (quantity->prec > comm_prec + extend_by_digits)
+ quantity->prec = static_cast<precision_t>(comm_prec + extend_by_digits);
+ }
+
+ return *this;
+}
+
+amount_t::precision_t amount_t::precision() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine precision of an uninitialized amount"));
+
+ return quantity->prec;
+}
+
+bool amount_t::keep_precision() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine if precision of an uninitialized amount is kept"));
+
+ return quantity->has_flags(BIGINT_KEEP_PREC);
+}
+
+void amount_t::set_keep_precision(const bool keep) const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot set whether to keep the precision of an uninitialized amount"));
+
+ if (keep)
+ quantity->add_flags(BIGINT_KEEP_PREC);
+ else
+ quantity->drop_flags(BIGINT_KEEP_PREC);
+}
+
+amount_t::precision_t amount_t::display_precision() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine display precision of an uninitialized amount"));
+
+ commodity_t& comm(commodity());
+
+ if (! comm || keep_precision())
+ return quantity->prec;
+ else if (comm.precision() != quantity->prec)
+ return comm.precision();
+ else
+ return quantity->prec;
+}
+
+void amount_t::in_place_negate()
+{
+ if (quantity) {
+ _dup();
+ mpq_neg(MP(quantity), MP(quantity));
+ } else {
+ throw_(amount_error, _("Cannot negate an uninitialized amount"));
+ }
+}
+
+amount_t amount_t::inverted() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot invert an uninitialized amount"));
+
+ amount_t t(*this);
+ t._dup();
+ mpq_inv(MP(t.quantity), MP(t.quantity));
+
+ return t;
+}
+
+void amount_t::in_place_round()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot set rounding for an uninitialized amount"));
+ else if (! keep_precision())
+ return;
+
+ _dup();
+ set_keep_precision(false);
+}
+
+void amount_t::in_place_floor()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot floor an uninitialized amount"));
+
+ _dup();
+
+ std::ostringstream out;
+ stream_out_mpq(out, MP(quantity), 0);
+
+ mpq_set_str(MP(quantity), out.str().c_str(), 10);
+}
+
+void amount_t::in_place_unround()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot unround an uninitialized amount"));
+ else if (keep_precision())
+ return;
+
+ _dup();
+
+ DEBUG("amount.unround", "Unrounding " << *this);
+ set_keep_precision(true);
+ DEBUG("amount.unround", "Unrounded = " << *this);
+}
+
+void amount_t::in_place_reduce()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot reduce an uninitialized amount"));
+
+ while (commodity_ && commodity().smaller()) {
+ *this *= commodity().smaller()->number();
+ commodity_ = commodity().smaller()->commodity_;
+ }
+}
+
+void amount_t::in_place_unreduce()
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot unreduce an uninitialized amount"));
+
+ amount_t temp = *this;
+ commodity_t * comm = commodity_;
+ bool shifted = false;
+
+ while (comm && comm->larger()) {
+ amount_t next_temp = temp / comm->larger()->number();
+ if (next_temp.abs() < amount_t(1L))
+ break;
+ temp = next_temp;
+ comm = comm->larger()->commodity_;
+ shifted = true;
+ }
+
+ if (shifted) {
+ *this = temp;
+ commodity_ = comm;
+ }
+}
+
+optional<amount_t>
+amount_t::value(const bool primary_only,
+ const optional<datetime_t>& moment,
+ const optional<commodity_t&>& in_terms_of) const
+{
+ if (quantity) {
+#if defined(DEBUG_ON)
+ DEBUG("commodity.prices.find",
+ "amount_t::value of " << commodity().symbol());
+ if (moment)
+ DEBUG("commodity.prices.find",
+ "amount_t::value: moment = " << *moment);
+ if (in_terms_of)
+ DEBUG("commodity.prices.find",
+ "amount_t::value: in_terms_of = " << in_terms_of->symbol());
+#endif
+ if (has_commodity() &&
+ (! primary_only || ! commodity().has_flags(COMMODITY_PRIMARY))) {
+ if (in_terms_of && commodity() == *in_terms_of) {
+ return *this;
+ }
+ else if (has_annotation() && annotation().price &&
+ annotation().has_flags(ANNOTATION_PRICE_FIXATED)) {
+ return (*annotation().price * number()).rounded();
+ }
+ else {
+ optional<price_point_t> point =
+ commodity().find_price(in_terms_of, moment);
+
+ // Whether a price was found or not, check whether we should attempt
+ // to download a price from the Internet. This is done if (a) no
+ // price was found, or (b) the price is "stale" according to the
+ // setting of --price-exp.
+ point = commodity().check_for_updated_price(point, moment, in_terms_of);
+ if (point)
+ return (point->price * number()).rounded();
+ }
+ }
+ } else {
+ throw_(amount_error,
+ _("Cannot determine value of an uninitialized amount"));
+ }
+ return none;
+}
+
+amount_t amount_t::price() const
+{
+ if (has_annotation() && annotation().price) {
+ amount_t temp(*annotation().price);
+ temp *= *this;
+ DEBUG("amount.price", "Returning price of " << *this << " = " << temp);
+ return temp;
+ }
+ return *this;
+}
+
+
+int amount_t::sign() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot determine sign of an uninitialized amount"));
+
+ return mpq_sgn(MP(quantity));
+}
+
+bool amount_t::is_zero() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot determine if an uninitialized amount is zero"));
+
+ if (has_commodity()) {
+ if (keep_precision() || quantity->prec <= commodity().precision()) {
+ return is_realzero();
+ }
+ else if (is_realzero()) {
+ return true;
+ }
+ else if (mpz_cmp(mpq_numref(MP(quantity)),
+ mpq_denref(MP(quantity))) > 0) {
+ DEBUG("amount.is_zero", "Numerator is larger than the denominator");
+ return false;
+ }
+ else {
+ DEBUG("amount.is_zero", "We have to print the number to check for zero");
+
+ std::ostringstream out;
+ stream_out_mpq(out, MP(quantity), commodity().precision());
+
+ string output = out.str();
+ if (! output.empty()) {
+ for (const char * p = output.c_str(); *p; p++)
+ if (*p != '0' && *p != '.' && *p != '-')
+ return false;
+ }
+ return true;
+ }
+ }
+ return is_realzero();
+}
+
+
+double amount_t::to_double() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot convert an uninitialized amount to a double"));
+
+ mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
+ return mpfr_get_d(tempf, GMP_RNDN);
+}
+
+long amount_t::to_long() const
+{
+ if (! quantity)
+ throw_(amount_error, _("Cannot convert an uninitialized amount to a long"));
+
+ mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
+ return mpfr_get_si(tempf, GMP_RNDN);
+}
+
+bool amount_t::fits_in_long() const
+{
+ mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
+ return mpfr_fits_slong_p(tempf, GMP_RNDN);
+}
+
+commodity_t& amount_t::commodity() const
+{
+ return (has_commodity() ?
+ *commodity_ : *commodity_pool_t::current_pool->null_commodity);
+}
+
+bool amount_t::has_commodity() const
+{
+ return commodity_ && commodity_ != commodity_->pool().null_commodity;
+}
+
+void amount_t::annotate(const annotation_t& details)
+{
+ commodity_t * this_base;
+ annotated_commodity_t * this_ann = NULL;
+
+ if (! quantity)
+ throw_(amount_error, _("Cannot annotate the commodity of an uninitialized amount"));
+ else if (! has_commodity())
+ return; // ignore attempt to annotate a "bare commodity
+
+ if (commodity().has_annotation()) {
+ this_ann = &as_annotated_commodity(commodity());
+ this_base = &this_ann->referent();
+ } else {
+ this_base = &commodity();
+ }
+ assert(this_base);
+
+ DEBUG("amounts.commodities", "Annotating commodity for amount "
+ << *this << std::endl << details);
+
+ if (commodity_t * ann_comm =
+ this_base->pool().find_or_create(*this_base, details))
+ set_commodity(*ann_comm);
+#ifdef ASSERTS_ON
+ else
+ assert(false);
+#endif
+
+ DEBUG("amounts.commodities", "Annotated amount is " << *this);
+}
+
+bool amount_t::has_annotation() const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot determine if an uninitialized amount's commodity is annotated"));
+
+ assert(! has_commodity() || ! commodity().has_annotation() ||
+ as_annotated_commodity(commodity()).details);
+ return has_commodity() && commodity().has_annotation();
+}
+
+annotation_t& amount_t::annotation()
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot return commodity annotation details of an uninitialized amount"));
+
+ if (! commodity().has_annotation())
+ throw_(amount_error,
+ _("Request for annotation details from an unannotated amount"));
+
+ annotated_commodity_t& ann_comm(as_annotated_commodity(commodity()));
+ return ann_comm.details;
+}
+
+amount_t amount_t::strip_annotations(const keep_details_t& what_to_keep) const
+{
+ if (! quantity)
+ throw_(amount_error,
+ _("Cannot strip commodity annotations from an uninitialized amount"));
+
+ if (! what_to_keep.keep_all(commodity())) {
+ amount_t t(*this);
+ t.set_commodity(commodity().strip_annotations(what_to_keep));
+ return t;
+ }
+ return *this;
+}
+
+
+namespace {
+ 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 == ',');
+
+ string::size_type len = std::strlen(buf);
+ while (len > 0 && ! std::isdigit(buf[len - 1])) {
+ buf[--len] = '\0';
+ in.unget();
+ }
+
+ value = buf;
+ }
+}
+
+bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
+{
+ // The possible syntax for an amount is:
+ //
+ // [-]NUM[ ]SYM [@ AMOUNT]
+ // SYM[ ][-]NUM [@ AMOUNT]
+
+ string symbol;
+ string quant;
+ annotation_t details;
+ bool negative = false;
+
+ commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS;
+
+ 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 = static_cast<char>(in.peek())) != '\n')) {
+ if (std::isspace(n))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! symbol.empty())
+ comm_flags |= COMMODITY_STYLE_SUFFIXED;
+
+ if (! in.eof() && ((n = static_cast<char>(in.peek())) != '\n'))
+ details.parse(in);
+ }
+ } else {
+ commodity_t::parse_symbol(in, symbol);
+
+ if (! in.eof() && ((n = static_cast<char>(in.peek())) != '\n')) {
+ if (std::isspace(static_cast<char>(in.peek())))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_quantity(in, quant);
+
+ if (! quant.empty() && ! in.eof() &&
+ ((n = static_cast<char>(in.peek())) != '\n'))
+ details.parse(in);
+ }
+ }
+
+ if (quant.empty()) {
+ if (flags.has_flags(PARSE_SOFT_FAIL))
+ return false;
+ else
+ throw_(amount_error, _("No quantity specified for amount"));
+ }
+
+ // Allocate memory for the amount's quantity value. We have to
+ // monitor the allocation in an auto_ptr because this function gets
+ // called sometimes from amount_t's constructor; and if there is an
+ // exeception thrown by any of the function calls after this point,
+ // the destructor will never be called and the memory never freed.
+
+ std::auto_ptr<bigint_t> new_quantity;
+
+ if (quantity) {
+ if (quantity->refc > 1)
+ _release();
+ else
+ new_quantity.reset(quantity);
+ quantity = NULL;
+ }
+
+ if (! new_quantity.get())
+ new_quantity.reset(new bigint_t);
+
+ // No one is holding a reference to this now.
+ new_quantity->refc--;
+
+ // 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_pool_t::current_pool->find(symbol);
+ if (! commodity_) {
+ commodity_ = commodity_pool_t::current_pool->create(symbol);
+ newly_created = true;
+ }
+ assert(commodity_);
+
+ if (details)
+ commodity_ =
+ commodity_pool_t::current_pool->find_or_create(*commodity_, details);
+ }
+
+ // Quickly scan through and verify the correctness of the amount's use of
+ // punctuation.
+
+ precision_t decimal_offset = 0;
+ string::size_type string_index = quant.length();
+ string::size_type last_comma = string::npos;
+ string::size_type last_period = string::npos;
+
+ bool no_more_commas = false;
+ bool no_more_periods = false;
+ bool european_style = (commodity_t::european_by_default ||
+ commodity().has_flags(COMMODITY_STYLE_EUROPEAN));
+
+ new_quantity->prec = 0;
+
+ BOOST_REVERSE_FOREACH (const char& ch, quant) {
+ string_index--;
+
+ if (ch == '.') {
+ if (no_more_periods)
+ throw_(amount_error, _("Too many periods in amount"));
+
+ if (european_style) {
+ if (decimal_offset % 3 != 0)
+ throw_(amount_error, _("Incorrect use of european-style period"));
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ no_more_commas = true;
+ } else {
+ if (last_comma != string::npos) {
+ european_style = true;
+ if (decimal_offset % 3 != 0)
+ throw_(amount_error, _("Incorrect use of european-style period"));
+ } else {
+ no_more_periods = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ }
+
+ if (last_period == string::npos)
+ last_period = string_index;
+ }
+ else if (ch == ',') {
+ if (no_more_commas)
+ throw_(amount_error, _("Too many commas in amount"));
+
+ if (european_style) {
+ if (last_period != string::npos) {
+ throw_(amount_error, _("Incorrect use of european-style comma"));
+ } else {
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ } else {
+ if (decimal_offset % 3 != 0) {
+ if (last_comma != string::npos ||
+ last_period != string::npos) {
+ throw_(amount_error, _("Incorrect use of American-style comma"));
+ } else {
+ european_style = true;
+ no_more_commas = true;
+ new_quantity->prec = decimal_offset;
+ decimal_offset = 0;
+ }
+ } else {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ no_more_periods = true;
+ }
+ }
+
+ if (last_comma == string::npos)
+ last_comma = string_index;
+ }
+ else {
+ decimal_offset++;
+ }
+ }
+
+ if (european_style)
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
+
+ if (flags.has_flags(PARSE_NO_MIGRATE)) {
+ // Can't call set_keep_precision here, because it assumes that `quantity'
+ // is non-NULL.
+ new_quantity->add_flags(BIGINT_KEEP_PREC);
+ }
+ else if (commodity_) {
+ commodity().add_flags(comm_flags);
+
+ if (new_quantity->prec > commodity().precision())
+ commodity().set_precision(new_quantity->prec);
+ }
+
+ // Now we have the final number. Remove commas and periods, if necessary.
+
+ if (last_comma != string::npos || last_period != string::npos) {
+ string::size_type len = quant.length();
+ scoped_array<char> buf(new char[len + 1]);
+ const char * p = quant.c_str();
+ char * t = buf.get();
+
+ while (*p) {
+ if (*p == ',' || *p == '.')
+ p++;
+ *t++ = *p++;
+ }
+ *t = '\0';
+
+ mpq_set_str(MP(new_quantity.get()), buf.get(), 10);
+ mpz_ui_pow_ui(temp, 10, new_quantity->prec);
+ mpq_set_z(tempq, temp);
+ mpq_div(MP(new_quantity.get()), MP(new_quantity.get()), tempq);
+
+ IF_DEBUG("amount.parse") {
+ char * buf = mpq_get_str(NULL, 10, MP(new_quantity.get()));
+ DEBUG("amount.parse", "Rational parsed = " << buf);
+ std::free(buf);
+ }
+ } else {
+ mpq_set_str(MP(new_quantity.get()), quant.c_str(), 10);
+ }
+
+ if (negative)
+ mpq_neg(MP(new_quantity.get()), MP(new_quantity.get()));
+
+ new_quantity->refc++;
+ quantity = new_quantity.release();
+
+ if (! flags.has_flags(PARSE_NO_REDUCE))
+ in_place_reduce(); // will not throw an exception
+
+ VERIFY(valid());
+
+ return true;
+}
+
+void amount_t::parse_conversion(const string& larger_str,
+ const string& smaller_str)
+{
+ amount_t larger, smaller;
+
+ larger.parse(larger_str, PARSE_NO_REDUCE);
+ smaller.parse(smaller_str, PARSE_NO_REDUCE);
+
+ larger *= smaller.number();
+
+ if (larger.commodity()) {
+ larger.commodity().set_smaller(smaller);
+ larger.commodity().add_flags(smaller.commodity().flags() |
+ COMMODITY_NOMARKET);
+ }
+ if (smaller.commodity())
+ smaller.commodity().set_larger(larger);
+}
+
+void amount_t::print(std::ostream& _out) const
+{
+ VERIFY(valid());
+
+ if (! quantity) {
+ _out << "<null>";
+ return;
+ }
+
+ std::ostringstream out;
+
+ commodity_t& comm(commodity());
+
+ if (! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ comm.print(out);
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ }
+
+ stream_out_mpq(out, MP(quantity), display_precision(),
+ comm ? commodity().precision() : 0, comm);
+
+ if (comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
+ if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
+ out << " ";
+ comm.print(out);
+ }
+
+ // If there are any annotations associated with this commodity, output them
+ // now.
+ comm.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();
+}
+
+bool amount_t::valid() const
+{
+ if (quantity) {
+ if (! quantity->valid()) {
+ DEBUG("ledger.validate", "amount_t: ! quantity->valid()");
+ return false;
+ }
+
+ if (quantity->refc == 0) {
+ DEBUG("ledger.validate", "amount_t: quantity->refc == 0");
+ return false;
+ }
+ }
+ else if (commodity_) {
+ DEBUG("ledger.validate", "amount_t: commodity_ != NULL");
+ return false;
+ }
+ return true;
+}
+
+void to_xml(std::ostream& out, const amount_t& amt, bool commodity_details)
+{
+ push_xml x(out, "amount");
+
+ if (amt.has_commodity())
+ to_xml(out, amt.commodity(), commodity_details);
+
+ {
+ push_xml y(out, "quantity");
+ out << y.guard(amt.quantity_string());
+ }
+}
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+
+template<class Archive>
+void amount_t::serialize(Archive& ar, const unsigned int /* version */)
+{
+ ar & is_initialized;
+ ar & quantity;
+ ar & commodity_;
+}
+
+#endif // HAVE_BOOST_SERIALIZATION
+
+} // namespace ledger
+
+#if defined(HAVE_BOOST_SERIALIZATION)
+namespace boost {
+namespace serialization {
+
+template <class Archive>
+void serialize(Archive& ar, MP_INT& mpz, const unsigned int /* version */)
+{
+ ar & mpz._mp_alloc;
+ ar & mpz._mp_size;
+ ar & mpz._mp_d;
+}
+
+template <class Archive>
+void serialize(Archive& ar, MP_RAT& mpq, const unsigned int /* version */)
+{
+ ar & mpq._mp_num;
+ ar & mpq._mp_den;
+}
+
+template <class Archive>
+void serialize(Archive& ar, long unsigned int& integer,
+ const unsigned int /* version */)
+{
+ ar & make_binary_object(&integer, sizeof(long unsigned int));
+}
+
+} // namespace serialization
+} // namespace boost
+
+BOOST_CLASS_EXPORT(ledger::annotated_commodity_t)
+
+template void boost::serialization::serialize(boost::archive::binary_iarchive&,
+ MP_INT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_oarchive&,
+ MP_INT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_iarchive&,
+ MP_RAT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_oarchive&,
+ MP_RAT&, const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_iarchive&,
+ long unsigned int&,
+ const unsigned int);
+template void boost::serialization::serialize(boost::archive::binary_oarchive&,
+ long unsigned int&,
+ const unsigned int);
+
+template void ledger::amount_t::serialize(boost::archive::binary_iarchive&,
+ const unsigned int);
+template void ledger::amount_t::serialize(boost::archive::binary_oarchive&,
+ const unsigned int);
+
+#endif // HAVE_BOOST_SERIALIZATION