diff options
author | John Wiegley <johnw@newartisans.com> | 2004-07-26 23:33:51 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2004-07-26 23:33:51 -0400 |
commit | 161d6f79bd6f4ab45afa1cbae77548c8e508809a (patch) | |
tree | 55391f4997e20de173579d90b43316a968b27c9e /amount.cc | |
parent | fde56d0f1214b8fb9de5ba4d42d683ed494c45b0 (diff) | |
download | fork-ledger-161d6f79bd6f4ab45afa1cbae77548c8e508809a.tar.gz fork-ledger-161d6f79bd6f4ab45afa1cbae77548c8e508809a.tar.bz2 fork-ledger-161d6f79bd6f4ab45afa1cbae77548c8e508809a.zip |
initial rev of 2.0
Diffstat (limited to 'amount.cc')
-rw-r--r-- | amount.cc | 942 |
1 files changed, 520 insertions, 422 deletions
@@ -1,99 +1,21 @@ #include "ledger.h" -#include <sstream> -#include <cstdio> -#include <gmp.h> // GNU multi-precision library +#include "gmp.h" -namespace ledger { - -#define MAX_PRECISION 10 // must be 2 or higher - -////////////////////////////////////////////////////////////////////// -// -// 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 gmp_amount : public amount -{ - bool priced; - - mpz_t price; - commodity * price_comm; - - mpz_t quantity; - commodity * quantity_comm; - - gmp_amount(const gmp_amount& other) {} - gmp_amount& operator=(const gmp_amount& other) { return *this; } - - public: - gmp_amount() : priced(false), price_comm(NULL), quantity_comm(NULL) { - mpz_init(price); - mpz_init(quantity); - } - - virtual ~gmp_amount() { - mpz_clear(price); - mpz_clear(quantity); - } - - virtual commodity * commdty() const { - return quantity_comm; - } - - virtual void set_commdty(commodity * comm) { - quantity_comm = comm; - } +#define MAX_PRECISION 10 - 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; +#define MPZ(x) ((MP_INT *)(x)) - 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; - - virtual void negate() { - mpz_ui_sub(quantity, 0, quantity); - } - virtual void credit(const amount * other); +#define INIT() if (! quantity) _init() - 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); -}; +namespace ledger { -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; -} +commodity_t * amount_t::null_commodity = NULL; -static void round(mpz_t out, const mpz_t val, int prec) +static void mpz_round(mpz_t value, int precision) { mpz_t divisor; + mpz_t quotient; mpz_t remainder; @@ -101,29 +23,29 @@ static void round(mpz_t out, const mpz_t val, int prec) 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, MAX_PRECISION - precision); + mpz_tdiv_qr(quotient, remainder, value, divisor); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision - 1); mpz_mul_ui(divisor, divisor, 5); if (mpz_sgn(remainder) < 0) { mpz_ui_sub(divisor, 0, divisor); if (mpz_cmp(remainder, divisor) < 0) { - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision); mpz_add(remainder, divisor, remainder); mpz_ui_sub(remainder, 0, remainder); - mpz_add(out, val, remainder); + mpz_add(value, value, remainder); } else { - mpz_sub(out, val, remainder); + mpz_sub(value, value, remainder); } } else { if (mpz_cmp(remainder, divisor) >= 0) { - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - precision); mpz_sub(remainder, divisor, remainder); - mpz_add(out, val, remainder); + mpz_add(value, value, remainder); } else { - mpz_sub(out, val, remainder); + mpz_sub(value, value, remainder); } } @@ -132,247 +54,391 @@ static void round(mpz_t out, const mpz_t val, int prec) mpz_clear(remainder); } -static void multiply(mpz_t out, const mpz_t l, const mpz_t r) +// destructor +void amount_t::_clear() { - mpz_t divisor; - - mpz_init(divisor); + mpz_clear(MPZ(quantity)); + delete (MP_INT *) quantity; +} - mpz_mul(out, l, r); +void amount_t::_init() +{ + quantity = new MP_INT; + mpz_init(MPZ(quantity)); +} - // 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::_copy(const amount_t& amt) +{ + if (quantity) { + mpz_set(MPZ(quantity), MPZ(amt.quantity)); + } else { + quantity = new MP_INT; + mpz_init_set(MPZ(quantity), MPZ(amt.quantity)); + } + commodity = amt.commodity; + assert(commodity); +} - // after multiplying, truncate to the correct precision - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_q(out, out, divisor); +amount_t& amount_t::operator=(const std::string& value) +{ + std::istringstream str(value); + parse(str); + return *this; +} - mpz_clear(divisor); +// assignment operator +amount_t& amount_t::operator=(const amount_t& amt) +{ + if (amt.quantity) + _copy(amt); + return *this; } -amount * gmp_amount::copy() const +amount_t& amount_t::operator=(const int value) { - gmp_amount * new_amt = new gmp_amount(); + if (value == 0) { + if (quantity) { + _clear(); + quantity = NULL; + commodity = NULL; + } + } else { + std::string str; + std::ostringstream strstr(str); + strstr << value; + parse(strstr.str()); + } + return *this; +} - mpz_set(new_amt->quantity, quantity); - new_amt->quantity_comm = quantity_comm; +amount_t& amount_t::operator=(const unsigned int value) +{ + if (value == 0) { + if (quantity) { + _clear(); + quantity = NULL; + commodity = NULL; + } + } else { + std::string str; + std::ostringstream strstr(str); + strstr << value; + parse(strstr.str()); + } + return *this; +} - return new_amt; +amount_t& amount_t::operator=(const double value) +{ + if (value == 0.0) { + if (quantity) { + _clear(); + quantity = NULL; + commodity = NULL; + } + } else { + std::string str; + std::ostringstream strstr(str); + strstr << value; + parse(strstr.str()); + } + return *this; } -amount * gmp_amount::per_item_price() const + +amount_t& amount_t::operator+=(const amount_t& amt) { - if (! priced) - return NULL; + if (amt.quantity) { + assert(commodity == amt.commodity); + INIT(); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + return *this; +} - gmp_amount * new_amt = new gmp_amount(); +amount_t& amount_t::operator-=(const amount_t& amt) +{ + if (amt.quantity) { + assert(commodity == amt.commodity); + INIT(); + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + return *this; +} - mpz_set(new_amt->quantity, price); - new_amt->quantity_comm = price_comm; +// unary negation +amount_t& amount_t::negate() +{ + if (quantity) + mpz_ui_sub(MPZ(quantity), 0, MPZ(quantity)); + return *this; +} - return new_amt; +// comparisons to zero +bool amount_t::operator<(const int num) const +{ + if (num == 0) { + return quantity ? mpz_sgn(MPZ(quantity)) < 0 : false; + } else { + std::string str; + std::ostringstream strstr(str); + strstr << num; + amount_t amt(strstr.str()); + return *this < amt; + } } -amount * gmp_amount::value(const amount * pr) const +bool amount_t::operator<=(const int num) const { - if (pr) { - const gmp_amount * p = dynamic_cast<const gmp_amount *>(pr); - assert(p); + if (num == 0) { + return quantity ? mpz_sgn(MPZ(quantity)) <= 0 : true; + } else { + std::string str; + std::ostringstream strstr(str); + strstr << num; + amount_t amt(strstr.str()); + return *this <= amt; + } +} - gmp_amount * new_amt = new gmp_amount(); +bool amount_t::operator>(const int num) const +{ + if (num == 0) { + return quantity ? mpz_sgn(MPZ(quantity)) > 0 : false; + } else { + std::string str; + std::ostringstream strstr(str); + strstr << num; + amount_t amt(strstr.str()); + return *this > amt; + } +} - multiply(new_amt->quantity, quantity, p->quantity); +bool amount_t::operator>=(const int num) const +{ + if (num == 0) { + return quantity ? mpz_sgn(MPZ(quantity)) >= 0 : true; + } else { + std::string str; + std::ostringstream strstr(str); + strstr << num; + amount_t amt(strstr.str()); + return *this >= amt; + } +} - // 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; +// comparisons between amounts +bool amount_t::operator<(const amount_t& amt) const +{ + if (! quantity) // equivalent to zero + return amt > 0; + if (! amt.quantity) // equivalent to zero + return *this < 0; + assert(commodity == amt.commodity); + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)) < 0; +} - if (new_amt->quantity_comm && - new_amt->quantity_comm->precision < MAX_PRECISION) - round(new_amt->quantity, new_amt->quantity, - new_amt->quantity_comm->precision); +bool amount_t::operator<=(const amount_t& amt) const +{ + if (! quantity) // equivalent to zero + return amt >= 0; + if (! amt.quantity) // equivalent to zero + return *this <= 0; + assert(commodity == amt.commodity); + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)) <= 0; +} - return new_amt; - } - else if (! priced) { - return copy(); - } - else { - gmp_amount * new_amt = new gmp_amount(); +bool amount_t::operator>(const amount_t& amt) const +{ + if (! quantity) // equivalent to zero + return amt < 0; + if (! amt.quantity) // equivalent to zero + return *this > 0; + assert(commodity == amt.commodity); + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)) > 0; +} - multiply(new_amt->quantity, quantity, price); +bool amount_t::operator>=(const amount_t& amt) const +{ + if (! quantity) // equivalent to zero + return amt <= 0; + if (! amt.quantity) // equivalent to zero + return *this >= 0; + assert(commodity == amt.commodity); + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)) >= 0; +} - 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); +bool amount_t::operator==(const amount_t& amt) const +{ + if (commodity != amt.commodity) + return false; + assert(amt.quantity); + assert(quantity); + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)) == 0; +} - return new_amt; +amount_t::operator bool() const +{ + if (quantity) { + assert(commodity); + return mpz_sgn(MPZ(round().quantity)) != 0; + } else { + return false; } } -amount * gmp_amount::street(std::time_t * when, - bool use_history, bool download) const +amount_t amount_t::value(const std::time_t moment) const { - static std::time_t now = std::time(NULL); - if (! when) - when = &now; + if (quantity && ! (commodity->flags & COMMODITY_STYLE_NOMARKET)) + if (amount_t amt = commodity->value(moment)) + return (amt * *this).round(); - amount * amt = copy(); - - if (! amt->commdty()) - return amt; + return *this; +} - int max = 10; - while (--max >= 0) { - amount * price = amt->commdty()->price(when, use_history, download); - if (! price) - break; +amount_t& amount_t::operator*=(const amount_t& amt) +{ + if (! amt.quantity) + return *this; - amount * old = amt; - amt = amt->value(price); + INIT(); - if (amt->commdty() == old->commdty()) { - delete old; - break; - } + mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - delete old; - } + // Truncate to the recorded precision: MAX_PRECISION. + mpz_t divisor; + mpz_init(divisor); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); + mpz_tdiv_q(MPZ(quantity), MPZ(quantity), divisor); + mpz_clear(divisor); - 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) + return *this; - const gmp_amount * v = dynamic_cast<const gmp_amount *>(val); - assert(v); + INIT(); - mpz_t quotient; - mpz_t remainder; - mpz_t addend; - - mpz_init(quotient); - mpz_init(remainder); - mpz_init(addend); - - mpz_ui_pow_ui(addend, 10, MAX_PRECISION); + mpz_t divisor; + mpz_init(divisor); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - 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); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - priced = true; - mpz_set(price, quotient); - price_comm = v->quantity_comm; + mpz_clear(divisor); - mpz_clear(quotient); - mpz_clear(remainder); - mpz_clear(addend); + return *this; } -bool gmp_amount::is_zero() const +amount_t& amount_t::operator%=(const amount_t& amt) { - 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 (! amt.quantity) + return *this; -bool gmp_amount::is_negative() const -{ - return mpz_sgn(quantity) < 0; + INIT(); + + mpz_t divisor; + mpz_init(divisor); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); + + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + mpz_tdiv_r(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + + mpz_clear(divisor); + + return *this; } -int gmp_amount::compare(const amount * other) const +amount_t amount_t::round(int precision) 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) { + return *this; + } else { + amount_t temp = *this; + mpz_round(MPZ(temp.quantity), + precision == -1 ? commodity->precision : precision); + return temp; + } } -static std::string amount_to_str(const commodity * comm, const mpz_t val, - bool full_precision) +std::ostream& operator<<(std::ostream& out, const amount_t& amt) { - mpz_t temp; mpz_t quotient; mpz_t rquotient; mpz_t remainder; mpz_t divisor; - bool negative = false; - - mpz_init_set(temp, val); + if (! amt.quantity) + return out; mpz_init(quotient); mpz_init(rquotient); mpz_init(remainder); mpz_init(divisor); - if (comm == NULL) - full_precision = true; - - if (! full_precision && comm->precision < MAX_PRECISION) - round(temp, temp, comm->precision); + bool negative = false; mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_qr(quotient, remainder, temp, divisor); + mpz_tdiv_qr(quotient, remainder, MPZ(amt.quantity), divisor); 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) { + if (amt.commodity->precision == MAX_PRECISION) { mpz_set(rquotient, remainder); } else { - assert(MAX_PRECISION - comm->precision > 0); - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - comm->precision); + // Ensure the value is rounded to the commodity's precision before + // outputting it + mpz_round(MPZ(amt.quantity), amt.commodity->precision); + + assert(MAX_PRECISION - amt.commodity->precision > 0); + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - amt.commodity->precision); mpz_tdiv_qr(rquotient, remainder, remainder, divisor); } - std::ostringstream s; + bool odd_chars_in_symbol = false; - if (comm && comm->prefix) { - s << comm->symbol; - if (comm->separate) - s << " "; + for (const char * p = amt.commodity->symbol.c_str(); + *p; + p++) + if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.') { + odd_chars_in_symbol = true; + break; + } + + if (! (amt.commodity->flags & COMMODITY_STYLE_SUFFIXED)) { + if (odd_chars_in_symbol) + out << "\"" << amt.commodity->symbol << "\""; + else + out << amt.commodity->symbol; + if (amt.commodity->flags & COMMODITY_STYLE_SEPARATED) + out << " "; } if (negative) - s << "-"; + out << "-"; - if (mpz_sgn(quotient) == 0) - s << '0'; - else if (! comm || ! comm->thousands) - s << quotient; + if (mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (! (amt.commodity->flags & COMMODITY_STYLE_THOUSANDS)) { + out << quotient; + } else { bool printed = false; + mpz_t temp; + mpz_init(temp); + // jww (2003-09-29): use a smarter starting value for `powers' - for (int powers = 27; powers >= 0; powers -= 3) { + for (int powers = 15; powers >= 0; powers -= 3) { mpz_ui_pow_ui(divisor, 10, powers); mpz_tdiv_q(temp, quotient, divisor); @@ -383,239 +449,271 @@ static std::string amount_to_str(const commodity * comm, const mpz_t val, mpz_tdiv_r(temp, temp, divisor); if (printed) { - s.width(3); - s.fill('0'); + out.width(3); + out.fill('0'); } - s << temp; + out << temp; if (powers > 0) { - s << (comm && comm->european ? '.' : ','); + out << ((amt.commodity->flags & COMMODITY_STYLE_EUROPEAN) ? '.' : ','); printed = true; } } - } - 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); - - width = MAX_PRECISION - width; + mpz_clear(temp); + } - if (comm) { - while (p >= buf && *p == '0' && - (p - buf) >= (comm->precision - width)) - p--; - *(p + 1) = '\0'; - } + out << ((amt.commodity->flags & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - s.width(width + std::strlen(buf)); - s.fill('0'); - s << buf; - } + out.width(amt.commodity->precision); + out.fill('0'); + out << rquotient; - if (comm && ! comm->prefix) { - if (comm->separate) - s << " "; - s << comm->symbol; + if (amt.commodity->flags & COMMODITY_STYLE_SUFFIXED) { + if (amt.commodity->flags & COMMODITY_STYLE_SEPARATED) + out << " "; + if (odd_chars_in_symbol) + out << "\"" << amt.commodity->symbol << "\""; + else + out << amt.commodity->symbol; } - mpz_clear(temp); mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); mpz_clear(divisor); - return s.str(); + return out; } -const std::string gmp_amount::as_str(bool full_prec) const +amount_t::operator std::string() const { std::ostringstream s; - - s << amount_to_str(quantity_comm, quantity, full_prec); - - if (priced) { - s << " @ "; - s << amount_to_str(price_comm, price, full_prec); - } + s << *this; return s.str(); } -static void parse_number(mpz_t out, const std::string& number, - commodity * comm) +static inline char peek_next_nonws(std::istream& in) { - const char * num = number.c_str(); - - if (char * p = std::strchr(num, '/')) { - mpz_t numer; - mpz_t val; + char c = in.peek(); + while (! in.eof() && std::isspace(c)) { + in.get(c); + c = in.peek(); + } + return c; +} - // 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. +void parse_quantity(std::istream& in, std::string& value) +{ + char c = peek_next_nonws(in); + while (std::isdigit(c) || c == '-' || c == '.' || c == ',') { + in.get(c); + if (in.eof()) + break; + value += c; + c = in.peek(); + } +} - std::string numer_str(num, p - num); - mpz_init_set_str(numer, numer_str.c_str(), 10); - mpz_init(val); +void parse_commodity(std::istream& in, std::string& symbol) +{ + char c = peek_next_nonws(in); + if (c == '"') { + in.get(c); + c = in.peek(); + while (! in.eof() && c != '"') { + in.get(c); + if (c == '\\') + in.get(c); + symbol += c; + c = in.peek(); + } - int missing = MAX_PRECISION - (std::strlen(++p) - 1); - assert(missing > 0); - mpz_ui_pow_ui(val, 10, missing); + if (c == '"') + in.get(c); + else + assert(0); + } else { + while (! std::isspace(c) && ! std::isdigit(c) && c != '-' && c != '.') { + in.get(c); + if (in.eof()) + break; + symbol += c; + c = in.peek(); + } + } +} - mpz_mul(out, numer, val); +void amount_t::parse(std::istream& in, ledger_t * ledger) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] - mpz_clear(numer); - mpz_clear(val); - } - else { - static char buf[256]; + std::string symbol; + std::string quant; + unsigned int flags = COMMODITY_STYLE_DEFAULTS;; + unsigned int precision = MAX_PRECISION; - // The number is specified as the user desires, with the commodity - // telling us how to parse it. + if (! quantity) + _init(); - std::memset(buf, '0', 255); - std::strncpy(buf, num, std::strlen(num)); + char c = peek_next_nonws(in); + if (std::isdigit(c) || c == '.' || c == '-') { + parse_quantity(in, quant); - if (comm && comm->thousands) - while (char * t = std::strchr(buf, comm->european ? '.' : ',')) - do { *t = *(t + 1); } while (*(t++ + 1)); + char n; + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(n)) + flags |= COMMODITY_STYLE_SEPARATED; - char * t = std::strchr(buf, (comm && comm->european) ? ',' : '.'); - if (! t) - t = buf + std::strlen(num); + parse_commodity(in, symbol); - for (int prec = 0; prec < MAX_PRECISION; prec++) { - *t = *(t + 1); - t++; + flags |= COMMODITY_STYLE_SUFFIXED; } - *t = '\0'; + } else { + parse_commodity(in, symbol); + + if (std::isspace(in.peek())) + flags |= COMMODITY_STYLE_SEPARATED; - mpz_set_str(out, buf, 10); + parse_quantity(in, quant); } -} -static commodity * parse_amount(mpz_t out, const char * num, - int matched, int * ovector, int base) -{ - static char buf[256]; + std::string::size_type last_comma = quant.rfind(','); + std::string::size_type last_period = quant.rfind('.'); - bool saw_commodity = false; - bool prefix = false; - bool separate = true; - bool thousands = true; - bool european = false; + if (last_comma != std::string::npos && last_period != std::string::npos) { + flags |= COMMODITY_STYLE_THOUSANDS; + if (last_comma > last_period) { + flags |= COMMODITY_STYLE_EUROPEAN; + precision = quant.length() - last_comma - 1; + } else { + precision = quant.length() - last_period - 1; + } + } + else if (last_comma != std::string::npos) { + flags |= COMMODITY_STYLE_EUROPEAN; + precision = quant.length() - last_comma - 1; + } + else if (last_period != std::string::npos) { + precision = quant.length() - last_period - 1; + } + else { + precision = 0; + quant = quant + ".0"; + } + assert(precision <= MAX_PRECISION); + + // Create the commodity if has not already been seen. + if (ledger) { + commodity = ledger->find_commodity(symbol, true); + commodity->flags |= flags; + if (precision > commodity->precision) + commodity->precision = precision; + } + else if (symbol.empty()) { + if (! null_commodity) { + commodity = null_commodity = new commodity_t(symbol, precision, flags); + } else { + commodity = null_commodity; + commodity->flags |= flags; + if (precision > commodity->precision) + commodity->precision = precision; + } + } + else { + commodity = new commodity_t(symbol, precision, flags); + } - std::string symbol; - int precision, result; + // The number is specified as the user desires, with the commodity + // flags telling how to parse it. - 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; - } + int len = quant.length(); + int buf_len = len + MAX_PRECISION; + char * buf = new char[buf_len]; - // 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); + std::memset(buf, '0', buf_len - 1); + std::strncpy(buf, quant.c_str(), len); - // 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 (flags & COMMODITY_STYLE_THOUSANDS) + while (char * t = + std::strchr(buf, flags & COMMODITY_STYLE_EUROPEAN ? '.' : ',')) + do { *t = *(t + 1); } while (*(t++ + 1)); - // 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; + char * t = std::strchr(buf, flags & COMMODITY_STYLE_EUROPEAN ? ',' : '.'); + if (! t) + t = buf + len; - // 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; + for (int prec = 0; prec < MAX_PRECISION; prec++) { + *t = *(t + 1); + t++; } + *t = '\0'; - parse_number(out, value_str.c_str(), comm); + mpz_set_str(MPZ(quantity), buf, 10); - return comm; + delete[] buf; } -void gmp_amount::parse(const std::string& number) +static char buf[4096]; + +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); + unsigned short len; + if (quantity) { + mpz_get_str(buf, 10, MPZ(quantity)); + len = std::strlen(buf); + assert(len); + out.write((char *)&len, sizeof(len)); + out.write(buf, len); + } else { + len = 0; + out.write((char *)&len, sizeof(len)); } +} - 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); - - // 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); - } +void amount_t::read_quantity(std::istream& in) +{ + unsigned short len; + in.read((char *)&len, sizeof(len)); + if (len) { + in.read(buf, len); + buf[len] = '\0'; + if (! quantity) + _init(); + mpz_set_str(MPZ(quantity), buf, 10); } else { - std::cerr << "Failed to parse amount: " << number << std::endl; + if (quantity) + _clear(); + quantity = NULL; } } -void gmp_amount::credit(const amount * value) +void (*commodity_t::updater)(commodity_t * commodity, + const std::time_t date, + const amount_t& price, + const std::time_t moment) = NULL; + +amount_t commodity_t::value(const std::time_t moment) { - const gmp_amount * val = dynamic_cast<const gmp_amount *>(value); - assert(quantity_comm == val->quantity_comm); - mpz_add(quantity, quantity, val->quantity); + std::time_t age = 0; + amount_t price; + + if (updater) + updater(this, age, price, moment); + + 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; + } + + return price; } } // namespace ledger |