diff options
Diffstat (limited to 'amount.cc')
-rw-r--r-- | amount.cc | 1332 |
1 files changed, 417 insertions, 915 deletions
@@ -1,1119 +1,621 @@ -#include "amount.h" -#include "util.h" +#include "ledger.h" #include <sstream> -#include <deque> - -#include "gmp.h" +#include <cstdio> +#include <gmp.h> // GNU multi-precision library namespace ledger { -#define BIGINT_BULK_ALLOC 0x0001 +#define MAX_PRECISION 10 // must be 2 or higher -class amount_t::bigint_t { - public: - mpz_t val; - unsigned short prec; - unsigned short flags; - unsigned int ref; - unsigned int index; +////////////////////////////////////////////////////////////////////// +// +// The `amount' structure. Every transaction has an associated +// amount, which is represented by this structure. `amount' uses the +// GNU multi-precision library, allowing for arbitrarily large +// amounts. Each amount is a quantity of a certain commodity, with +// an optional price per-unit for that commodity at the time the +// amount was stated. +// +// To create an amount, for example: +// +// amount * cost = create_amount("50.2 MSFT @ $100.50"); +// - bigint_t() : prec(0), flags(0), ref(1), index(0) { - mpz_init(val); - } - bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) { - mpz_init_set(val, _val); - } - bigint_t(const bigint_t& other) - : prec(other.prec), flags(0), ref(1), index(0) { - mpz_init_set(val, other.val); - } - ~bigint_t() { - assert(ref == 0); - mpz_clear(val); - } -}; +class gmp_amount : public amount +{ + bool priced; -unsigned int sizeof_bigint_t() { - return sizeof(amount_t::bigint_t); -} + mpz_t price; + commodity * price_comm; -#define MPZ(x) ((x)->val) + mpz_t quantity; + commodity * quantity_comm; -static mpz_t temp; -static mpz_t divisor; -static amount_t::bigint_t true_value; + gmp_amount(const gmp_amount& other) {} + gmp_amount& operator=(const gmp_amount& other) { return *this; } -commodity_t::updater_t * commodity_t::updater = NULL; -commodities_map commodity_t::commodities; -commodity_t * commodity_t::null_commodity; + public: + gmp_amount() : priced(false), price_comm(NULL), quantity_comm(NULL) { + mpz_init(price); + mpz_init(quantity); + } -static struct _init_amounts { - _init_amounts(); - ~_init_amounts(); -} _init_obj; + virtual ~gmp_amount() { + mpz_clear(price); + mpz_clear(quantity); + } -_init_amounts::_init_amounts() -{ - mpz_init(temp); - mpz_init(divisor); + virtual commodity * commdty() const { + return quantity_comm; + } - mpz_set_ui(true_value.val, 1); + virtual void set_commdty(commodity * comm) { + quantity_comm = comm; + } - commodity_t::updater = NULL; - commodity_t::null_commodity = commodity_t::find_commodity("", true); -} + virtual amount * copy() const; + virtual amount * value(const amount *) const; + virtual void set_value(const amount * val); + virtual amount * street(std::time_t * when = NULL, + bool use_history = false, + bool download = false) const; -_init_amounts::~_init_amounts() -{ - mpz_clear(divisor); - mpz_clear(temp); + virtual bool has_price() const { + return priced; + } + virtual amount * per_item_price() const; + + virtual bool is_zero() const; + virtual bool is_negative() const; + virtual int compare(const amount * other) const; - if (commodity_t::updater) { - delete commodity_t::updater; - commodity_t::updater = NULL; + virtual void negate() { + mpz_ui_sub(quantity, 0, quantity); } + virtual void credit(const amount * other); - for (commodities_map::iterator i = commodity_t::commodities.begin(); - i != commodity_t::commodities.end(); - i++) - delete (*i).second; + virtual void parse(const std::string& num); + virtual const std::string as_str(bool full_prec) const; - commodity_t::commodities.clear(); + friend amount * create_amount(const std::string& value, + const amount * cost); +}; - true_value.ref--; +amount * create_amount(const std::string& value, const amount * cost) +{ + gmp_amount * a = new gmp_amount(); + a->parse(value); + if (cost) + a->set_value(cost); + return a; } -static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) +static void round(mpz_t out, const mpz_t val, int 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 divisor; mpz_t quotient; mpz_t remainder; + mpz_init(divisor); 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_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_tdiv_qr(quotient, remainder, val, divisor); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec - 1); mpz_mul_ui(divisor, divisor, 5); if (mpz_sgn(remainder) < 0) { - mpz_neg(divisor, divisor); + mpz_ui_sub(divisor, 0, divisor); + if (mpz_cmp(remainder, divisor) < 0) { - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); mpz_add(remainder, divisor, remainder); mpz_ui_sub(remainder, 0, remainder); - mpz_add(out, value, remainder); + mpz_add(out, val, remainder); } else { - mpz_sub(out, value, remainder); + mpz_sub(out, val, remainder); } } else { if (mpz_cmp(remainder, divisor) >= 0) { - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); mpz_sub(remainder, divisor, remainder); - mpz_add(out, value, remainder); + mpz_add(out, val, remainder); } else { - mpz_sub(out, value, remainder); + mpz_sub(out, val, remainder); } } + + mpz_clear(divisor); mpz_clear(quotient); mpz_clear(remainder); - - // chop off the rounded bits - mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); - mpz_tdiv_q(out, out, divisor); } -amount_t::amount_t(const bool value) +static void multiply(mpz_t out, const mpz_t l, const mpz_t r) { - if (value) { - quantity = &true_value; - quantity->ref++; - } else { - quantity = NULL; - } - commodity_ = NULL; -} + mpz_t divisor; -amount_t::amount_t(const int value) -{ - if (value != 0) { - quantity = new bigint_t; - mpz_set_si(MPZ(quantity), value); - } else { - quantity = NULL; - } - commodity_ = NULL; -} + mpz_init(divisor); -amount_t::amount_t(const unsigned int value) -{ - if (value != 0) { - quantity = new bigint_t; - mpz_set_ui(MPZ(quantity), value); - } else { - quantity = NULL; - } - commodity_ = NULL; -} + mpz_mul(out, l, r); -amount_t::amount_t(const double value) -{ - if (value != 0.0) { - quantity = new bigint_t; - mpz_set_d(MPZ(quantity), value); - // jww (2004-08-20): How do I calculate this? - } else { - quantity = NULL; - } - commodity_ = NULL; -} + // The number is at double-precision right now, so rounding at + // precision 0 effectively means rounding to the ordinary + // precision. + round(out, out, 0); -void amount_t::_release() -{ - if (--quantity->ref == 0) { - if (! (quantity->flags & BIGINT_BULK_ALLOC)) - delete quantity; - else - quantity->~bigint_t(); - } -} + // after multiplying, truncate to the correct precision + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); + mpz_tdiv_q(out, out, divisor); -void amount_t::_init() -{ - if (! quantity) { - quantity = new bigint_t; - } - else if (quantity->ref > 1) { - _release(); - quantity = new bigint_t; - } + mpz_clear(divisor); } -void amount_t::_dup() +amount * gmp_amount::copy() const { - if (quantity->ref > 1) { - bigint_t * q = new bigint_t(*quantity); - _release(); - quantity = q; - } -} + gmp_amount * new_amt = new gmp_amount(); -void amount_t::_copy(const amount_t& amt) -{ - if (quantity != amt.quantity) { - if (quantity) - _release(); - - // Never maintain a pointer into a bulk allocation pool; such - // pointers are not guaranteed to remain. - if (amt.quantity->flags & BIGINT_BULK_ALLOC) { - quantity = new bigint_t(*amt.quantity); - } else { - quantity = amt.quantity; - quantity->ref++; - } - } - commodity_ = amt.commodity_; -} + mpz_set(new_amt->quantity, quantity); + new_amt->quantity_comm = quantity_comm; -amount_t& amount_t::operator=(const std::string& value) -{ - std::istringstream str(value); - parse(str); - return *this; + return new_amt; } -amount_t& amount_t::operator=(const char * value) +amount * gmp_amount::per_item_price() const { - std::string valstr(value); - std::istringstream str(valstr); - parse(str); - return *this; -} + if (! priced) + return NULL; -// assignment operator -amount_t& amount_t::operator=(const amount_t& amt) -{ - if (this != &amt) { - if (amt.quantity) - _copy(amt); - else if (quantity) - _clear(); - } - return *this; -} + gmp_amount * new_amt = new gmp_amount(); -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; -} + mpz_set(new_amt->quantity, price); + new_amt->quantity_comm = price_comm; -amount_t& amount_t::operator=(const int value) -{ - if (value == 0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_si(MPZ(quantity), value); - } - return *this; + return new_amt; } -amount_t& amount_t::operator=(const unsigned int value) +amount * gmp_amount::value(const amount * pr) const { - if (value == 0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - mpz_set_ui(MPZ(quantity), value); - } - return *this; -} - -amount_t& amount_t::operator=(const double value) -{ - if (value == 0.0) { - if (quantity) - _clear(); - } else { - commodity_ = NULL; - _init(); - // jww (2004-08-20): How do I calculate precision? - mpz_set_d(MPZ(quantity), value); - } - return *this; -} + if (pr) { + const gmp_amount * p = dynamic_cast<const gmp_amount *>(pr); + assert(p); + gmp_amount * new_amt = new gmp_amount(); -void amount_t::_resize(unsigned int prec) -{ - assert(prec < 256); + multiply(new_amt->quantity, quantity, p->quantity); - if (! quantity || prec == quantity->prec) - return; + // If the price we are multiplying by has no commodity, use the + // commodity of the current amount. + if (p->quantity_comm) + new_amt->quantity_comm = p->quantity_comm; + else + new_amt->quantity_comm = quantity_comm; - _dup(); + if (new_amt->quantity_comm && + new_amt->quantity_comm->precision < MAX_PRECISION) + round(new_amt->quantity, new_amt->quantity, + new_amt->quantity_comm->precision); - 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); + return new_amt; } - - quantity->prec = prec; -} - - -amount_t& amount_t::operator+=(const amount_t& amt) -{ - if (! amt.quantity) - return *this; - - if (! quantity) { - _copy(amt); - return *this; + else if (! priced) { + return copy(); } + else { + gmp_amount * new_amt = new gmp_amount(); - _dup(); + multiply(new_amt->quantity, quantity, price); - if (commodity_ != amt.commodity_) - throw amount_error("Adding amounts with different commodities"); + new_amt->quantity_comm = price_comm; + if (new_amt->quantity_comm->precision < MAX_PRECISION) + round(new_amt->quantity, new_amt->quantity, + new_amt->quantity_comm->precision); - if (quantity->prec == amt.quantity->prec) { - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - _resize(amt.quantity->prec); - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } else { - amount_t temp = amt; - temp._resize(quantity->prec); - mpz_add(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); + return new_amt; } - - return *this; } -amount_t& amount_t::operator-=(const amount_t& amt) +amount * gmp_amount::street(std::time_t * when, + bool use_history, bool download) const { - if (! amt.quantity) - return *this; - - if (! quantity) { - quantity = new bigint_t(*amt.quantity); - commodity_ = amt.commodity_; - mpz_neg(MPZ(quantity), MPZ(quantity)); - return *this; - } - - _dup(); - - if (commodity_ != amt.commodity_) - throw amount_error("Subtracting amounts with different commodities"); + static std::time_t now = std::time(NULL); + if (! when) + when = &now; - if (quantity->prec == amt.quantity->prec) { - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } - else if (quantity->prec < amt.quantity->prec) { - _resize(amt.quantity->prec); - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - } else { - amount_t temp = amt; - temp._resize(quantity->prec); - mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(temp.quantity)); - } + amount * amt = copy(); - return *this; -} + if (! amt->commdty()) + return amt; -amount_t& amount_t::operator*=(const amount_t& amt) -{ - if (! amt.quantity || ! quantity) - return *this; + int max = 10; + while (--max >= 0) { + amount * price = amt->commdty()->price(when, use_history, download); + if (! price) + break; - _dup(); + amount * old = amt; + amt = amt->value(price); - mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += amt.quantity->prec; + if (amt->commdty() == old->commdty()) { + delete old; + break; + } - 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; + delete old; } - return *this; + return amt; } -amount_t& amount_t::operator/=(const amount_t& amt) +void gmp_amount::set_value(const amount * val) { - if (! quantity) - return *this; - if (! amt.quantity) - 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 + 6); - mpz_mul(MPZ(quantity), MPZ(quantity), divisor); - mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += 6; - - 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; - } + assert(! priced); // don't specify the pricing twice! - return *this; -} + const gmp_amount * v = dynamic_cast<const gmp_amount *>(val); + assert(v); -// unary negation -void amount_t::negate() -{ - if (quantity) { - _dup(); - mpz_neg(MPZ(quantity), MPZ(quantity)); - } -} + mpz_t quotient; + mpz_t remainder; + mpz_t addend; -int amount_t::sign() const -{ - return quantity ? mpz_sgn(MPZ(quantity)) : 0; -} + mpz_init(quotient); + mpz_init(remainder); + mpz_init(addend); -// comparisons between amounts -#define AMOUNT_CMP_AMOUNT(OP) \ -bool amount_t::operator OP(const amount_t& amt) const \ -{ \ - if (! quantity) \ - return amt > 0; \ - if (! amt.quantity) \ - return *this < 0; \ - \ - if (commodity() && amt.commodity() && \ - commodity() != amt.commodity()) \ - return false; \ - \ - if (quantity->prec == amt.quantity->prec) { \ - return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)) OP 0; \ - } \ - 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)) OP 0; \ - } \ - else { \ - amount_t temp = amt; \ - temp._resize(quantity->prec); \ - return mpz_cmp(MPZ(quantity), MPZ(temp.quantity)) OP 0; \ - } \ -} + mpz_ui_pow_ui(addend, 10, MAX_PRECISION); -AMOUNT_CMP_AMOUNT(<) -AMOUNT_CMP_AMOUNT(<=) -AMOUNT_CMP_AMOUNT(>) -AMOUNT_CMP_AMOUNT(>=) -AMOUNT_CMP_AMOUNT(==) + mpz_tdiv_qr(quotient, remainder, v->quantity, quantity); + mpz_mul(remainder, remainder, addend); + mpz_tdiv_q(remainder, remainder, quantity); + mpz_mul(quotient, quotient, addend); + mpz_add(quotient, quotient, remainder); + mpz_abs(quotient, quotient); -amount_t::operator bool() const -{ - if (! quantity) - return false; + priced = true; + mpz_set(price, quotient); + price_comm = v->quantity_comm; - if (quantity->prec <= commodity().precision) { - return mpz_sgn(MPZ(quantity)) != 0; - } else { - mpz_set(temp, MPZ(quantity)); - if (commodity_) - mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity_->precision); - else - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_q(temp, temp, divisor); - bool zero = mpz_sgn(temp) == 0; - return ! zero; - } + mpz_clear(quotient); + mpz_clear(remainder); + mpz_clear(addend); } -amount_t amount_t::value(const std::time_t moment) const +bool gmp_amount::is_zero() const { - if (quantity && ! (commodity().flags & COMMODITY_STYLE_NOMARKET)) - if (amount_t amt = commodity().value(moment)) - return (amt * *this).round(commodity().precision); - - return *this; + mpz_t copy; + mpz_init_set(copy, quantity); + if (quantity_comm && quantity_comm->precision < MAX_PRECISION) + round(copy, copy, quantity_comm->precision); + bool zero = mpz_sgn(copy) == 0; + mpz_clear(copy); + return zero; } -amount_t amount_t::round(unsigned int prec) const +bool gmp_amount::is_negative() const { - if (! quantity || quantity->prec <= prec) - return *this; - - amount_t temp = *this; - temp._dup(); - - mpz_round(MPZ(temp.quantity), MPZ(temp.quantity), temp.quantity->prec, prec); - temp.quantity->prec = prec; - - return temp; + return mpz_sgn(quantity) < 0; } -std::ostream& operator<<(std::ostream& _out, const amount_t& amt) +int gmp_amount::compare(const amount * other) const { - if (! amt.quantity) - return _out; - - std::ostringstream out; + amount * revalued = copy(); + amount * copied = other->copy(); + revalued->negate(); + copied->credit(revalued); + delete revalued; + int result = 1; + if (copied->is_zero()) + result = 0; + else if (copied->is_negative()) + result = -1; + delete copied; + return result; +} +static std::string amount_to_str(const commodity * comm, const mpz_t val, + bool full_precision) +{ + mpz_t temp; mpz_t quotient; mpz_t rquotient; mpz_t remainder; + mpz_t divisor; + + bool negative = false; + + mpz_init_set(temp, val); mpz_init(quotient); mpz_init(rquotient); mpz_init(remainder); + mpz_init(divisor); - bool negative = false; + if (comm == NULL) + full_precision = true; - // Ensure the value is rounded to the commodity's precision before - // outputting it. NOTE: `rquotient' is used here as a temp variable! + if (! full_precision && comm->precision < MAX_PRECISION) + round(temp, temp, comm->precision); - commodity_t& commodity(amt.commodity()); - unsigned short precision; + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); + mpz_tdiv_qr(quotient, remainder, temp, divisor); - if (commodity == *commodity_t::null_commodity) { - mpz_ui_pow_ui(divisor, 10, amt.quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(amt.quantity), divisor); - precision = amt.quantity->prec; - } - else if (commodity.precision < amt.quantity->prec) { - mpz_round(rquotient, MPZ(amt.quantity), amt.quantity->prec, - commodity.precision); - mpz_ui_pow_ui(divisor, 10, commodity.precision); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = commodity.precision; - } - else if (commodity.precision > amt.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, commodity.precision - amt.quantity->prec); - mpz_mul(rquotient, MPZ(amt.quantity), divisor); - mpz_ui_pow_ui(divisor, 10, commodity.precision); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = commodity.precision; - } - else if (amt.quantity->prec) { - mpz_ui_pow_ui(divisor, 10, amt.quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(amt.quantity), divisor); - precision = amt.quantity->prec; - } - else { - mpz_set(quotient, MPZ(amt.quantity)); - mpz_set_ui(remainder, 0); - precision = 0; - } - - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) negative = true; + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); + if (full_precision || comm->precision == MAX_PRECISION) { + mpz_set(rquotient, remainder); + } else { + assert(MAX_PRECISION - comm->precision > 0); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - comm->precision); + mpz_tdiv_qr(rquotient, remainder, remainder, divisor); } - mpz_set(rquotient, remainder); - if (! (commodity.flags & COMMODITY_STYLE_SUFFIXED)) { - if (commodity.quote) - out << "\"" << commodity.symbol << "\""; - else - out << commodity.symbol; - if (commodity.flags & COMMODITY_STYLE_SEPARATED) - out << " "; + std::ostringstream s; + + if (comm && comm->prefix) { + s << comm->symbol; + if (comm->separate) + s << " "; } if (negative) - out << "-"; + s << "-"; - if (mpz_sgn(quotient) == 0) { - out << '0'; - } - else if (! (commodity.flags & COMMODITY_STYLE_THOUSANDS)) { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); - } + if (mpz_sgn(quotient) == 0) + s << '0'; + else if (! comm || ! comm->thousands) + s << quotient; else { - std::deque<std::string> strs; - char buf[4]; + bool printed = false; - 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); - } + // jww (2003-09-29): use a smarter starting value for `powers' + for (int powers = 27; powers >= 0; powers -= 3) { + mpz_ui_pow_ui(divisor, 10, powers); + mpz_tdiv_q(temp, quotient, divisor); - bool printed = false; + if (mpz_sgn(temp) == 0) + continue; + + mpz_ui_pow_ui(divisor, 10, 3); + mpz_tdiv_r(temp, temp, divisor); - for (std::deque<std::string>::reverse_iterator i = strs.rbegin(); - i != strs.rend(); - i++) { if (printed) { - out << (commodity.flags & COMMODITY_STYLE_EUROPEAN ? '.' : ','); - out.width(3); - out.fill('0'); + s.width(3); + s.fill('0'); } - out << *i; + s << temp; - printed = true; + if (powers > 0) { + s << (comm && comm->european ? '.' : ','); + printed = true; + } } } - if (precision) { - out << ((commodity.flags & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + s << (comm && comm->european ? ',' : '.'); + + if (comm && (! full_precision || mpz_sgn(rquotient) == 0)) { + s.width(comm->precision); + s.fill('0'); + s << rquotient; + } else { + char buf[MAX_PRECISION + 1]; + gmp_sprintf(buf, "%Zd", rquotient); + + int width = std::strlen(buf); + char * p = buf + (width - 1); - out.width(precision); - out.fill('0'); + width = MAX_PRECISION - width; - char * p = mpz_get_str(NULL, 10, rquotient); - out << p; - std::free(p); + if (comm) { + while (p >= buf && *p == '0' && + (p - buf) >= (comm->precision - width)) + p--; + *(p + 1) = '\0'; + } + + s.width(width + std::strlen(buf)); + s.fill('0'); + s << buf; } - if (commodity.flags & COMMODITY_STYLE_SUFFIXED) { - if (commodity.flags & COMMODITY_STYLE_SEPARATED) - out << " "; - if (commodity.quote) - out << "\"" << commodity.symbol << "\""; - else - out << commodity.symbol; + if (comm && ! comm->prefix) { + if (comm->separate) + s << " "; + s << comm->symbol; } + mpz_clear(temp); mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); + mpz_clear(divisor); - // 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 s.str(); } -void parse_quantity(std::istream& in, std::string& value) +const std::string gmp_amount::as_str(bool full_prec) const { - char buf[256]; - char c = peek_next_nonws(in); - READ_INTO(in, buf, 255, c, - std::isdigit(c) || c == '-' || c == '.' || c == ','); - value = buf; -} + std::ostringstream s; -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 amount_error("Quoted commodity symbol lacks closing quote"); - } else { - READ_INTO(in, buf, 255, c, ! std::isspace(c) && ! std::isdigit(c) && - c != '-' && c != '.'); + s << amount_to_str(quantity_comm, quantity, full_prec); + + if (priced) { + s << " @ "; + s << amount_to_str(price_comm, price, full_prec); } - symbol = buf; + return s.str(); } -void amount_t::parse(std::istream& in) +static void parse_number(mpz_t out, const std::string& number, + commodity * comm) { - // The possible syntax for an amount is: - // - // [-]NUM[ ]SYM [@ AMOUNT] - // SYM[ ][-]NUM [@ AMOUNT] + const char * num = number.c_str(); - std::string symbol; - std::string quant; - unsigned int flags = COMMODITY_STYLE_DEFAULTS;; + if (char * p = std::strchr(num, '/')) { + mpz_t numer; + mpz_t val; - char c = peek_next_nonws(in); - if (std::isdigit(c) || c == '.' || c == '-') { - parse_quantity(in, quant); + // The number was specified as a numerator over denominator, such + // as 5250/100. This gives us the precision, and avoids any + // nastiness having to do with international numbering formats. - char n; - if (! in.eof() && ((n = in.peek()) != '\n')) { - if (std::isspace(n)) - flags |= COMMODITY_STYLE_SEPARATED; - - parse_commodity(in, symbol); - - flags |= COMMODITY_STYLE_SUFFIXED; - } - } else { - parse_commodity(in, symbol); - - if (std::isspace(in.peek())) - flags |= COMMODITY_STYLE_SEPARATED; - - parse_quantity(in, quant); - } - - if (quant.empty()) - throw amount_error("No quantity specified for amount"); + std::string numer_str(num, p - num); + mpz_init_set_str(numer, numer_str.c_str(), 10); + mpz_init(val); - _init(); + int missing = MAX_PRECISION - (std::strlen(++p) - 1); + assert(missing > 0); + mpz_ui_pow_ui(val, 10, missing); - std::string::size_type last_comma = quant.rfind(','); - std::string::size_type last_period = quant.rfind('.'); + mpz_mul(out, numer, val); - if (last_comma != std::string::npos && last_period != std::string::npos) { - flags |= COMMODITY_STYLE_THOUSANDS; - if (last_comma > last_period) { - flags |= COMMODITY_STYLE_EUROPEAN; - quantity->prec = quant.length() - last_comma - 1; - } else { - quantity->prec = quant.length() - last_period - 1; - } - } - else if (last_comma != std::string::npos) { - flags |= COMMODITY_STYLE_EUROPEAN; - quantity->prec = quant.length() - last_comma - 1; - } - else if (last_period != std::string::npos) { - quantity->prec = quant.length() - last_period - 1; + mpz_clear(numer); + mpz_clear(val); } else { - quantity->prec = 0; - } + static char buf[256]; - // Create the commodity if has not already been seen, and update the - // precision if something greater was used for the quantity. + // The number is specified as the user desires, with the commodity + // telling us how to parse it. - commodity_ = commodity_t::find_commodity(symbol, true); - commodity_->flags |= flags; - if (quantity->prec > commodity_->precision) - commodity_->precision = quantity->prec; + std::memset(buf, '0', 255); + std::strncpy(buf, num, std::strlen(num)); - // Now we have the final number. Remove commas and periods, if - // necessary. + if (comm && comm->thousands) + while (char * t = std::strchr(buf, comm->european ? '.' : ',')) + do { *t = *(t + 1); } while (*(t++ + 1)); - if (last_comma != std::string::npos || last_period != std::string::npos) { - int len = quant.length(); - char * buf = new char[len + 1]; + char * t = std::strchr(buf, (comm && comm->european) ? ',' : '.'); + if (! t) + t = buf + std::strlen(num); - const char * p = quant.c_str(); - char * t = buf; - - while (*p) { - if (*p == ',' || *p == '.') - p++; - *t++ = *p++; + for (int prec = 0; prec < MAX_PRECISION; prec++) { + *t = *(t + 1); + t++; } *t = '\0'; - mpz_set_str(MPZ(quantity), buf, 10); - - delete[] buf; - } else { - mpz_set_str(MPZ(quantity), quant.c_str(), 10); + mpz_set_str(out, buf, 10); } } -void amount_t::parse(const std::string& str) +static commodity * parse_amount(mpz_t out, const char * num, + int matched, int * ovector, int base) { - std::istringstream stream(str); - parse(stream); -} + static char buf[256]; + bool saw_commodity = false; + bool prefix = false; + bool separate = true; + bool thousands = true; + bool european = false; -char * bigints; -char * bigints_next; -unsigned int bigints_index; -unsigned int bigints_count; + std::string symbol; + int precision, result; -void amount_t::read_quantity(char *& data) -{ - char byte = *data++;; - - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new((bigint_t *)bigints_next) bigint_t; - bigints_next += sizeof(bigint_t); - quantity->flags |= BIGINT_BULK_ALLOC; - - unsigned short len = *((unsigned short *) data); - data += sizeof(unsigned short); - mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), - 0, 0, data); - data += len; - - char negative = *data++; - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); - - quantity->prec = *((unsigned short *) data); - data += sizeof(unsigned short); - } else { - unsigned int index = *((unsigned int *) data); - data += sizeof(unsigned int); - - quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); - quantity->ref++; + if (ovector[base * 2] >= 0) { + // A prefix symbol was found + saw_commodity = true; + prefix = true; + separate = ovector[(base + 2) * 2] != ovector[(base + 2) * 2 + 1]; + result = pcre_copy_substring(num, ovector, matched, base + 1, buf, 255); + assert(result >= 0); + symbol = buf; } -} -static char buf[4096]; - -void amount_t::read_quantity(std::istream& in) -{ - static std::deque<bigint_t *> _bigints; + // This is the value, and must be present + assert(ovector[(base + 3) * 2] >= 0); + result = pcre_copy_substring(num, ovector, matched, base + 3, buf, 255); + assert(result >= 0); - char byte; - in.read(&byte, sizeof(byte)); - - if (byte == 0) { - quantity = NULL; - } - else if (byte == 1) { - quantity = new bigint_t; - _bigints.push_back(quantity); - - unsigned short len; - in.read((char *)&len, sizeof(len)); - assert(len < 4096); - in.read(buf, len); - mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), - 0, 0, buf); - - char negative; - in.read(&negative, sizeof(negative)); - if (negative) - mpz_neg(MPZ(quantity), MPZ(quantity)); - - in.read((char *)&quantity->prec, sizeof(quantity->prec)); - } else { - unsigned int index; - in.read((char *)&index, sizeof(index)); - quantity = _bigints[index - 1]; - quantity->ref++; + // Where "thousands" markers used? Is it a european number? + if (char * p = std::strrchr(buf, ',')) { + if (std::strchr(p, '.')) + thousands = true; + else + european = true; } -} -void amount_t::write_quantity(std::ostream& out) const -{ - char byte; + // Determine the precision used + if (char * p = std::strchr(buf, european ? ',' : '.')) + precision = std::strlen(++p); + else if (char * p = std::strchr(buf, '/')) + precision = std::strlen(++p) - 1; + else + precision = 0; - if (! quantity) { - byte = 0; - out.write(&byte, sizeof(byte)); - return; + // Parse the actual quantity + std::string value_str = buf; + + if (ovector[(base + 4) * 2] >= 0) { + // A suffix symbol was found + saw_commodity = true; + prefix = false; + separate = ovector[(base + 5) * 2] != ovector[(base + 5) * 2 + 1]; + result = pcre_copy_substring(num, ovector, matched, base + 6, buf, 255); + assert(result >= 0); + symbol = buf; + } + + commodity * comm = NULL; + if (saw_commodity) { + commodities_map_iterator item = + main_ledger->commodities.find(symbol.c_str()); + if (item == main_ledger->commodities.end()) + comm = new commodity(symbol, prefix, separate, thousands, + european, precision); + else + comm = (*item).second; } - if (quantity->index == 0) { - quantity->index = ++bigints_index; - bigints_count++; + parse_number(out, value_str.c_str(), comm); - byte = 1; - out.write(&byte, sizeof(byte)); - - std::size_t size; - mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); - unsigned short len = size * sizeof(short); - out.write((char *)&len, sizeof(len)); - if (len) { - assert(len < 4096); - out.write(buf, len); - } - - byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0; - out.write(&byte, sizeof(byte)); - - out.write((char *)&quantity->prec, sizeof(quantity->prec)); - } else { - assert(quantity->ref > 1); - - // Since this value has already been written, we simply write - // out a reference to which one it was. - byte = 2; - out.write(&byte, sizeof(byte)); - out.write((char *)&quantity->index, sizeof(quantity->index)); - } + return comm; } -bool amount_t::valid() const +void gmp_amount::parse(const std::string& number) { - if (quantity) { - if (! commodity_) - return false; - - if (quantity->ref == 0) - return false; - } - else if (commodity_) { - return false; + // Compile the regular expression used for parsing amounts + static pcre * re = NULL; + if (! re) { + const char *error; + int erroffset; + static const std::string amount_re = + "(([^-0-9/., ]+)(\\s*))?([-0-9/.,]+)((\\s*)([^-0-9/., @]+))?"; + const std::string regexp = + "^" + amount_re + "(\\s*@\\s*" + amount_re + ")?$"; + re = pcre_compile(regexp.c_str(), 0, &error, &erroffset, NULL); } - return true; -} + int ovector[60]; + int matched; + matched = pcre_exec(re, NULL, number.c_str(), number.length(), + 0, 0, ovector, 60); + if (matched > 0) { + quantity_comm = parse_amount(quantity, number.c_str(), matched, + ovector, 1); -void commodity_t::add_price(const std::time_t date, const amount_t& price) -{ - history_map::iterator i = history.find(date); - if (i != history.end()) { - (*i).second = price; + // If the following succeeded, then we have a price + if (ovector[8 * 2] >= 0) { + priced = true; + price_comm = parse_amount(price, number.c_str(), matched, + ovector, 9); + } } else { - std::pair<history_map::iterator, bool> result - = history.insert(history_pair(date, price)); - assert(result.second); - } -} - -commodity_t * commodity_t::find_commodity(const std::string& symbol, - bool auto_create) -{ - commodities_map::const_iterator i = commodities.find(symbol); - if (i != commodities.end()) - return (*i).second; - - if (auto_create) { - commodity_t * commodity = new commodity_t(symbol); - add_commodity(commodity); - return commodity; + std::cerr << "Failed to parse amount: " << number << std::endl; } - - return NULL; } -amount_t commodity_t::value(const std::time_t moment) +void gmp_amount::credit(const amount * value) { - std::time_t age = 0; - amount_t price; - - for (history_map::reverse_iterator i = history.rbegin(); - i != history.rend(); - i++) - if (moment == 0 || std::difftime(moment, (*i).first) >= 0) { - age = (*i).first; - price = (*i).second; - break; - } - - if (updater) - (*updater)(*this, moment, age, (history.size() > 0 ? - (*history.rbegin()).first : 0), price); - return price; + const gmp_amount * val = dynamic_cast<const gmp_amount *>(value); + assert(quantity_comm == val->quantity_comm); + mpz_add(quantity, quantity, val->quantity); } } // namespace ledger - -#ifdef USE_BOOST_PYTHON - -#include <boost/python.hpp> -#include <Python.h> - -using namespace boost::python; -using namespace ledger; - -void (amount_t::*parse_1)(std::istream& in) = &amount_t::parse; -void (amount_t::*parse_2)(const std::string& str) = &amount_t::parse; - -struct commodity_updater_wrap : public commodity_t::updater_t -{ - PyObject * self; - commodity_updater_wrap(PyObject * self_) : self(self_) {} - - virtual void operator()(commodity_t& commodity, - const std::time_t moment, - const std::time_t date, - const std::time_t last, - amount_t& price) { - call_method<void>(self, "__call__", commodity, moment, date, last, price); - } -}; - -commodity_t * py_find_commodity_1(const std::string& symbol) -{ - return commodity_t::find_commodity(symbol); -} - -commodity_t * py_find_commodity_2(const std::string& symbol, bool auto_create) -{ - return commodity_t::find_commodity(symbol, auto_create); -} - -void export_amount() -{ - class_< amount_t > ("Amount") - .def(init<amount_t>()) - .def(init<std::string>()) - .def(init<char *>()) - .def(init<bool>()) - .def(init<int>()) - .def(init<unsigned int>()) - .def(init<double>()) - - .def("commodity", &amount_t::commodity, - return_value_policy<reference_existing_object>()) - .def("set_commodity", &amount_t::set_commodity) - .def("clear_commodity", &amount_t::clear_commodity) - - .def(self += self) - .def(self += int()) - .def(self + self) - .def(self + int()) - .def(self -= self) - .def(self -= int()) - .def(self - self) - .def(self - int()) - .def(self *= self) - .def(self *= int()) - .def(self * self) - .def(self * int()) - .def(self /= self) - .def(self /= int()) - .def(self / self) - .def(self / int()) - .def(- self) - - .def(self < self) - .def(self < int()) - .def(self <= self) - .def(self <= int()) - .def(self > self) - .def(self > int()) - .def(self >= self) - .def(self >= int()) - .def(self == self) - .def(self == int()) - .def(self != self) - .def(self != int()) - .def(! self) - - .def(abs(self)) - .def(self_ns::str(self)) - - .def("negate", &amount_t::negate) - .def("parse", parse_1) - .def("parse", parse_2) - .def("valid", &amount_t::valid) - ; - - class_< commodity_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; - - class_< commodity_t > ("Commodity") - .def(init<std::string, optional<unsigned int, unsigned int> >()) - - .def_readonly("symbol", &commodity_t::symbol) - .def("set_symbol", &commodity_t::set_symbol) - .def_readwrite("name", &commodity_t::name) - .def_readwrite("note", &commodity_t::name) - .def_readwrite("precision", &commodity_t::precision) - .def_readwrite("flags", &commodity_t::flags) - .def_readwrite("last_lookup", &commodity_t::last_lookup) - .def_readwrite("conversion", &commodity_t::conversion) - .def_readwrite("ident", &commodity_t::ident) - .def_readwrite("updater", &commodity_t::updater) - - .def(self_ns::str(self)) - - .def("add_price", &commodity_t::add_price) - .def("remove_price", &commodity_t::remove_price) - .def("value", &commodity_t::value) - - .def("valid", &commodity_t::valid) - ; - - def("add_commodity", &commodity_t::add_commodity); - def("remove_commodity", &commodity_t::remove_commodity); - def("find_commodity", py_find_commodity_1, - return_value_policy<reference_existing_object>()); - def("find_commodity", py_find_commodity_2, - return_value_policy<reference_existing_object>()); -} - -#endif // USE_BOOST_PYTHON |