diff options
Diffstat (limited to 'amount.cc')
-rw-r--r-- | amount.cc | 2455 |
1 files changed, 920 insertions, 1535 deletions
@@ -1,287 +1,313 @@ -#include "amount.h" -#include "util.h" - -#include <list> -#include <sstream> -#include <cstdlib> +/* + * 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. + */ + +/** + * @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 also defines a + * helper class, bigint_t, which is used as a refcounted wrapper + * around libgmp's mpz_t type. + */ -#include <gmp.h> +#include "amount.h" +#include "parser.h" namespace ledger { -bool do_cleanup = true; +commodity_pool_t * amount_t::current_pool = NULL; + +bool amount_t::keep_base = false; 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::keep_tag = false; -#define BIGINT_BULK_ALLOC 0x0001 -#define BIGINT_KEEP_PREC 0x0002 +bool amount_t::stream_fullstrings = false; -class amount_t::bigint_t { - public: - mpz_t val; - unsigned char prec; - unsigned char flags; - unsigned int ref; - unsigned int index; +#ifndef THREADSAFE +/** + * These global temporaries are pre-initialized for the sake of + * efficiency, and reused over and over again. + */ +static mpz_t temp; +static mpz_t divisor; +#endif + +struct amount_t::bigint_t : public supports_flags<> +{ +#define BIGINT_BULK_ALLOC 0x01 +#define BIGINT_KEEP_PREC 0x02 - bigint_t() : prec(0), flags(0), ref(1), index(0) { + mpz_t val; + precision_t prec; + uint_least16_t ref; + uint_fast32_t index; + +#define MPZ(bigint) ((bigint)->val) + + bigint_t() : prec(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) { + bigint_t(mpz_t _val) : prec(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) { + : supports_flags<>(other.flags() & BIGINT_KEEP_PREC), + prec(other.prec), ref(1), index(0) { + TRACE_CTOR(bigint_t, "copy"); mpz_init_set(val, other.val); } - ~bigint_t(); + ~bigint_t() { + TRACE_DTOR(bigint_t); + assert(ref == 0); + mpz_clear(val); + } }; -unsigned int sizeof_bigint_t() { - return sizeof(amount_t::bigint_t); -} - -#define MPZ(x) ((x)->val) - -static mpz_t temp; // these are the global temp variables -static mpz_t divisor; - -static amount_t::bigint_t true_value; - -inline amount_t::bigint_t::~bigint_t() { - assert(ref == 0 || (! do_cleanup && this == &true_value)); - mpz_clear(val); -} - -base_commodities_map commodity_base_t::commodities; - -commodity_base_t::updater_t * commodity_base_t::updater = NULL; - -commodities_map commodity_t::commodities; -bool commodity_t::commodities_sorted = false; -commodity_t * commodity_t::null_commodity; -commodity_t * commodity_t::default_commodity = NULL; - -static struct _init_amounts { - _init_amounts() { - mpz_init(temp); - mpz_init(divisor); - - mpz_set_ui(true_value.val, 1); - - commodity_base_t::updater = NULL; - commodity_t::null_commodity = commodity_t::create(""); - commodity_t::default_commodity = NULL; - - commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET | - COMMODITY_STYLE_BUILTIN); +void amount_t::initialize() +{ + mpz_init(temp); + mpz_init(divisor); - // Add time commodity conversions, so that timelog's may be parsed - // in terms of seconds, but reported as minutes or hours. - commodity_t * commodity; + // jww (2007-05-02): Be very careful here! + if (! current_pool) + current_pool = new commodity_pool_t; - commodity = commodity_t::create("s"); + // 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 = current_pool->create("s")) { commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); parse_conversion("1.0m", "60s"); parse_conversion("1.0h", "60m"); - -#if 0 - commodity = commodity_t::create("b"); - commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); - - parse_conversion("1.00 Kb", "1024 b"); - parse_conversion("1.00 Mb", "1024 Kb"); - parse_conversion("1.00 Gb", "1024 Mb"); - parse_conversion("1.00 Tb", "1024 Gb"); -#endif + } else { + assert(false); } +} - ~_init_amounts() { - if (! do_cleanup) - return; - - mpz_clear(temp); - mpz_clear(divisor); - - if (commodity_base_t::updater) { - delete commodity_base_t::updater; - commodity_base_t::updater = NULL; - } - - for (commodities_map::iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); - i++) - delete (*i).second; - - commodity_t::commodities.clear(); +void amount_t::shutdown() +{ + mpz_clear(temp); + mpz_clear(divisor); - true_value.ref--; + // jww (2007-05-02): Be very careful here! + if (current_pool) { + checked_delete(current_pool); + current_pool = NULL; } -} _init_obj; +} -static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) +void amount_t::_copy(const amount_t& amt) { - // 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); + if (quantity != amt.quantity) { + if (quantity) + _release(); - 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); + // 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 { - mpz_sub(out, value, remainder); + quantity = amt.quantity; + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; } } - 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); + commodity_ = amt.commodity_; } -amount_t::amount_t(const bool value) +void amount_t::_dup() { - if (value) { - quantity = &true_value; - quantity->ref++; - } else { - quantity = NULL; + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; } - commodity_ = NULL; } -amount_t::amount_t(const long value) +void amount_t::_resize(precision_t prec) { - if (value != 0) { - quantity = new bigint_t; - mpz_set_si(MPZ(quantity), value); - } else { - quantity = NULL; - } - commodity_ = NULL; -} + assert(prec < 256); -amount_t::amount_t(const unsigned long value) -{ - if (value != 0) { - quantity = new bigint_t; - mpz_set_ui(MPZ(quantity), value); - } else { - quantity = NULL; - } - commodity_ = NULL; + if (! quantity || prec == quantity->prec) + return; + + _dup(); + + assert(prec > quantity->prec); + mpz_ui_pow_ui(divisor, 10, prec - quantity->prec); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + + quantity->prec = prec; } -amount_t::amount_t(const double value) +void amount_t::_clear() { - if (value != 0.0) { - quantity = new bigint_t; - mpz_set_d(MPZ(quantity), value); + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; } else { - quantity = NULL; + assert(! commodity_); } - commodity_ = NULL; } void amount_t::_release() { - DEBUG_PRINT("amounts.refs", - quantity << " ref--, now " << (quantity->ref - 1)); + DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); + if (--quantity->ref == 0) { - if (! (quantity->flags & BIGINT_BULK_ALLOC)) - delete quantity; - else + if (quantity->has_flags(BIGINT_BULK_ALLOC)) quantity->~bigint_t(); + else + checked_delete(quantity); } } -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; - } -} +namespace { + amount_t::precision_t convert_double(mpz_t dest, double val) + { +#ifndef HAVE_GDTOA + // This code is far too imprecise to be worthwhile. -void amount_t::_copy(const amount_t& amt) -{ - if (quantity != amt.quantity) { - if (quantity) - _release(); + mpf_t temp; + mpf_init_set_d(temp, val); - // 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); + 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 { - quantity = amt.quantity; - DEBUG_PRINT("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; + // 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 amount_t::precision_t(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 + 1]; + + 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) + 2]; + newbuf[0] = '-'; + std::strcpy(&newbuf[1], result ? result : buf); + mpz_set_str(dest, newbuf, 10); + checked_array_delete(newbuf); + } else { + mpz_set_str(dest, result ? result : buf, 10); + } + + if (result) + checked_array_delete(result); + freedtoa(buf); + + return decpt; +#endif } - commodity_ = amt.commodity_; } -amount_t& amount_t::operator=(const std::string& value) +amount_t::amount_t(const double val) : commodity_(NULL) { - std::istringstream str(value); - parse(str); - return *this; + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + quantity->prec = convert_double(MPZ(quantity), val); } -amount_t& amount_t::operator=(const char * value) +amount_t::amount_t(const unsigned long val) : commodity_(NULL) { - std::string valstr(value); - std::istringstream str(valstr); - parse(str); - return *this; + TRACE_CTOR(amount_t, "const unsigned long"); + quantity = new bigint_t; + mpz_set_ui(MPZ(quantity), val); } -// assignment operator +amount_t::amount_t(const long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const long"); + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), val); +} + + amount_t& amount_t::operator=(const amount_t& amt) { if (this != &amt) { @@ -293,99 +319,59 @@ amount_t& amount_t::operator=(const amount_t& amt) return *this; } -amount_t& amount_t::operator=(const bool value) -{ - if (! value) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - if (quantity) - _release(); - quantity = &true_value; - quantity->ref++; - } - return *this; -} -amount_t& amount_t::operator=(const long value) +int amount_t::compare(const amount_t& amt) const { - if (value == 0) { + if (! quantity || ! amt.quantity) { if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_si(MPZ(quantity), value); + 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"); } - return *this; -} -amount_t& amount_t::operator=(const unsigned long value) -{ - if (value == 0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_ui(MPZ(quantity), value); - } - return *this; -} + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Cannot compare amounts with different commodities: " << + commodity().symbol() << " and " << amt.commodity().symbol()); -amount_t& amount_t::operator=(const double value) -{ - if (value == 0.0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_d(MPZ(quantity), value); + if (quantity->prec == amt.quantity->prec) { + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); } - 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); + 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)); } - - quantity->prec = prec; } amount_t& amount_t::operator+=(const amount_t& amt) { - if (! amt.quantity) - return *this; - - if (! quantity) { - _copy(amt); - return *this; + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot add an amount to an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot add an uninitialized amount to an amount"); + else + throw_(amount_error, "Cannot add two uninitialized amounts"); } - _dup(); - if (commodity() != amt.commodity()) - throw new amount_error - (std::string("Adding amounts with different commodities: ") + - commodity_->qualified_symbol + " != " + - amt.commodity_->qualified_symbol); + throw_(amount_error, + "Adding amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _dup(); if (quantity->prec == amt.quantity->prec) { mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); @@ -395,9 +381,9 @@ amount_t& amount_t::operator+=(const amount_t& amt) mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); } else { - amount_t temp = amt; - temp._resize(quantity->prec); - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); + amount_t t = amt; + t._resize(quantity->prec); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); } return *this; @@ -405,23 +391,23 @@ amount_t& amount_t::operator+=(const amount_t& amt) amount_t& amount_t::operator-=(const amount_t& amt) { - if (! amt.quantity) - return *this; - - if (! quantity) { - quantity = new bigint_t(*amt.quantity); - commodity_ = amt.commodity_; - mpz_neg(MPZ(quantity), MPZ(quantity)); - return *this; + 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"); } - _dup(); - if (commodity() != amt.commodity()) - throw new amount_error - (std::string("Subtracting amounts with different commodities: ") + - commodity_->qualified_symbol + " != " + - amt.commodity_->qualified_symbol); + throw_(amount_error, + "Subtracting amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _dup(); if (quantity->prec == amt.quantity->prec) { mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); @@ -431,30 +417,95 @@ amount_t& amount_t::operator-=(const amount_t& amt) mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); } else { - amount_t temp = amt; - temp._resize(quantity->prec); - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); + amount_t t = amt; + t._resize(quantity->prec); + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); } return *this; } +namespace { + 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::operator*=(const amount_t& amt) { - if (! amt.quantity) - return (*this = amt); - else if (! quantity) - return *this; + 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"); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Multiplying amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); _dup(); mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); quantity->prec += amt.quantity->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; + if (! has_commodity()) + commodity_ = amt.commodity_; + + if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { + precision_t 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; @@ -462,121 +513,194 @@ amount_t& amount_t::operator*=(const amount_t& amt) amount_t& amount_t::operator/=(const amount_t& amt) { - if (! amt.quantity || ! amt) - throw new amount_error("Divide by zero"); - else if (! quantity) - return *this; + 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 (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Dividing amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + if (! amt) + throw_(amount_error, "Divide by zero"); _dup(); // Increase the value's precision, to capture fractional parts after - // the divide. - mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + 6U); + // 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 += 6U; + quantity->prec += amt.quantity->prec + quantity->prec + 7U; + + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1); + quantity->prec -= 1; - 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; + 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->has_flags(BIGINT_KEEP_PREC))) { + precision_t 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::negate() + +amount_t::precision_t amount_t::precision() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine precision of an uninitialized amount"); + + return quantity->prec; +} + +amount_t& amount_t::in_place_negate() { if (quantity) { _dup(); mpz_neg(MPZ(quantity), MPZ(quantity)); + } else { + throw_(amount_error, "Cannot negate an uninitialized amount"); } + return *this; } -int amount_t::sign() const +amount_t amount_t::round() const { - return quantity ? mpz_sgn(MPZ(quantity)) : 0; + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); + + if (! has_commodity()) + return *this; + + return round(commodity().precision()); } -int amount_t::compare(const amount_t& amt) const +amount_t amount_t::round(precision_t prec) const { - if (! quantity) { - if (! amt.quantity) - return 0; - return - amt.sign(); - } - if (! amt.quantity) - return sign(); + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); - if (commodity() && amt.commodity() && commodity() != amt.commodity()) - throw new amount_error - (std::string("Cannot compare amounts with different commodities: ") + - commodity().symbol() + " and " + amt.commodity().symbol()); + amount_t t(*this); - if (quantity->prec == amt.quantity->prec) { - return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - amount_t temp = *this; - temp._resize(amt.quantity->prec); - return mpz_cmp(MPZ(temp.quantity), MPZ(amt.quantity)); - } - else { - amount_t temp = amt; - temp._resize(quantity->prec); - return mpz_cmp(MPZ(quantity), MPZ(temp.quantity)); + if (quantity->prec <= prec) { + if (quantity && quantity->has_flags(BIGINT_KEEP_PREC)) { + t._dup(); + t.quantity->drop_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->drop_flags(BIGINT_KEEP_PREC); + + return t; } -bool amount_t::operator==(const amount_t& amt) const +amount_t amount_t::unround() const { - if (commodity() != amt.commodity()) - return false; - return compare(amt) == 0; + if (! quantity) + throw_(amount_error, "Cannot unround an uninitialized amount"); + else if (quantity->has_flags(BIGINT_KEEP_PREC)) + return *this; + + amount_t t(*this); + t._dup(); + t.quantity->add_flags(BIGINT_KEEP_PREC); + + return t; } -bool amount_t::operator!=(const amount_t& amt) const +amount_t& amount_t::in_place_reduce() { - if (commodity() != amt.commodity()) - return true; - return compare(amt) != 0; + if (! quantity) + throw_(amount_error, "Cannot reduce an uninitialized amount"); + + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; + } + return *this; } -amount_t::operator bool() const +amount_t& amount_t::in_place_unreduce() { if (! quantity) - return false; + throw_(amount_error, "Cannot unreduce an uninitialized amount"); - if (quantity->prec <= commodity().precision()) { - return mpz_sgn(MPZ(quantity)) != 0; + while (commodity_ && commodity().larger()) { + *this /= commodity().larger()->number(); + commodity_ = commodity().larger()->commodity_; + if (abs() < amount_t(1.0)) + break; + } + return *this; +} + +optional<amount_t> amount_t::value(const optional<moment_t>& moment) const +{ + if (quantity) { + optional<amount_t> amt(commodity().value(moment)); + if (amt) + return (*amt * number()).round(); } else { - mpz_set(temp, MPZ(quantity)); - if (quantity->flags & BIGINT_KEEP_PREC) - mpz_ui_pow_ui(divisor, 10, quantity->prec); - else - mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision()); - mpz_tdiv_q(temp, temp, divisor); - bool zero = mpz_sgn(temp) == 0; - return ! zero; + throw_(amount_error, "Cannot determine value of an uninitialized amount"); } + return none; } -amount_t::operator long() const + +int amount_t::sign() const { if (! quantity) - return 0; + throw_(amount_error, "Cannot determine sign of an uninitialized amount"); - mpz_set(temp, MPZ(quantity)); - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_q(temp, temp, divisor); - return mpz_get_si(temp); + return mpz_sgn(MPZ(quantity)); } -amount_t::operator double() const +bool amount_t::is_zero() const { if (! quantity) - return 0.0; + throw_(amount_error, "Cannot determine sign if an uninitialized amount is zero"); + + if (has_commodity()) { + if (quantity->prec <= commodity().precision()) + return is_realzero(); + else + return round(commodity().precision()).sign() == 0; + } + return is_realzero(); +} + + +double amount_t::to_double(bool no_check) const +{ + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a double"); mpz_t remainder; mpz_init(remainder); @@ -596,486 +720,151 @@ amount_t::operator double() const mpz_clear(remainder); - return std::atof(num.str().c_str()); -} + double value = lexical_cast<double>(num.str()); -bool amount_t::realzero() const -{ - if (! quantity) - return true; - return mpz_sgn(MPZ(quantity)) == 0; -} + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_double loses precision"); -amount_t amount_t::value(const datetime_t& moment) const -{ - if (quantity) { - amount_t amt(commodity().value(moment)); - if (! amt.realzero()) - return (amt * *this).round(); - } - return *this; + return value; } -amount_t amount_t::round(unsigned int prec) const +long amount_t::to_long(bool no_check) const { - amount_t temp = *this; - - if (! quantity || quantity->prec <= prec) { - if (quantity && quantity->flags & BIGINT_KEEP_PREC) { - temp._dup(); - temp.quantity->flags &= ~BIGINT_KEEP_PREC; - } - return temp; - } + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a long"); - temp._dup(); + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); - mpz_round(MPZ(temp.quantity), MPZ(temp.quantity), temp.quantity->prec, prec); + long value = mpz_get_si(temp); - temp.quantity->prec = prec; - temp.quantity->flags &= ~BIGINT_KEEP_PREC; + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_long loses precision"); - return temp; + return value; } -amount_t amount_t::unround() const +bool amount_t::fits_in_double() const { - if (! quantity) { - amount_t temp(0L); - assert(temp.quantity); - temp.quantity->flags |= BIGINT_KEEP_PREC; - return temp; - } - else if (quantity->flags & BIGINT_KEEP_PREC) { - return *this; - } - - amount_t temp = *this; - temp._dup(); - temp.quantity->flags |= BIGINT_KEEP_PREC; - - return temp; + double value = to_double(true); + return *this == amount_t(value); } -std::string amount_t::quantity_string() const +bool amount_t::fits_in_long() const { - if (! quantity) - return "0"; - - 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(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; + long value = to_long(true); + return *this == amount_t(value); +} - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); - } - mpz_set(rquotient, remainder); - if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) - return "0"; +void amount_t::annotate_commodity(const annotation_t& details) +{ + commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; - if (negative) - out << "-"; + if (! quantity) + throw_(amount_error, "Cannot annotate the commodity of an uninitialized amount"); + else if (! has_commodity()) + throw_(amount_error, "Cannot annotate an amount with no commodity"); - if (mpz_sgn(quotient) == 0) { - out << '0'; + if (commodity().annotated) { + this_ann = &as_annotated_commodity(commodity()); + this_base = &this_ann->referent(); } else { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); + this_base = &commodity(); } + assert(this_base); - if (precision) { - out << '.'; - - out.width(precision); - out.fill('0'); - - char * p = mpz_get_str(NULL, 10, rquotient); - out << p; - std::free(p); - } + DEBUG("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl << details); - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); + if (commodity_t * ann_comm = + this_base->parent().find_or_create(*this_base, details)) + set_commodity(*ann_comm); +#ifdef ASSERTS_ON + else + assert(false); +#endif - return out.str(); + DEBUG("amounts.commodities", " Annotated amount is " << *this); } -std::ostream& operator<<(std::ostream& _out, const amount_t& amt) +bool amount_t::commodity_annotated() const { - if (! amt.quantity) { - _out << "0"; - return _out; - } - - amount_t base(amt); - if (! amount_t::keep_base && amt.commodity().larger()) { - amount_t last(amt); - while (last.commodity().larger()) { - last /= *last.commodity().larger(); - last.commodity_ = last.commodity().larger()->commodity_; - if (ledger::abs(last) < 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; - - if (! comm || 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 (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { - _out << "0"; - return _out; - } - - if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { - comm.write(out); - - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; - } - - if (negative) - out << "-"; - - if (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<std::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<std::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 (precision) { - std::ostringstream final; - final.width(precision); - final.fill('0'); - char * p = mpz_get_str(NULL, 10, rquotient); - final << p; - std::free(p); - - const std::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; - - std::string ender; - if (i == len) - ender = str; - else if (i < comm.precision()) - ender = std::string(str, 0, comm.precision()); - else - ender = std::string(str, 0, i); - - if (! ender.empty()) { - out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - out << ender; - } - } - - if (comm.flags() & COMMODITY_STYLE_SUFFIXED) { - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; + if (! quantity) + throw_(amount_error, + "Cannot determine if an uninitialized amount's commodity is annotated"); - comm.write(out); - } + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); + return commodity().annotated; +} - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); +annotation_t amount_t::annotation_details() const +{ + if (! quantity) + throw_(amount_error, + "Cannot return commodity annotation details of an uninitialized amount"); - // If there are any annotations associated with this commodity, - // output them now. + assert(! commodity().annotated || as_annotated_commodity(commodity()).details); - if (comm.annotated) { - annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); - assert(&ann.price != &amt); - ann.write_annotations(out); + if (commodity().annotated) { + annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); + return ann_comm.details; } - - // 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 _out; + return annotation_t(); } -void parse_quantity(std::istream& in, std::string& value) +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const { - char buf[256]; - char c = peek_next_nonws(in); - READ_INTO(in, buf, 255, c, - std::isdigit(c) || c == '-' || c == '.' || c == ','); + if (! quantity) + throw_(amount_error, + "Cannot strip commodity annotations from an uninitialized amount"); - int len = std::strlen(buf); - while (len > 0 && ! std::isdigit(buf[len - 1])) { - buf[--len] = '\0'; - in.unget(); - } + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; - value = buf; + amount_t t(*this); + t.set_commodity(as_annotated_commodity(commodity()). + strip_annotations(_keep_price, _keep_date, _keep_tag)); + return t; } -// 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, -}; -void parse_commodity(std::istream& in, std::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 new amount_error("Quoted commodity symbol lacks closing quote"); - } else { - READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]); - } - symbol = buf; -} - -void parse_annotations(std::istream& in, amount_t& price, - datetime_t& date, std::string& tag) -{ - do { +namespace { + void parse_quantity(std::istream& in, string& value) + { char buf[256]; char c = peek_next_nonws(in); - if (c == '{') { - if (price) - throw new amount_error("Commodity specifies more than one price"); - - in.get(c); - READ_INTO(in, buf, 255, c, c != '}'); - if (c == '}') - in.get(c); - else - throw new amount_error("Commodity price lacks closing brace"); - - price.parse(buf, AMOUNT_PARSE_NO_MIGRATE); - price.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.quantity->prec < price.commodity().precision()) - price = price.round(); // no need to retain individual precision - } - else if (c == '[') { - if (date) - throw new amount_error("Commodity specifies more than one date"); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ']'); - if (c == ']') - in.get(c); - else - throw new amount_error("Commodity date lacks closing bracket"); - - date = buf; - } - else if (c == '(') { - if (! tag.empty()) - throw new amount_error("Commodity specifies more than one tag"); - - in.get(c); - READ_INTO(in, buf, 255, c, c != ')'); - if (c == ')') - in.get(c); - else - throw new amount_error("Commodity tag lacks closing parenthesis"); + READ_INTO(in, buf, 255, c, + std::isdigit(c) || c == '-' || c == '.' || c == ','); - tag = buf; + int len = std::strlen(buf); + while (len > 0 && ! std::isdigit(buf[len - 1])) { + buf[--len] = '\0'; + in.unget(); } - else { - break; - } - } while (true); - DEBUG_PRINT("amounts.commodities", - "Parsed commodity annotations: " - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); + value = buf; + } } -void amount_t::parse(std::istream& in, unsigned char flags) +void amount_t::parse(std::istream& in, flags_t flags) { // The possible syntax for an amount is: // // [-]NUM[ ]SYM [@ AMOUNT] // SYM[ ][-]NUM [@ AMOUNT] - std::string symbol; - std::string quant; - amount_t price; - datetime_t date; - std::string tag; - unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS; - bool negative = false; + 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 == '-') { @@ -1092,16 +881,16 @@ void amount_t::parse(std::istream& in, unsigned char flags) if (std::isspace(n)) comm_flags |= COMMODITY_STYLE_SEPARATED; - parse_commodity(in, symbol); + commodity_t::parse_symbol(in, symbol); if (! symbol.empty()) comm_flags |= COMMODITY_STYLE_SUFFIXED; if (! in.eof() && ((n = in.peek()) != '\n')) - parse_annotations(in, price, date, tag); + details.parse(in); } } else { - parse_commodity(in, symbol); + commodity_t::parse_symbol(in, symbol); if (! in.eof() && ((n = in.peek()) != '\n')) { if (std::isspace(in.peek())) @@ -1110,14 +899,30 @@ void amount_t::parse(std::istream& in, unsigned char flags) parse_quantity(in, quant); if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) - parse_annotations(in, price, date, tag); + details.parse(in); } } if (quant.empty()) - throw new amount_error("No quantity specified for amount"); + throw_(amount_error, "No quantity specified for amount"); - _init(); + // 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> safe_holder; + + if (! quantity) { + quantity = new bigint_t; + safe_holder.reset(quantity); + } + else if (quantity->ref > 1) { + _release(); + quantity = new bigint_t; + safe_holder.reset(quantity); + } // Create the commodity if has not already been seen, and update the // precision if something greater was used for the quantity. @@ -1125,27 +930,26 @@ void amount_t::parse(std::istream& in, unsigned char flags) bool newly_created = false; if (symbol.empty()) { - commodity_ = commodity_t::null_commodity; + commodity_ = NULL; } else { - commodity_ = commodity_t::find(symbol); + commodity_ = current_pool->find(symbol); if (! commodity_) { - commodity_ = commodity_t::create(symbol); + commodity_ = current_pool->create(symbol); newly_created = true; } assert(commodity_); - if (! price.realzero() || date || ! tag.empty()) - commodity_ = - annotated_commodity_t::find_or_create(*commodity_, price, date, tag); + if (details) + commodity_ = current_pool->find_or_create(*commodity_, details); } // Determine the precision of the amount, based on the usage of // comma or period. - std::string::size_type last_comma = quant.rfind(','); - std::string::size_type last_period = quant.rfind('.'); + string::size_type last_comma = quant.rfind(','); + string::size_type last_period = quant.rfind('.'); - if (last_comma != std::string::npos && last_period != std::string::npos) { + if (last_comma != string::npos && last_period != string::npos) { comm_flags |= COMMODITY_STYLE_THOUSANDS; if (last_comma > last_period) { comm_flags |= COMMODITY_STYLE_EUROPEAN; @@ -1154,14 +958,12 @@ void amount_t::parse(std::istream& in, unsigned char flags) quantity->prec = quant.length() - last_period - 1; } } - else if (last_comma != std::string::npos && - (! commodity_t::default_commodity || - commodity_t::default_commodity->flags() & COMMODITY_STYLE_EUROPEAN)) { - comm_flags |= COMMODITY_STYLE_EUROPEAN; + else if (last_comma != string::npos && + commodity().has_flags(COMMODITY_STYLE_EUROPEAN)) { quantity->prec = quant.length() - last_comma - 1; } - else if (last_period != std::string::npos && - ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) { + else if (last_period != string::npos && + ! (commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) { quantity->prec = quant.length() - last_period - 1; } else { @@ -1170,19 +972,22 @@ void amount_t::parse(std::istream& in, unsigned char flags) // Set the commodity's flags and precision accordingly - if (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { + if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { commodity().add_flags(comm_flags); + if (quantity->prec > commodity().precision()) commodity().set_precision(quantity->prec); } + // Setup the amount's own flags + if (flags & AMOUNT_PARSE_NO_MIGRATE) - quantity->flags |= BIGINT_KEEP_PREC; + quantity->add_flags(BIGINT_KEEP_PREC); // Now we have the final number. Remove commas and periods, if // necessary. - if (last_comma != std::string::npos || last_period != std::string::npos) { + 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(); @@ -1196,41 +1001,29 @@ void amount_t::parse(std::istream& in, unsigned char flags) *t = '\0'; mpz_set_str(MPZ(quantity), buf, 10); - delete[] buf; + checked_array_delete(buf); } else { mpz_set_str(MPZ(quantity), quant.c_str(), 10); } if (negative) - negate(); + in_place_negate(); if (! (flags & AMOUNT_PARSE_NO_REDUCE)) - reduce(); -} + in_place_reduce(); -void amount_t::reduce() -{ - while (commodity_ && commodity().smaller()) { - *this *= *commodity().smaller(); - commodity_ = commodity().smaller()->commodity_; - } -} - -void amount_t::parse(const std::string& str, unsigned char flags) -{ - std::istringstream stream(str); - parse(stream, flags); + safe_holder.release(); // `this->quantity' owns the pointer } -void parse_conversion(const std::string& larger_str, - const std::string& smaller_str) +void amount_t::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.parse(larger_str, AMOUNT_PARSE_NO_REDUCE); + smaller.parse(smaller_str, AMOUNT_PARSE_NO_REDUCE); - larger *= smaller; + larger *= smaller.number(); if (larger.commodity()) { larger.commodity().set_smaller(smaller); @@ -1242,774 +1035,366 @@ void parse_conversion(const std::string& larger_str, } -char * bigints; -char * bigints_next; -unsigned int bigints_index; -unsigned int bigints_count; - -void amount_t::read_quantity(char *& data) +void amount_t::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const { - char byte = *data++;; + if (! quantity) + throw_(amount_error, "Cannot write out an uninitialized amount"); - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new((bigint_t *)bigints_next) bigint_t; - bigints_next += sizeof(bigint_t); + amount_t base(*this); + if (! amount_t::keep_base) + base.in_place_unreduce(); - 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)); + std::ostringstream out; - 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); + mpz_t quotient; + mpz_t rquotient; + mpz_t remainder; - quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); - DEBUG_PRINT("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } -} + mpz_init(quotient); + mpz_init(rquotient); + mpz_init(remainder); -static char buf[4096]; + bool negative = false; -void amount_t::read_quantity(std::istream& in) -{ - char byte; - in.read(&byte, sizeof(byte)); + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new bigint_t; + commodity_t& comm(base.commodity()); + precision_t precision = 0; - 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); + if (quantity) { + if (! comm || full_precision || base.quantity->has_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; + } - char negative; - in.read(&negative, sizeof(negative)); - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + negative = true; - in.read((char *)&quantity->prec, sizeof(quantity->prec)); - in.read((char *)&quantity->flags, sizeof(quantity->flags)); - } - else { - assert(0); + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); } -} - -void amount_t::write_quantity(std::ostream& out) const -{ - char byte; - if (! quantity) { - byte = 0; - out.write(&byte, sizeof(byte)); - return; + if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + comm.print(out); + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; } - if (quantity->index == 0) { - quantity->index = ++bigints_index; - bigints_count++; + if (negative) + out << "-"; - byte = 1; - out.write(&byte, sizeof(byte)); + if (! quantity || mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (omit_commodity || ! comm.has_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]; - 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); + 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); } - 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); + bool printed = false; - // 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)); - } -} + for (std::list<string>::reverse_iterator i = strs.rbegin(); + i != strs.rend(); + i++) { + if (printed) { + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ','); + out.width(3); + out.fill('0'); + } + out << *i; -bool amount_t::valid() const -{ - if (quantity) { - if (quantity->ref == 0) { - DEBUG_PRINT("ledger.validate", "amount_t: quantity->ref == 0"); - return false; + printed = true; } } - else if (commodity_) { - DEBUG_PRINT("ledger.validate", "amount_t: commodity_ != NULL"); - return false; - } - return true; -} - -void amount_t::annotate_commodity(const amount_t& price, - const datetime_t& date, - const std::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); + 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); - DEBUG_PRINT("amounts.commodities", "Annotating commodity for amount " - << *this << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); - - commodity_t * ann_comm = - annotated_commodity_t::find_or_create - (*this_base, ! price && this_ann ? this_ann->price : price, - ! date && this_ann ? this_ann->date : date, - tag.empty() && this_ann ? this_ann->tag : tag); - if (ann_comm) - set_commodity(*ann_comm); + 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; - DEBUG_PRINT("amounts.commodities", " Annotated amount is " << *this); -} + string ender; + if (i == len) + ender = str; + else if (i < comm.precision()) + ender = string(str, 0, comm.precision()); + else + ender = string(str, 0, i); -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; + if (! ender.empty()) { + if (omit_commodity) + out << '.'; + else + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } - DEBUG_PRINT("amounts.commodities", "Reducing commodity for amount " - << *this << std::endl - << " keep price " << _keep_price << " " - << " keep date " << _keep_date << " " - << " keep tag " << _keep_tag); + if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + comm.print(out); + } - annotated_commodity_t& - ann_comm(static_cast<annotated_commodity_t&>(commodity())); - assert(ann_comm.base); + mpz_clear(quotient); + mpz_clear(rquotient); + mpz_clear(remainder); - commodity_t * new_comm; + // If there are any annotations associated with this commodity, + // output them now. - if ((_keep_price && ann_comm.price) || - (_keep_date && 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 : datetime_t(), - _keep_tag ? ann_comm.tag : ""); - } else { - new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); + if (! omit_commodity && comm.annotated) { + annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); + assert(&*ann.details.price != this); + ann.write_annotations(out); } - assert(new_comm); - amount_t temp(*this); - temp.set_commodity(*new_comm); - - DEBUG_PRINT("amounts.commodities", " Reduced amount is " << temp); + // 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. - return temp; + _out << out.str(); } -amount_t amount_t::price() const -{ - if (commodity_ && commodity_->annotated) { - amount_t temp(((annotated_commodity_t *)commodity_)->price); - temp *= *this; - DEBUG_PRINT("amounts.commodities", - "Returning price of " << *this << " = " << temp); - return temp; - } - return *this; -} -datetime_t amount_t::date() const -{ - if (commodity_ && commodity_->annotated) { - DEBUG_PRINT("amounts.commodities", - "Returning date of " << *this << " = " - << ((annotated_commodity_t *)commodity_)->date); - return ((annotated_commodity_t *)commodity_)->date; - } - return 0L; +namespace { + char * bigints; + char * bigints_next; + uint_fast32_t bigints_index; + uint_fast32_t bigints_count; + char buf[4096]; } - -void commodity_base_t::add_price(const datetime_t& date, - const amount_t& price) +void amount_t::read(std::istream& in) { - if (! history) - history = new history_t; + using namespace ledger::binary; - 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); - } -} + // Read in the commodity for this amount -bool commodity_base_t::remove_price(const datetime_t& date) -{ - if (history) { - history_map::size_type n = history->prices.erase(date); - if (n > 0) { - if (history->prices.empty()) - history = NULL; - return true; - } + commodity_t::ident_t ident; + read_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); } - return false; -} -commodity_base_t * commodity_base_t::create(const std::string& symbol) -{ - commodity_base_t * commodity = new commodity_base_t(symbol); - - DEBUG_PRINT("amounts.commodities", "Creating base commodity " << symbol); + // Read in the quantity - std::pair<base_commodities_map::iterator, bool> result - = commodities.insert(base_commodities_pair(symbol, commodity)); - assert(result.second); + char byte; + in.read(&byte, sizeof(byte)); - return commodity; -} + if (byte < 3) { + quantity = new bigint_t; -bool commodity_t::needs_quotes(const std::string& symbol) -{ - for (const char * p = symbol.c_str(); *p; p++) - if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') - return true; + 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); - return false; -} + char negative; + in.read(&negative, sizeof(negative)); + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); -bool commodity_t::valid() const -{ - if (symbol().empty() && this != null_commodity) { - DEBUG_PRINT("ledger.validate", - "commodity_t: symbol().empty() && this != null_commodity"); - return false; - } + in.read((char *)&quantity->prec, sizeof(quantity->prec)); - if (annotated && ! base) { - DEBUG_PRINT("ledger.validate", "commodity_t: annotated && ! base"); - return false; + bigint_t::flags_t tflags; + in.read((char *)&tflags, sizeof(tflags)); + quantity->set_flags(tflags); } - - if (precision() > 16) { - DEBUG_PRINT("ledger.validate", "commodity_t: precision() > 16"); - return false; + else { + assert(false); } - - return true; } -commodity_t * commodity_t::create(const std::string& symbol) +void amount_t::read(const char *& data) { - std::auto_ptr<commodity_t> commodity(new commodity_t); + using namespace ledger::binary; - commodity->base = commodity_base_t::create(symbol); + // Read in the commodity for this amount - if (needs_quotes(symbol)) { - commodity->qualified_symbol = "\""; - commodity->qualified_symbol += symbol; - commodity->qualified_symbol += "\""; - } else { - commodity->qualified_symbol = symbol; + commodity_t::ident_t ident; + read_long(data, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); } - DEBUG_PRINT("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; - - // 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 std::string& symbol) -{ - DEBUG_PRINT("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 std::string& symbol) -{ - DEBUG_PRINT("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 datetime_t& moment) -{ - datetime_t age; - amount_t price; + // Read in the quantity - if (history) { - assert(history->prices.size() > 0); + char byte = *data++;; - if (! moment) { - history_map::reverse_iterator r = history->prices.rbegin(); - age = (*r).first; - price = (*r).second; + if (byte < 3) { + if (byte == 2) { + quantity = new((bigint_t *)bigints_next) bigint_t; + bigints_next += sizeof(bigint_t); } 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 = 0; - } - } else { - price = (*i).second; - } - } + quantity = new bigint_t; } - } - if (updater && ! (flags & COMMODITY_STYLE_NOMARKET)) - (*updater)(*this, moment, age, - (history && history->prices.size() > 0 ? - (*history->prices.rbegin()).first : datetime_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 (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 datetime_t& date, - const std::string& tag) -{ - if (price) - out << " {" << price << '}'; - - if (date) - out << " [" << date << ']'; - - if (! tag.empty()) - out << " (" << tag << ')'; -} - -commodity_t * -annotated_commodity_t::create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag, - const std::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_PRINT("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; - - return commodity.release(); -} - -namespace { - std::string make_qualified_name(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag) - { - if (price < 0) - throw new amount_error("A commodity's price may not be negative"); - - std::ostringstream name; + 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; - comm.write(name); - annotated_commodity_t::write_annotations(name, price, date, tag); + char negative = *data++; + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); - DEBUG_PRINT("amounts.commodities", "make_qualified_name for " - << comm.qualified_symbol << std::endl - << " price " << price << " " - << " date " << date << " " - << " tag " << tag); + quantity->prec = *((precision_t *) data); + data += sizeof(precision_t); + quantity->set_flags(*((flags_t *) data)); + data += sizeof(flags_t); - DEBUG_PRINT("amounts.commodities", "qualified_name is " << name.str()); + if (byte == 2) + quantity->add_flags(BIGINT_BULK_ALLOC); + } else { + uint_fast32_t index = *((uint_fast32_t *) data); + data += sizeof(uint_fast32_t); - return name.str(); + quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; } } -commodity_t * -annotated_commodity_t::find_or_create(const commodity_t& comm, - const amount_t& price, - const datetime_t& date, - const std::string& tag) +void amount_t::write(std::ostream& out, bool optimized) const { - std::string name = make_qualified_name(comm, price, date, tag); + using namespace ledger::binary; - 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); -} + // Write out the commodity for this amount -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 (! quantity) + throw_(amount_error, "Cannot serialize an uninitialized amount"); - 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 (commodity_) + write_long(out, commodity_->ident); + else + write_long<commodity_t::ident_t>(out, 0xffffffff); - if (! aleftcomm.price && arightcomm.price) - return true; - if (aleftcomm.price && ! arightcomm.price) - return false; + // Write out the quantity - if (aleftcomm.price && arightcomm.price) { - amount_t leftprice(aleftcomm.price); - leftprice.reduce(); - amount_t rightprice(arightcomm.price); - rightprice.reduce(); + char byte; - if (leftprice.commodity() == rightprice.commodity()) { - int diff = leftprice.compare(rightprice); - if (diff) - return diff; - } 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(); - - int diff = leftprice.compare(rightprice); - if (diff) - return diff; - } + if (! optimized || quantity->index == 0) { + if (optimized) { + quantity->index = ++bigints_index; // if !optimized, this is garbage + bigints_count++; + byte = 2; + } else { + byte = 1; } + out.write(&byte, sizeof(byte)); - if (! aleftcomm.date && arightcomm.date) - return true; - if (aleftcomm.date && ! arightcomm.date) - return false; - - if (aleftcomm.date && arightcomm.date) { - int diff = aleftcomm.date - arightcomm.date; - if (diff) - return diff < 0; + 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); } - if (aleftcomm.tag.empty() && ! arightcomm.tag.empty()) - return true; - if (! aleftcomm.tag.empty() && arightcomm.tag.empty()) - return false; + byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0; + out.write(&byte, sizeof(byte)); - if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty()) - return aleftcomm.tag < arightcomm.tag; + out.write((char *)&quantity->prec, sizeof(quantity->prec)); + bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC; + assert(sizeof(tflags) == sizeof(bigint_t::flags_t)); + out.write((char *)&tflags, sizeof(tflags)); + } else { + assert(quantity->ref > 1); - // The two annotated commodities don't differ enough to matter. This - // should make this identical. - assert(0); - return true; + // Since this value has already been written, we simply write + // out a reference to which one it was. + byte = 3; + out.write(&byte, sizeof(byte)); + out.write((char *)&quantity->index, sizeof(quantity->index)); } } -} // namespace ledger - -#ifdef USE_BOOST_PYTHON - -#include <boost/python.hpp> -#include <Python.h> - -using namespace boost::python; -using namespace ledger; - -int py_amount_quantity(amount_t& amount) -{ - std::string quant = amount.quantity_string(); - return std::atol(quant.c_str()); -} -void py_parse_1(amount_t& amount, const std::string& str, - unsigned char flags) { - amount.parse(str, flags); -} -void py_parse_2(amount_t& amount, const std::string& str) { - amount.parse(str); -} - -struct commodity_updater_wrap : public commodity_base_t::updater_t +bool amount_t::valid() const { - PyObject * self; - commodity_updater_wrap(PyObject * self_) : self(self_) {} - - virtual void operator()(commodity_base_t& commodity, - const datetime_t& moment, - const datetime_t& date, - const datetime_t& last, - amount_t& price) { - call_method<void>(self, "__call__", commodity, moment, date, last, price); + if (quantity) { + if (quantity->ref == 0) { + DEBUG("ledger.validate", "amount_t: quantity->ref == 0"); + return false; + } } -}; - -commodity_t * py_find_commodity(const std::string& symbol) -{ - return commodity_t::find(symbol); -} - -#define EXC_TRANSLATOR(type) \ - void exc_translate_ ## type(const type& err) { \ - PyErr_SetString(PyExc_RuntimeError, err.what()); \ + else if (commodity_) { + DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); + return false; } - -EXC_TRANSLATOR(amount_error) - -void export_amount() -{ - scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE; - scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE; - - class_< amount_t > ("Amount") - .def(init<amount_t>()) - .def(init<std::string>()) - .def(init<char *>()) - .def(init<bool>()) - .def(init<long>()) - .def(init<unsigned long>()) - .def(init<double>()) - - .def(self += self) - .def(self += long()) - .def(self + self) - .def(self + long()) - .def(self -= self) - .def(self -= long()) - .def(self - self) - .def(self - long()) - .def(self *= self) - .def(self *= long()) - .def(self * self) - .def(self * long()) - .def(self /= self) - .def(self /= long()) - .def(self / self) - .def(self / long()) - .def(- self) - - .def(self < self) - .def(self < long()) - .def(self <= self) - .def(self <= long()) - .def(self > self) - .def(self > long()) - .def(self >= self) - .def(self >= long()) - .def(self == self) - .def(self == long()) - .def(self != self) - .def(self != long()) - .def(! self) - - .def(self_ns::int_(self)) - .def(self_ns::float_(self)) - .def(self_ns::str(self)) - .def(abs(self)) - - .add_property("commodity", - make_function(&amount_t::commodity, - return_value_policy<reference_existing_object>()), - make_function(&amount_t::set_commodity, - with_custodian_and_ward<1, 2>())) - - .def("strip_annotations", &amount_t::strip_annotations) - - .def("negate", &amount_t::negate) - .def("negated", &amount_t::negated) - .def("parse", py_parse_1) - .def("parse", py_parse_2) - .def("reduce", &amount_t::reduce) - - .def("valid", &amount_t::valid) - ; - - class_< commodity_base_t::updater_t, commodity_updater_wrap, - boost::noncopyable > - ("Updater") - ; - - scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS; - scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED; - scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED; - scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN; - scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS; - scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET; - scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN; - - class_< commodity_t > ("Commodity") - .add_property("symbol", &commodity_t::symbol) - -#if 0 - .add_property("name", &commodity_t::name, &commodity_t::set_name) - .add_property("note", &commodity_t::note, &commodity_t::set_note) - .add_property("precision", &commodity_t::precision, - &commodity_t::set_precision) - .add_property("flags", &commodity_t::flags, &commodity_t::set_flags) - .add_property("add_flags", &commodity_t::add_flags) - .add_property("drop_flags", &commodity_t::drop_flags) -#if 0 - .add_property("updater", &commodity_t::updater) -#endif - - .add_property("smaller", - make_getter(&commodity_t::smaller, - return_value_policy<reference_existing_object>()), - make_setter(&commodity_t::smaller, - return_value_policy<reference_existing_object>())) - .add_property("larger", - make_getter(&commodity_t::larger, - return_value_policy<reference_existing_object>()), - make_setter(&commodity_t::larger, - return_value_policy<reference_existing_object>())) - - .def(self_ns::str(self)) - - .def("find", py_find_commodity, - return_value_policy<reference_existing_object>()) - .staticmethod("find") -#endif - - .def("add_price", &commodity_t::add_price) - .def("remove_price", &commodity_t::remove_price) - .def("value", &commodity_t::value) - - .def("valid", &commodity_t::valid) - ; - -#define EXC_TRANSLATE(type) \ - register_exception_translator<type>(&exc_translate_ ## type); - - EXC_TRANSLATE(amount_error); + return true; } -#endif // USE_BOOST_PYTHON +} // namespace ledger |