diff options
Diffstat (limited to 'amount.cc')
-rw-r--r-- | amount.cc | 1529 |
1 files changed, 1117 insertions, 412 deletions
@@ -1,621 +1,1326 @@ -#include "ledger.h" +#include "amount.h" +#include "util.h" +#include <list> #include <sstream> -#include <cstdio> -#include <gmp.h> // GNU multi-precision library +#include <cstdlib> + +#include <gmp.h> namespace ledger { -#define MAX_PRECISION 10 // must be 2 or higher +#define BIGINT_BULK_ALLOC 0x0001 -////////////////////////////////////////////////////////////////////// -// -// 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"); -// +class amount_t::bigint_t { + public: + mpz_t val; + unsigned short prec; + unsigned short flags; + unsigned int ref; + unsigned int index; -class gmp_amount : public amount -{ - bool priced; + 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); + } +}; - mpz_t price; - commodity * price_comm; +unsigned int sizeof_bigint_t() { + return sizeof(amount_t::bigint_t); +} - mpz_t quantity; - commodity * quantity_comm; +#define MPZ(x) ((x)->val) - gmp_amount(const gmp_amount& other) {} - gmp_amount& operator=(const gmp_amount& other) { return *this; } +static mpz_t temp; +static mpz_t divisor; +static amount_t::bigint_t true_value; - public: - gmp_amount() : priced(false), price_comm(NULL), quantity_comm(NULL) { - mpz_init(price); - mpz_init(quantity); - } +commodity_t::updater_t * commodity_t::updater = NULL; +commodities_map commodity_t::commodities; +commodity_t * commodity_t::null_commodity; - virtual ~gmp_amount() { - mpz_clear(price); - mpz_clear(quantity); - } +static struct _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() { + mpz_clear(temp); + mpz_clear(divisor); - virtual bool has_price() const { - return priced; - } - virtual amount * per_item_price() const; + if (commodity_t::updater) { + delete commodity_t::updater; + commodity_t::updater = NULL; + } - virtual bool is_zero() const; - virtual bool is_negative() const; - virtual int compare(const amount * other) const; + for (commodities_map::iterator i = commodity_t::commodities.begin(); + i != commodity_t::commodities.end(); + i++) + delete (*i).second; - virtual void negate() { - mpz_ui_sub(quantity, 0, quantity); - } - virtual void credit(const amount * other); + commodity_t::commodities.clear(); - virtual void parse(const std::string& num); - virtual const std::string as_str(bool full_prec) const; - - friend amount * create_amount(const std::string& value, - const amount * cost); -}; + true_value.ref--; + } +} _init_obj; -amount * create_amount(const std::string& value, const amount * cost) +static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) { - gmp_amount * a = new gmp_amount(); - a->parse(value); - if (cost) - a->set_value(cost); - return a; -} + // 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); -static void round(mpz_t out, const mpz_t val, int 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, MAX_PRECISION - prec); - mpz_tdiv_qr(quotient, remainder, val, divisor); - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec - 1); + 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_ui_sub(divisor, 0, divisor); - + mpz_neg(divisor, divisor); if (mpz_cmp(remainder, divisor) < 0) { - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); mpz_add(remainder, divisor, remainder); mpz_ui_sub(remainder, 0, remainder); - mpz_add(out, val, remainder); + mpz_add(out, value, remainder); } else { - mpz_sub(out, val, remainder); + mpz_sub(out, value, remainder); } } else { if (mpz_cmp(remainder, divisor) >= 0) { - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); mpz_sub(remainder, divisor, remainder); - mpz_add(out, val, remainder); + mpz_add(out, value, remainder); } else { - mpz_sub(out, val, remainder); + mpz_sub(out, value, 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); } -static void multiply(mpz_t out, const mpz_t l, const mpz_t r) +amount_t::amount_t(const bool value) { - mpz_t divisor; + if (value) { + quantity = &true_value; + quantity->ref++; + } else { + quantity = NULL; + } + commodity_ = NULL; +} - mpz_init(divisor); +amount_t::amount_t(const long value) +{ + if (value != 0) { + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), value); + } else { + quantity = NULL; + } + commodity_ = NULL; +} - mpz_mul(out, l, r); +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; +} - // The number is at double-precision right now, so rounding at - // precision 0 effectively means rounding to the ordinary - // precision. - round(out, out, 0); +amount_t::amount_t(const double value) +{ + if (value != 0.0) { + quantity = new bigint_t; + mpz_set_d(MPZ(quantity), value); + } else { + quantity = NULL; + } + commodity_ = NULL; +} - // after multiplying, truncate to the correct precision - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_q(out, out, divisor); +void amount_t::_release() +{ + if (--quantity->ref == 0) { + if (! (quantity->flags & BIGINT_BULK_ALLOC)) + delete quantity; + else + quantity->~bigint_t(); + } +} - mpz_clear(divisor); +void amount_t::_init() +{ + if (! quantity) { + quantity = new bigint_t; + } + else if (quantity->ref > 1) { + _release(); + quantity = new bigint_t; + } } -amount * gmp_amount::copy() const +void amount_t::_dup() { - gmp_amount * new_amt = new gmp_amount(); + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; + } +} - mpz_set(new_amt->quantity, quantity); - new_amt->quantity_comm = quantity_comm; +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_; +} - return new_amt; +amount_t& amount_t::operator=(const std::string& value) +{ + std::istringstream str(value); + parse(str); + return *this; } -amount * gmp_amount::per_item_price() const +amount_t& amount_t::operator=(const char * value) { - if (! priced) - return NULL; + std::string valstr(value); + std::istringstream str(valstr); + parse(str); + return *this; +} - gmp_amount * new_amt = new gmp_amount(); +// 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; +} - mpz_set(new_amt->quantity, price); - new_amt->quantity_comm = price_comm; +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; +} - return new_amt; +amount_t& amount_t::operator=(const long value) +{ + if (value == 0) { + if (quantity) + _clear(); + } else { + commodity_ = NULL; + _init(); + mpz_set_si(MPZ(quantity), value); + } + return *this; } -amount * gmp_amount::value(const amount * pr) const +amount_t& amount_t::operator=(const unsigned long value) { - if (pr) { - const gmp_amount * p = dynamic_cast<const gmp_amount *>(pr); - assert(p); + if (value == 0) { + if (quantity) + _clear(); + } else { + commodity_ = NULL; + _init(); + mpz_set_ui(MPZ(quantity), value); + } + return *this; +} - gmp_amount * new_amt = new gmp_amount(); +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); + } + return *this; +} - multiply(new_amt->quantity, quantity, p->quantity); - // 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; +void amount_t::_resize(unsigned int prec) +{ + assert(prec < 256); + + if (! quantity || prec == quantity->prec) + return; - if (new_amt->quantity_comm && - new_amt->quantity_comm->precision < MAX_PRECISION) - round(new_amt->quantity, new_amt->quantity, - new_amt->quantity_comm->precision); + _dup(); - return new_amt; + 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 (! priced) { - return copy(); + + quantity->prec = prec; +} + + +amount_t& amount_t::operator+=(const amount_t& amt) +{ + if (! amt.quantity) + return *this; + + if (! quantity) { + _copy(amt); + return *this; } - else { - gmp_amount * new_amt = new gmp_amount(); - multiply(new_amt->quantity, quantity, price); + _dup(); - 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 (commodity_ != amt.commodity_) + throw amount_error("Adding amounts with different commodities"); - return new_amt; + 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 *this; } -amount * gmp_amount::street(std::time_t * when, - bool use_history, bool download) const +amount_t& amount_t::operator-=(const amount_t& amt) { - static std::time_t now = std::time(NULL); - if (! when) - when = &now; + if (! amt.quantity) + return *this; + + if (! quantity) { + quantity = new bigint_t(*amt.quantity); + commodity_ = amt.commodity_; + mpz_neg(MPZ(quantity), MPZ(quantity)); + return *this; + } - amount * amt = copy(); + _dup(); - if (! amt->commdty()) - return amt; + if (commodity_ != amt.commodity_) + throw amount_error("Subtracting amounts with different commodities"); - int max = 10; - while (--max >= 0) { - amount * price = amt->commdty()->price(when, use_history, download); - if (! price) - break; + 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 * old = amt; - amt = amt->value(price); + return *this; +} - if (amt->commdty() == old->commdty()) { - delete old; - break; - } +amount_t& amount_t::operator*=(const amount_t& amt) +{ + if (! amt.quantity) + return (*this = amt); + else if (! quantity) + return *this; + + _dup(); - delete old; + 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; } - return amt; + return *this; } -void gmp_amount::set_value(const amount * val) +amount_t& amount_t::operator/=(const amount_t& amt) { - assert(! priced); // don't specify the pricing twice! + if (! amt.quantity) + throw amount_error("Divide by zero"); + else if (! quantity) + return *this; + + _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; + } - const gmp_amount * v = dynamic_cast<const gmp_amount *>(val); - assert(v); + return *this; +} - mpz_t quotient; - mpz_t remainder; - mpz_t addend; +// unary negation +void amount_t::negate() +{ + if (quantity) { + _dup(); + mpz_neg(MPZ(quantity), MPZ(quantity)); + } +} - mpz_init(quotient); - mpz_init(remainder); - mpz_init(addend); +int amount_t::sign() const +{ + return quantity ? mpz_sgn(MPZ(quantity)) : 0; +} - mpz_ui_pow_ui(addend, 10, MAX_PRECISION); +// comparisons between amounts +#define AMOUNT_CMP_AMOUNT(OP) \ +bool amount_t::operator OP(const amount_t& amt) const \ +{ \ + if (! quantity) \ + return ! (amt OP 0); \ + if (! amt.quantity) \ + return *this OP 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_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_CMP_AMOUNT(<) +AMOUNT_CMP_AMOUNT(<=) +AMOUNT_CMP_AMOUNT(>) +AMOUNT_CMP_AMOUNT(>=) +AMOUNT_CMP_AMOUNT(==) - priced = true; - mpz_set(price, quotient); - price_comm = v->quantity_comm; +amount_t::operator bool() const +{ + if (! quantity) + return false; - mpz_clear(quotient); - mpz_clear(remainder); - mpz_clear(addend); + 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; + } } -bool gmp_amount::is_zero() const +amount_t::operator long() const { - 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; + if (! quantity) + return 0; + + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); + return mpz_get_si(temp); } -bool gmp_amount::is_negative() const +amount_t::operator double() const { - return mpz_sgn(quantity) < 0; + if (! quantity) + return 0.0; + + mpz_t remainder; + mpz_init(remainder); + + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(temp, remainder, temp, divisor); + + char * quotient_s = mpz_get_str(NULL, 10, temp); + char * remainder_s = mpz_get_str(NULL, 10, remainder); + + std::ostringstream num; + num << quotient_s << '.' << remainder_s; + + std::free(quotient_s); + std::free(remainder_s); + + mpz_clear(remainder); + + return std::atof(num.str().c_str()); } -int gmp_amount::compare(const amount * other) const +amount_t amount_t::value(const std::time_t moment) const { - 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; + if (quantity) { + commodity_t& comm = commodity(); + if (! (comm.flags & COMMODITY_STYLE_NOMARKET)) + if (amount_t amt = comm.value(moment)) + return (amt * *this).round(comm.precision); + } + return *this; +} + +amount_t amount_t::round(unsigned int prec) 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; } -static std::string amount_to_str(const commodity * comm, const mpz_t val, - bool full_precision) +std::string amount_t::quantity_string() const { - mpz_t temp; + if (! quantity) + return "0"; + + std::ostringstream out; + 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); - if (comm == NULL) - full_precision = true; + bool negative = false; - if (! full_precision && comm->precision < MAX_PRECISION) - round(temp, temp, comm->precision); + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_qr(quotient, remainder, temp, divisor); + commodity_t& comm(commodity()); + unsigned short precision; - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) + if (comm == *commodity_t::null_commodity || + comm.flags & COMMODITY_STYLE_VARIABLE) { + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); + precision = quantity->prec; + } + else if (comm.precision < quantity->prec) { + mpz_round(rquotient, MPZ(quantity), quantity->prec, + comm.precision); + mpz_ui_pow_ui(divisor, 10, comm.precision); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision; + } + else if (comm.precision > quantity->prec) { + mpz_ui_pow_ui(divisor, 10, comm.precision - quantity->prec); + mpz_mul(rquotient, MPZ(quantity), divisor); + mpz_ui_pow_ui(divisor, 10, comm.precision); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision; + } + else if (quantity->prec) { + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); + precision = quantity->prec; + } + else { + mpz_set(quotient, MPZ(quantity)); + mpz_set_ui(remainder, 0); + precision = 0; + } + + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { negative = true; - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); - if (full_precision || comm->precision == MAX_PRECISION) { - mpz_set(rquotient, remainder); + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); + + if (negative) + out << "-"; + + if (mpz_sgn(quotient) == 0) { + out << '0'; } else { - assert(MAX_PRECISION - comm->precision > 0); - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - comm->precision); - mpz_tdiv_qr(rquotient, remainder, remainder, divisor); + char * p = mpz_get_str(NULL, 10, quotient); + out << p; + std::free(p); } - std::ostringstream s; + if (precision) { + out << '.'; + + out.width(precision); + out.fill('0'); - if (comm && comm->prefix) { - s << comm->symbol; - if (comm->separate) - s << " "; + char * p = mpz_get_str(NULL, 10, rquotient); + out << p; + std::free(p); } - if (negative) - s << "-"; + mpz_clear(quotient); + mpz_clear(rquotient); + mpz_clear(remainder); + + return out.str(); +} + +std::ostream& operator<<(std::ostream& _out, const amount_t& amt) +{ + if (! amt.quantity) { + _out << "0"; + return _out; + } + + amount_t base(amt); + if (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; + } + } + + std::ostringstream out; - if (mpz_sgn(quotient) == 0) - s << '0'; - else if (! comm || ! comm->thousands) - s << quotient; + 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 short precision; + + if (comm == *commodity_t::null_commodity || + comm.flags & COMMODITY_STYLE_VARIABLE) { + 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 { - bool printed = false; + mpz_set(quotient, MPZ(base.quantity)); + mpz_set_ui(remainder, 0); + precision = 0; + } - // 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); + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + negative = true; - if (mpz_sgn(temp) == 0) - continue; + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); - mpz_ui_pow_ui(divisor, 10, 3); - mpz_tdiv_r(temp, temp, divisor); + if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { + _out << "0"; + return _out; + } - if (printed) { - s.width(3); - s.fill('0'); - } - s << temp; + if (! (comm.flags & COMMODITY_STYLE_SUFFIXED)) { + if (comm.quote) + out << "\"" << comm.symbol << "\""; + else + out << comm.symbol; + 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) { - s << (comm && comm->european ? '.' : ','); - printed = true; + 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); } - } - s << (comm && comm->european ? ',' : '.'); + bool printed = false; - 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); + 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; - int width = std::strlen(buf); - char * p = buf + (width - 1); + printed = true; + } + } - width = MAX_PRECISION - width; + if (precision) { + out << ((comm.flags & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - if (comm) { - while (p >= buf && *p == '0' && - (p - buf) >= (comm->precision - width)) - p--; - *(p + 1) = '\0'; - } + out.width(precision); + out.fill('0'); - s.width(width + std::strlen(buf)); - s.fill('0'); - s << buf; + char * p = mpz_get_str(NULL, 10, rquotient); + out << p; + std::free(p); } - if (comm && ! comm->prefix) { - if (comm->separate) - s << " "; - s << comm->symbol; + if (comm.flags & COMMODITY_STYLE_SUFFIXED) { + if (comm.flags & COMMODITY_STYLE_SEPARATED) + out << " "; + if (comm.quote) + out << "\"" << comm.symbol << "\""; + else + out << comm.symbol; } - mpz_clear(temp); mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); - mpz_clear(divisor); - return s.str(); + // 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; } -const std::string gmp_amount::as_str(bool full_prec) const +void parse_quantity(std::istream& in, std::string& value) { - std::ostringstream s; - - s << amount_to_str(quantity_comm, quantity, full_prec); + char buf[256]; + char c = peek_next_nonws(in); + READ_INTO(in, buf, 255, c, + std::isdigit(c) || c == '-' || c == '.' || c == ','); + value = buf; +} - if (priced) { - s << " @ "; - s << amount_to_str(price_comm, price, full_prec); +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 != '.'); } - return s.str(); + symbol = buf; } -static void parse_number(mpz_t out, const std::string& number, - commodity * comm) +void amount_t::parse(std::istream& in, unsigned short flags) { - const char * num = number.c_str(); + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] - if (char * p = std::strchr(num, '/')) { - mpz_t numer; - mpz_t val; + std::string symbol; + std::string quant; + unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS;; - // 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 c = peek_next_nonws(in); + if (std::isdigit(c) || c == '.' || c == '-') { + parse_quantity(in, quant); - std::string numer_str(num, p - num); - mpz_init_set_str(numer, numer_str.c_str(), 10); - mpz_init(val); + char n; + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(n)) + comm_flags |= COMMODITY_STYLE_SEPARATED; - int missing = MAX_PRECISION - (std::strlen(++p) - 1); - assert(missing > 0); - mpz_ui_pow_ui(val, 10, missing); + parse_commodity(in, symbol); - mpz_mul(out, numer, val); + comm_flags |= COMMODITY_STYLE_SUFFIXED; + } + } else { + parse_commodity(in, symbol); - mpz_clear(numer); - mpz_clear(val); + if (std::isspace(in.peek())) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + parse_quantity(in, quant); + } + + if (quant.empty()) + throw amount_error("No quantity specified for amount"); + + _init(); + + std::string::size_type last_comma = quant.rfind(','); + std::string::size_type last_period = quant.rfind('.'); + + if (last_comma != std::string::npos && last_period != std::string::npos) { + comm_flags |= COMMODITY_STYLE_THOUSANDS; + if (last_comma > last_period) { + comm_flags |= COMMODITY_STYLE_EUROPEAN; + quantity->prec = quant.length() - last_comma - 1; + } else { + quantity->prec = quant.length() - last_period - 1; + } + } + else if (last_comma != std::string::npos) { + comm_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; } else { - static char buf[256]; + quantity->prec = 0; + } - // The number is specified as the user desires, with the commodity - // telling us how to parse it. + // Create the commodity if has not already been seen, and update the + // precision if something greater was used for the quantity. - std::memset(buf, '0', 255); - std::strncpy(buf, num, std::strlen(num)); + commodity_ = commodity_t::find_commodity(symbol, true); + if (! (flags & AMOUNT_PARSE_NO_MIGRATE)) { + commodity_->flags |= comm_flags; + if (quantity->prec > commodity_->precision) + commodity_->precision = quantity->prec; + } - if (comm && comm->thousands) - while (char * t = std::strchr(buf, comm->european ? '.' : ',')) - do { *t = *(t + 1); } while (*(t++ + 1)); + // Now we have the final number. Remove commas and periods, if + // necessary. - char * t = std::strchr(buf, (comm && comm->european) ? ',' : '.'); - if (! t) - t = buf + std::strlen(num); + if (last_comma != std::string::npos || last_period != std::string::npos) { + int len = quant.length(); + char * buf = new char[len + 1]; + const char * p = quant.c_str(); + char * t = buf; - for (int prec = 0; prec < MAX_PRECISION; prec++) { - *t = *(t + 1); - t++; + while (*p) { + if (*p == ',' || *p == '.') + p++; + *t++ = *p++; } *t = '\0'; - mpz_set_str(out, buf, 10); + mpz_set_str(MPZ(quantity), buf, 10); + delete[] buf; + } else { + mpz_set_str(MPZ(quantity), quant.c_str(), 10); } + + if (! (flags & AMOUNT_PARSE_NO_REDUCE)) + reduce(); } -static commodity * parse_amount(mpz_t out, const char * num, - int matched, int * ovector, int base) +void amount_t::reduce() { - static char buf[256]; + while (commodity_ && commodity_->smaller) { + *this *= *commodity_->smaller; + commodity_ = commodity_->smaller->commodity_; + } +} - bool saw_commodity = false; - bool prefix = false; - bool separate = true; - bool thousands = true; - bool european = false; +void amount_t::parse(const std::string& str, unsigned short flags) +{ + std::istringstream stream(str); + parse(stream, flags); +} - std::string symbol; - int precision, result; +void parse_conversion(const std::string& larger_str, + const std::string& smaller_str) +{ + amount_t larger, smaller; - 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; - } + larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE); + smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE); - // 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); + larger *= smaller; - // Where "thousands" markers used? Is it a european number? - if (char * p = std::strrchr(buf, ',')) { - if (std::strchr(p, '.')) - thousands = true; - else - european = true; + if (larger.commodity()) { + larger.commodity().smaller = new amount_t(smaller); + larger.commodity().flags = smaller.commodity().flags; } + if (smaller.commodity()) + smaller.commodity().larger = new amount_t(larger); +} - // 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; - // 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; +char * bigints; +char * bigints_next; +unsigned int bigints_index; +unsigned int bigints_count; + +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); - parse_number(out, value_str.c_str(), comm); + quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); + quantity->ref++; + } +} - return comm; +static char buf[4096]; + +void amount_t::read_quantity(std::istream& in) +{ + char byte; + in.read(&byte, sizeof(byte)); + + if (byte == 0) { + quantity = NULL; + } + else if (byte == 1) { + quantity = new bigint_t; + + unsigned short len; + in.read((char *)&len, sizeof(len)); + assert(len < 4096); + in.read(buf, len); + mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short), + 0, 0, buf); + + char negative; + in.read(&negative, sizeof(negative)); + if (negative) + mpz_neg(MPZ(quantity), MPZ(quantity)); + + in.read((char *)&quantity->prec, sizeof(quantity->prec)); + } + else { + assert(0); + } } -void gmp_amount::parse(const std::string& number) +void amount_t::write_quantity(std::ostream& out) const { - // 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); + char byte; + + if (! quantity) { + byte = 0; + out.write(&byte, sizeof(byte)); + return; } - int ovector[60]; - int matched; + if (quantity->index == 0) { + quantity->index = ++bigints_index; + bigints_count++; - 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); + byte = 1; + out.write(&byte, sizeof(byte)); - // 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); + 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 { - std::cerr << "Failed to parse amount: " << number << std::endl; + 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)); } } -void gmp_amount::credit(const amount * value) +bool amount_t::valid() const { - const gmp_amount * val = dynamic_cast<const gmp_amount *>(value); - assert(quantity_comm == val->quantity_comm); - mpz_add(quantity, quantity, val->quantity); + if (quantity) { + if (! commodity_) + return false; + + if (quantity->ref == 0) + return false; + } + else if (commodity_) { + return false; + } + + return true; +} + + +void commodity_t::add_price(const std::time_t date, const amount_t& price) +{ + if (! history) + history = new history_t; + + history_map::iterator i = history->prices.find(date); + if (i != history->prices.end()) { + (*i).second = price; + } else { + std::pair<history_map::iterator, bool> result + = history->prices.insert(history_pair(date, price)); + assert(result.second); + } +} + +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; + } + + return NULL; +} + +amount_t commodity_t::value(const std::time_t moment) +{ + std::time_t age = 0; + amount_t price; + + if (! history) + return price; + + for (history_map::reverse_iterator i = history->prices.rbegin(); + i != history->prices.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->prices.size() > 0 ? + (*history->prices.rbegin()).first : 0), price); + + return price; } } // namespace ledger + +#ifdef USE_BOOST_PYTHON + +#include <boost/python.hpp> +#include <Python.h> + +using namespace boost::python; +using namespace ledger; + +void py_parse_1(amount_t& amount, const std::string& str, + unsigned short 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_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); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +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("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("quantity_string", &amount_t::quantity_string) + + .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)) + + .def("negate", &amount_t::negate) + .def("parse", py_parse_1) + .def("parse", py_parse_2) + .def("reduce", &amount_t::reduce) + .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; + scope().attr("COMMODITY_STYLE_VARIABLE") = COMMODITY_STYLE_VARIABLE; + + 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("ident", &commodity_t::ident) + .def_readwrite("updater", &commodity_t::updater) + .add_property("smaller", + make_getter(&commodity_t::smaller, + return_value_policy<reference_existing_object>())) + .add_property("larger", + make_getter(&commodity_t::larger, + return_value_policy<reference_existing_object>())) + + .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>()); + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(amount_error); +} + +#endif // USE_BOOST_PYTHON |