diff options
author | John Wiegley <johnw@newartisans.com> | 2003-09-30 03:22:38 +0000 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2003-09-30 03:22:38 +0000 |
commit | ef6161fefb09253b9de6d228c92ce41b3a0063dc (patch) | |
tree | 513811f9b3aad8617d8795b4715c8448fb26afab /amount.cc | |
parent | 7bf86bc48a564ffffa46461c15ae2ab34b258fe8 (diff) | |
download | fork-ledger-ef6161fefb09253b9de6d228c92ce41b3a0063dc.tar.gz fork-ledger-ef6161fefb09253b9de6d228c92ce41b3a0063dc.tar.bz2 fork-ledger-ef6161fefb09253b9de6d228c92ce41b3a0063dc.zip |
*** empty log message ***
Diffstat (limited to 'amount.cc')
-rw-r--r-- | amount.cc | 464 |
1 files changed, 255 insertions, 209 deletions
@@ -47,46 +47,27 @@ class gmp_amount : public amount return quantity_comm->symbol; } - virtual amount * copy() const { - gmp_amount * new_amt = new gmp_amount(); - new_amt->priced = priced; - mpz_set(new_amt->price, price); - new_amt->price_comm = price_comm; - mpz_set(new_amt->quantity, quantity); - new_amt->quantity_comm = quantity_comm; - return new_amt; - } - - virtual amount * value() const { - if (! priced) { - return copy(); - } else { - gmp_amount * new_amt = new gmp_amount(); - new_amt->priced = false; - multiply(new_amt->quantity, quantity, price); - new_amt->quantity_comm = price_comm; - return new_amt; - } - } + virtual amount * copy() const; + virtual amount * value() const; virtual operator bool() const; virtual void credit(const amount * other) { *this += *other; } + virtual void negate() { + mpz_ui_sub(quantity, 0, quantity); + } virtual void operator+=(const amount& other); virtual void parse(const char * num) { *this = num; } virtual amount& operator=(const char * num); - virtual operator std::string() const; - - static const std::string to_str(const commodity * comm, const mpz_t val); - - static void parse(mpz_t out, char * num); - static void round(mpz_t out, const mpz_t val, int prec); - static void multiply(mpz_t out, const mpz_t l, const mpz_t r); + virtual std::string as_str(bool full_prec) const; + virtual operator std::string() const { + return as_str(false); + } friend amount * create_amount(const char * value, const amount * price); }; @@ -141,46 +122,129 @@ amount * create_amount(const char * value, const amount * price) return a; } +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_mul_ui(divisor, divisor, 5); + if (mpz_cmp(remainder, divisor) >= 0) { + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); + mpz_sub(remainder, divisor, remainder); + mpz_add(out, val, remainder); + } else { + mpz_sub(out, val, remainder); + } + + mpz_clear(divisor); + mpz_clear(quotient); + mpz_clear(remainder); +} + +static void multiply(mpz_t out, const mpz_t l, const mpz_t r) +{ + mpz_t divisor; + + mpz_init(divisor); + + mpz_mul(out, l, r); + + // The number is at double-precision right now, so rounding at + // precision 0 effectively means rounding to the ordinary + // precision. + round(out, out, 0); + + // after multiplying, truncate to the correct precision + mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); + mpz_tdiv_q(out, out, divisor); + + mpz_clear(divisor); +} + +amount * gmp_amount::copy() const +{ + gmp_amount * new_amt = new gmp_amount(); +#if 0 + // Don't copy the price + new_amt->priced = priced; + mpz_set(new_amt->price, price); + new_amt->price_comm = price_comm; +#endif + mpz_set(new_amt->quantity, quantity); + new_amt->quantity_comm = quantity_comm; + return new_amt; +} + +amount * gmp_amount::value() const +{ + if (! priced) { + return copy(); + } else { + gmp_amount * new_amt = new gmp_amount(); + new_amt->priced = false; + multiply(new_amt->quantity, quantity, price); + new_amt->quantity_comm = price_comm; + return new_amt; + } +} + gmp_amount::operator bool() const { mpz_t copy; mpz_init_set(copy, quantity); assert(quantity_comm); - gmp_amount::round(copy, copy, quantity_comm->precision); + if (quantity_comm->precision < MAX_PRECISION) + round(copy, copy, quantity_comm->precision); bool zero = mpz_sgn(copy) == 0; mpz_clear(copy); return ! zero; } -const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) +static std::string amount_to_str(const commodity * comm, const mpz_t val, + bool full_precision) { - mpz_t copy; + mpz_t temp; mpz_t quotient; mpz_t rquotient; mpz_t remainder; mpz_t divisor; + bool negative = false; - mpz_init_set(copy, val); + mpz_init_set(temp, val); mpz_init(quotient); mpz_init(rquotient); mpz_init(remainder); mpz_init(divisor); - gmp_amount::round(copy, copy, comm->precision); + if (! full_precision && comm->precision < MAX_PRECISION) + round(temp, temp, comm->precision); mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_qr(quotient, remainder, copy, divisor); + mpz_tdiv_qr(quotient, remainder, temp, divisor); if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) negative = true; mpz_abs(quotient, quotient); mpz_abs(remainder, remainder); - assert(MAX_PRECISION - comm->precision > 0); - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - comm->precision); - mpz_tdiv_qr(rquotient, remainder, remainder, divisor); + 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); + } std::ostringstream s; @@ -192,10 +256,51 @@ const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) if (negative) s << "-"; - s << quotient; - s << '.'; - s.width(comm->precision); + if (comm->thousands) { + // jww (2003-09-29): use a smarter starting value + + bool printed = false; + + for (int powers = 27; powers >= 0; powers -= 3) { + mpz_ui_pow_ui(divisor, 10, powers); + mpz_tdiv_q(temp, quotient, divisor); + + if (mpz_sgn(temp) == 0) + continue; + + mpz_ui_pow_ui(divisor, 10, 3); + mpz_tdiv_r(temp, temp, divisor); + + if (printed) { + s.width(3); + s.fill('0'); + } + s << temp; + + if (powers > 0) { + if (comm->european) + s << "."; + else + s << ","; + + printed = true; + } + } + } else { + s << quotient; + } + + if (comm->european) + s << ','; + else + s << '.'; + + if (full_precision) + s.width(MAX_PRECISION); + else + s.width(comm->precision); + s.fill('0'); s << rquotient; @@ -205,7 +310,7 @@ const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) s << comm->symbol; } - mpz_clear(copy); + mpz_clear(temp); mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); @@ -214,26 +319,30 @@ const std::string gmp_amount::to_str(const commodity * comm, const mpz_t val) return s.str(); } -gmp_amount::operator std::string() const +std::string gmp_amount::as_str(bool full_prec) const { std::ostringstream s; assert(quantity_comm); - s << to_str(quantity_comm, quantity); + s << amount_to_str(quantity_comm, quantity, full_prec); if (priced) { assert(price_comm); - s << " @ " << to_str(price_comm, price); + s << " @ " << amount_to_str(price_comm, price, full_prec); } return s.str(); } -void gmp_amount::parse(mpz_t out, char * num) +static void parse_number(mpz_t out, const char * num, commodity * comm) { if (char * p = std::strchr(num, '/')) { mpz_t numer; mpz_t val; + // 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. + std::string numer_str(num, p - num); mpz_init_set_str(numer, numer_str.c_str(), 10); mpz_init(val); @@ -250,12 +359,20 @@ void gmp_amount::parse(mpz_t out, char * num) else { static char buf[256]; - // jww (2003-09-28): What if there is no decimal? + // The number is specified as the user desires, with the + // commodity telling us how to parse it. std::memset(buf, '0', 255); std::strncpy(buf, num, std::strlen(num)); - char * t = std::strchr(buf, '.'); + if (comm->thousands) + while (char * t = std::strchr(buf, comm->european ? '.' : ',')) + do { *t = *(t + 1); } while (*(t++ + 1)); + + char * t = std::strchr(buf, comm->european ? ',' : '.'); + if (! t) + t = buf + std::strlen(num); + for (int prec = 0; prec < MAX_PRECISION; prec++) { *t = *(t + 1); t++; @@ -266,6 +383,93 @@ void gmp_amount::parse(mpz_t out, char * num) } } +static commodity * parse_amount(mpz_t out, const char * num, + int matched, int * ovector, int base) +{ + static char buf[256]; + + bool saw_commodity = false; + + std::string symbol; + bool prefix, separate, thousands, european; + int precision, result; + + 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; + } + + // 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); + + // Determine the precision used + if (char * p = std::strchr(buf, '.')) + precision = std::strlen(++p); + else if (char * p = std::strchr(buf, '/')) + precision = std::strlen(++p) - 1; + else + precision = 0; + + // Where "thousands" markers used? Is it a european number? + if (char * p = std::strrchr(buf, ',')) { + if (std::strchr(p, '.')) + thousands = true; + else + european = true; + } + + // 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) { + std::cerr << "Error: No commodity specified: " << value_str + << std::endl; + std::exit(1); + } else { + commodities_iterator item = commodities.find(symbol.c_str()); + if (item == commodities.end()) { + comm = new commodity(symbol, prefix, separate, + thousands, european, precision); + } else { + comm = (*item).second; +#if 0 + // If a finer precision was used than the commodity allows, + // increase the precision. + if (precision > comm->precision) + comm->precision = precision; +#else + if (use_warnings && precision > comm->precision) + std::cerr << "Warning: Use of higher precision than expected: " + << value_str << std::endl; +#endif + } + } + assert(comm); + + parse_number(out, value_str.c_str(), comm); + + return comm; +} + amount& gmp_amount::operator=(const char * num) { // Compile the regular expression used for parsing amounts @@ -274,133 +478,23 @@ amount& gmp_amount::operator=(const char * num) const char *error; int erroffset; static const std::string amount_re = - "(([^-0-9/.]+)(\\s*))?([-0-9/.]+)((\\s*)([^-0-9/.@]+))?"; + "(([^-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); } - bool saw_commodity; - std::string symbol; - bool prefix; - bool separate; - int precision; - - static char buf[256]; int ovector[60]; - int matched, result; + int matched; matched = pcre_exec(re, NULL, num, std::strlen(num), 0, 0, ovector, 60); if (matched > 0) { - saw_commodity = false; - - if (ovector[1 * 2] >= 0) { - // A prefix symbol was found - saw_commodity = true; - prefix = true; - separate = ovector[3 * 2] != ovector[3 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 2, buf, 255); - assert(result >= 0); - symbol = buf; - } - - // This is the value, and must be present - assert(ovector[4 * 2] >= 0); - result = pcre_copy_substring(num, ovector, matched, 4, buf, 255); - assert(result >= 0); - - // Determine the precision used - if (char * p = std::strchr(buf, '.')) - precision = std::strlen(++p); - else if (char * p = std::strchr(buf, '/')) - precision = std::strlen(++p) - 1; - else - precision = 0; - - // Parse the actual quantity - parse(quantity, buf); - - if (ovector[5 * 2] >= 0) { - // A suffix symbol was found - saw_commodity = true; - prefix = false; - separate = ovector[6 * 2] != ovector[6 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 7, buf, 255); - assert(result >= 0); - symbol = buf; - } - - if (! saw_commodity) { - quantity_comm = commodity_usd; - } else { - commodities_iterator item = commodities.find(symbol.c_str()); - if (item == commodities.end()) { - quantity_comm = new commodity(symbol, prefix, separate, precision); - } else { - quantity_comm = (*item).second; - - // If a finer precision was used than the commodity allows, - // increase the precision. - if (precision > quantity_comm->precision) - quantity_comm->precision = precision; - } - } + quantity_comm = parse_amount(quantity, num, matched, ovector, 1); // If the following succeeded, then we have a price if (ovector[8 * 2] >= 0) { - saw_commodity = false; - - if (ovector[9 * 2] >= 0) { - // A prefix symbol was found - saw_commodity = true; - prefix = true; - separate = ovector[11 * 2] != ovector[11 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 10, buf, 255); - assert(result >= 0); - symbol = buf; - } - - assert(ovector[12 * 2] >= 0); - result = pcre_copy_substring(num, ovector, matched, 4, buf, 255); - assert(result >= 0); - - // Determine the precision used - if (char * p = std::strchr(buf, '.')) - precision = std::strlen(++p); - else if (char * p = std::strchr(buf, '/')) - precision = std::strlen(++p) - 1; - else - precision = 0; - - // Parse the actual price - parse(price, buf); priced = true; - - if (ovector[13 * 2] >= 0) { - // A suffix symbol was found - saw_commodity = true; - prefix = false; - separate = ovector[14 * 2] != ovector[14 * 2 + 1]; - result = pcre_copy_substring(num, ovector, matched, 15, buf, 255); - assert(result >= 0); - symbol = buf; - } - - if (! saw_commodity) { - price_comm = commodity_usd; - } else { - commodities_iterator item = commodities.find(symbol.c_str()); - if (item == commodities.end()) { - price_comm = new commodity(symbol, prefix, separate, precision); - } else { - price_comm = (*item).second; - - // If a finer precision was used than the commodity allows, - // increase the precision. - if (precision > price_comm->precision) - price_comm->precision = precision; - } - } + price_comm = parse_amount(price, num, matched, ovector, 9); } } else { std::cerr << "Failed to parse amount: " << num << std::endl; @@ -415,52 +509,4 @@ void gmp_amount::operator+=(const amount& _other) mpz_add(quantity, quantity, other.quantity); } -void gmp_amount::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_mul_ui(divisor, divisor, 5); - if (mpz_cmp(remainder, divisor) >= 0) { - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION - prec); - mpz_sub(remainder, divisor, remainder); - mpz_add(out, val, remainder); - } else { - mpz_sub(out, val, remainder); - } - - mpz_clear(divisor); - mpz_clear(quotient); - mpz_clear(remainder); -} - -void gmp_amount::multiply(mpz_t out, const mpz_t l, const mpz_t r) -{ - mpz_t divisor; - - mpz_init(divisor); - - mpz_mul(out, l, r); - - // The number is at double-precision right now, so rounding at - // precision 0 effectively means rounding to the ordinary - // precision. - gmp_amount::round(out, out, 0); - - // after multiplying, truncate to the correct precision - mpz_ui_pow_ui(divisor, 10, MAX_PRECISION); - mpz_tdiv_q(out, out, divisor); - - mpz_clear(divisor); -} - } // namespace ledger |