diff options
Diffstat (limited to 'src/numerics/amount.cc')
-rw-r--r-- | src/numerics/amount.cc | 1395 |
1 files changed, 1395 insertions, 0 deletions
diff --git a/src/numerics/amount.cc b/src/numerics/amount.cc new file mode 100644 index 00000000..4c771cc7 --- /dev/null +++ b/src/numerics/amount.cc @@ -0,0 +1,1395 @@ +/* + * Copyright (c) 2003-2007, John Wiegley. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of New Artisans LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file amount.cc + * @author John Wiegley + * @date Thu Apr 26 15:19:46 2007 + * + * @brief Types for handling commoditized math. + * + * This file defines member functions for amount_t, and also defines a + * helper class, bigint_t, which is used as a refcounted wrapper + * around libgmp's mpz_t type. + */ + +#include "amount.h" +#include "binary.h" + +namespace ledger { + +commodity_pool_t * amount_t::current_pool = NULL; + +bool amount_t::keep_base = false; + +bool amount_t::keep_price = false; +bool amount_t::keep_date = false; +bool amount_t::keep_tag = false; + +bool amount_t::stream_fullstrings = false; + +#ifndef THREADSAFE +/** + * These global temporaries are pre-initialized for the sake of + * efficiency, and reused over and over again. + */ +static mpz_t temp; +static mpz_t divisor; +#endif + +struct amount_t::bigint_t : public supports_flags<> +{ +#define BIGINT_BULK_ALLOC 0x01 +#define BIGINT_KEEP_PREC 0x02 + + mpz_t val; + precision_t prec; + uint_least16_t ref; + uint_fast32_t index; + +#define MPZ(bigint) ((bigint)->val) + + bigint_t() : prec(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, ""); + mpz_init(val); + } + bigint_t(mpz_t _val) : prec(0), ref(1), index(0) { + TRACE_CTOR(bigint_t, "mpz_t"); + mpz_init_set(val, _val); + } + bigint_t(const bigint_t& other) + : supports_flags<>(other.flags() & BIGINT_KEEP_PREC), + prec(other.prec), ref(1), index(0) { + TRACE_CTOR(bigint_t, "copy"); + mpz_init_set(val, other.val); + } + ~bigint_t() { + TRACE_DTOR(bigint_t); + assert(ref == 0); + mpz_clear(val); + } +}; + +void amount_t::initialize() +{ + mpz_init(temp); + mpz_init(divisor); + + // jww (2007-05-02): Be very careful here! + if (! current_pool) + current_pool = new commodity_pool_t; + + // Add time commodity conversions, so that timelog's may be parsed + // in terms of seconds, but reported as minutes or hours. + if (commodity_t * commodity = current_pool->create("s")) { + commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); + + parse_conversion("1.0m", "60s"); + parse_conversion("1.0h", "60m"); + } else { + assert(false); + } +} + +void amount_t::shutdown() +{ + mpz_clear(temp); + mpz_clear(divisor); + + // jww (2007-05-02): Be very careful here! + if (current_pool) { + checked_delete(current_pool); + current_pool = NULL; + } +} + +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->has_flags(BIGINT_BULK_ALLOC)) { + quantity = new bigint_t(*amt.quantity); + } else { + quantity = amt.quantity; + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } + } + commodity_ = amt.commodity_; +} + +void amount_t::_dup() +{ + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; + } +} + +void amount_t::_resize(precision_t prec) +{ + assert(prec < 256); + + if (! quantity || prec == quantity->prec) + return; + + _dup(); + + assert(prec > quantity->prec); + mpz_ui_pow_ui(divisor, 10, prec - quantity->prec); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + + quantity->prec = prec; +} + +void amount_t::_clear() +{ + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; + } else { + assert(! commodity_); + } +} + +void amount_t::_release() +{ + DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); + + if (--quantity->ref == 0) { + if (quantity->has_flags(BIGINT_BULK_ALLOC)) + quantity->~bigint_t(); + else + checked_delete(quantity); + } +} + + +namespace { + amount_t::precision_t convert_double(mpz_t dest, double val) + { +#ifndef HAVE_GDTOA + // This code is far too imprecise to be worthwhile. + + mpf_t temp; + mpf_init_set_d(temp, val); + + mp_exp_t exp; + char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp); + + int len = std::strlen(buf); + if (len > 0 && buf[0] == '-') + exp++; + + if (exp <= len) { + exp = len - exp; + } else { + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = exp - len; + + char * newbuf = (char *)std::malloc(len + zeroes); + std::strcpy(newbuf, buf); + + int i; + for (i = 0; i < zeroes; i++) + newbuf[len + i] = '0'; + newbuf[len + i] = '\0'; + + free(buf); + buf = newbuf; + + exp = (len - exp) + zeroes; + } + + mpz_set_str(dest, buf, 10); + free(buf); + + return amount_t::precision_t(exp); +#else + int decpt, sign; + char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL); + char * result; + int len = std::strlen(buf); + + if (decpt <= len) { + decpt = len - decpt; + result = NULL; + } else { + // There were trailing zeros, which we have to put back on in + // order to convert this buffer into an integer. + + int zeroes = decpt - len; + result = new char[len + zeroes + 1]; + + std::strcpy(result, buf); + int i; + for (i = 0; i < zeroes; i++) + result[len + i] = '0'; + result[len + i] = '\0'; + + decpt = (len - decpt) + zeroes; + } + + if (sign) { + char * newbuf = new char[std::strlen(result ? result : buf) + 2]; + newbuf[0] = '-'; + std::strcpy(&newbuf[1], result ? result : buf); + mpz_set_str(dest, newbuf, 10); + checked_array_delete(newbuf); + } else { + mpz_set_str(dest, result ? result : buf, 10); + } + + if (result) + checked_array_delete(result); + freedtoa(buf); + + return decpt; +#endif + } +} + +amount_t::amount_t(const double val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const double"); + quantity = new bigint_t; + quantity->prec = convert_double(MPZ(quantity), val); +} + +amount_t::amount_t(const unsigned long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const unsigned long"); + quantity = new bigint_t; + mpz_set_ui(MPZ(quantity), val); +} + +amount_t::amount_t(const long val) : commodity_(NULL) +{ + TRACE_CTOR(amount_t, "const long"); + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), val); +} + + +amount_t& amount_t::operator=(const amount_t& amt) +{ + if (this != &amt) { + if (amt.quantity) + _copy(amt); + else if (quantity) + _clear(); + } + return *this; +} + + +int amount_t::compare(const amount_t& amt) const +{ + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot compare an amount to an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot compare an uninitialized amount to an amount"); + else + throw_(amount_error, "Cannot compare two uninitialized amounts"); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Cannot compare amounts with different commodities: " << + commodity().symbol() << " and " << amt.commodity().symbol()); + + if (quantity->prec == amt.quantity->prec) { + return mpz_cmp(MPZ(quantity), MPZ(amt.quantity)); + } + else if (quantity->prec < amt.quantity->prec) { + amount_t t(*this); + t._resize(amt.quantity->prec); + return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity)); + } + else { + amount_t t = amt; + t._resize(quantity->prec); + return mpz_cmp(MPZ(quantity), MPZ(t.quantity)); + } +} + + +amount_t& amount_t::operator+=(const amount_t& amt) +{ + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot add an amount to an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot add an uninitialized amount to an amount"); + else + throw_(amount_error, "Cannot add two uninitialized amounts"); + } + + if (commodity() != amt.commodity()) + throw_(amount_error, + "Adding amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _dup(); + + if (quantity->prec == amt.quantity->prec) { + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else if (quantity->prec < amt.quantity->prec) { + _resize(amt.quantity->prec); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else { + amount_t t = amt; + t._resize(quantity->prec); + mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); + } + + return *this; +} + +amount_t& amount_t::operator-=(const amount_t& amt) +{ + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot subtract an amount from an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot subtract an uninitialized amount from an amount"); + else + throw_(amount_error, "Cannot subtract two uninitialized amounts"); + } + + if (commodity() != amt.commodity()) + throw_(amount_error, + "Subtracting amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _dup(); + + if (quantity->prec == amt.quantity->prec) { + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else if (quantity->prec < amt.quantity->prec) { + _resize(amt.quantity->prec); + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + } + else { + amount_t t = amt; + t._resize(quantity->prec); + mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity)); + } + + return *this; +} + +namespace { + void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) + { + // Round `value', with an encoding precision of `value_prec', to a + // rounded value with precision `round_prec'. Result is stored in + // `out'. + + assert(value_prec > round_prec); + + mpz_t quotient; + mpz_t remainder; + + mpz_init(quotient); + mpz_init(remainder); + + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_tdiv_qr(quotient, remainder, value, divisor); + mpz_divexact_ui(divisor, divisor, 10); + mpz_mul_ui(divisor, divisor, 5); + + if (mpz_sgn(remainder) < 0) { + mpz_neg(divisor, divisor); + if (mpz_cmp(remainder, divisor) < 0) { + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_add(remainder, divisor, remainder); + mpz_ui_sub(remainder, 0, remainder); + mpz_add(out, value, remainder); + } else { + mpz_sub(out, value, remainder); + } + } else { + if (mpz_cmp(remainder, divisor) >= 0) { + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_sub(remainder, divisor, remainder); + mpz_add(out, value, remainder); + } else { + mpz_sub(out, value, remainder); + } + } + mpz_clear(quotient); + mpz_clear(remainder); + + // chop off the rounded bits + mpz_ui_pow_ui(divisor, 10, value_prec - round_prec); + mpz_tdiv_q(out, out, divisor); + } +} + +amount_t& amount_t::operator*=(const amount_t& amt) +{ + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot multiply an amount by an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot multiply an uninitialized amount by an amount"); + else + throw_(amount_error, "Cannot multiply two uninitialized amounts"); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Multiplying amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + _dup(); + + mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + quantity->prec += amt.quantity->prec; + + if (! has_commodity()) + commodity_ = amt.commodity_; + + if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { + precision_t comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } + } + + return *this; +} + +amount_t& amount_t::operator/=(const amount_t& amt) +{ + if (! quantity || ! amt.quantity) { + if (quantity) + throw_(amount_error, "Cannot divide an amount by an uninitialized amount"); + else if (amt.quantity) + throw_(amount_error, "Cannot divide an uninitialized amount by an amount"); + else + throw_(amount_error, "Cannot divide two uninitialized amounts"); + } + + if (has_commodity() && amt.has_commodity() && + commodity() != amt.commodity()) + throw_(amount_error, + "Dividing amounts with different commodities: " << + (has_commodity() ? commodity().symbol() : "NONE") << + " != " << + (amt.has_commodity() ? amt.commodity().symbol() : "NONE")); + + if (! amt) + throw_(amount_error, "Divide by zero"); + + _dup(); + + // Increase the value's precision, to capture fractional parts after + // the divide. Round up in the last position. + + mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U); + mpz_mul(MPZ(quantity), MPZ(quantity), divisor); + mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); + quantity->prec += amt.quantity->prec + quantity->prec + 7U; + + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1); + quantity->prec -= 1; + + if (! has_commodity()) + commodity_ = amt.commodity_; + + // If this amount has a commodity, and we're not dealing with plain + // numbers, or internal numbers (which keep full precision at all + // times), then round the number to within the commodity's precision + // plus six places. + + if (has_commodity() && ! (quantity->has_flags(BIGINT_KEEP_PREC))) { + precision_t comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } + } + + return *this; +} + + +amount_t::precision_t amount_t::precision() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine precision of an uninitialized amount"); + + return quantity->prec; +} + +amount_t& amount_t::in_place_negate() +{ + if (quantity) { + _dup(); + mpz_neg(MPZ(quantity), MPZ(quantity)); + } else { + throw_(amount_error, "Cannot negate an uninitialized amount"); + } + return *this; +} + +amount_t amount_t::round(const optional<precision_t>& prec) const +{ + if (! quantity) + throw_(amount_error, "Cannot round an uninitialized amount"); + + if (! prec) { + if (! has_commodity()) + return *this; + return round(commodity().precision()); + } else { + amount_t t(*this); + + if (quantity->prec <= *prec) { + if (quantity && quantity->has_flags(BIGINT_KEEP_PREC)) { + t._dup(); + t.quantity->drop_flags(BIGINT_KEEP_PREC); + } + return t; + } + + t._dup(); + + mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, *prec); + + t.quantity->prec = *prec; + t.quantity->drop_flags(BIGINT_KEEP_PREC); + + return t; + } +} + +amount_t amount_t::unround() const +{ + if (! quantity) + throw_(amount_error, "Cannot unround an uninitialized amount"); + else if (quantity->has_flags(BIGINT_KEEP_PREC)) + return *this; + + amount_t t(*this); + t._dup(); + t.quantity->add_flags(BIGINT_KEEP_PREC); + + return t; +} + +amount_t& amount_t::in_place_reduce() +{ + if (! quantity) + throw_(amount_error, "Cannot reduce an uninitialized amount"); + + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; + } + return *this; +} + +amount_t& amount_t::in_place_unreduce() +{ + if (! quantity) + throw_(amount_error, "Cannot unreduce an uninitialized amount"); + + while (commodity_ && commodity().larger()) { + *this /= commodity().larger()->number(); + commodity_ = commodity().larger()->commodity_; + if (abs() < amount_t(1.0)) + break; + } + return *this; +} + +optional<amount_t> amount_t::value(const optional<moment_t>& moment) const +{ + if (quantity) { + optional<amount_t> amt(commodity().value(moment)); + if (amt) + return (*amt * number()).round(); + } else { + throw_(amount_error, "Cannot determine value of an uninitialized amount"); + } + return none; +} + + +int amount_t::sign() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine sign of an uninitialized amount"); + + return mpz_sgn(MPZ(quantity)); +} + +bool amount_t::is_zero() const +{ + if (! quantity) + throw_(amount_error, "Cannot determine sign if an uninitialized amount is zero"); + + if (has_commodity()) { + if (quantity->prec <= commodity().precision()) + return is_realzero(); + else + return round(commodity().precision()).sign() == 0; + } + return is_realzero(); +} + + +double amount_t::to_double(bool no_check) const +{ + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a double"); + + mpz_t remainder; + mpz_init(remainder); + + 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); + + double value = lexical_cast<double>(num.str()); + + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_double loses precision"); + + return value; +} + +long amount_t::to_long(bool no_check) const +{ + if (! quantity) + throw_(amount_error, "Cannot convert an uninitialized amount to a long"); + + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); + + long value = mpz_get_si(temp); + + if (! no_check && *this != value) + throw_(amount_error, "Conversion of amount to_long loses precision"); + + return value; +} + +bool amount_t::fits_in_double() const +{ + double value = to_double(true); + return *this == amount_t(value); +} + +bool amount_t::fits_in_long() const +{ + long value = to_long(true); + return *this == amount_t(value); +} + + +void amount_t::annotate_commodity(const annotation_t& details) +{ + commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; + + if (! quantity) + throw_(amount_error, "Cannot annotate the commodity of an uninitialized amount"); + else if (! has_commodity()) + throw_(amount_error, "Cannot annotate an amount with no commodity"); + + if (commodity().annotated) { + this_ann = &commodity().as_annotated(); + this_base = &this_ann->referent(); + } else { + this_base = &commodity(); + } + assert(this_base); + + DEBUG("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl << details); + + if (commodity_t * ann_comm = + this_base->parent().find_or_create(*this_base, details)) + set_commodity(*ann_comm); +#ifdef ASSERTS_ON + else + assert(false); +#endif + + DEBUG("amounts.commodities", " Annotated amount is " << *this); +} + +bool amount_t::commodity_annotated() const +{ + if (! quantity) + throw_(amount_error, + "Cannot determine if an uninitialized amount's commodity is annotated"); + + assert(! commodity().annotated || commodity().as_annotated().details); + return commodity().annotated; +} + +annotation_t amount_t::annotation_details() const +{ + if (! quantity) + throw_(amount_error, + "Cannot return commodity annotation details of an uninitialized amount"); + + assert(! commodity().annotated || commodity().as_annotated().details); + + if (commodity().annotated) { + annotated_commodity_t& ann_comm(commodity().as_annotated()); + return ann_comm.details; + } + return annotation_t(); +} + +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const +{ + if (! quantity) + throw_(amount_error, + "Cannot strip commodity annotations from an uninitialized amount"); + + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; + + amount_t t(*this); + t.set_commodity(commodity().as_annotated(). + strip_annotations(_keep_price, _keep_date, _keep_tag)); + return t; +} + + +namespace { + void parse_quantity(std::istream& in, string& value) + { + char buf[256]; + char c = peek_next_nonws(in); + READ_INTO(in, buf, 255, c, + std::isdigit(c) || c == '-' || c == '.' || c == ','); + + int len = std::strlen(buf); + while (len > 0 && ! std::isdigit(buf[len - 1])) { + buf[--len] = '\0'; + in.unget(); + } + + value = buf; + } +} + +void amount_t::parse(std::istream& in, flags_t flags) +{ + // The possible syntax for an amount is: + // + // [-]NUM[ ]SYM [@ AMOUNT] + // SYM[ ][-]NUM [@ AMOUNT] + + string symbol; + string quant; + annotation_t details; + bool negative = false; + + commodity_t::flags_t comm_flags = COMMODITY_STYLE_DEFAULTS; + + char c = peek_next_nonws(in); + if (c == '-') { + negative = true; + in.get(c); + c = peek_next_nonws(in); + } + + char n; + if (std::isdigit(c)) { + parse_quantity(in, quant); + + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(n)) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + commodity_t::parse_symbol(in, symbol); + + if (! symbol.empty()) + comm_flags |= COMMODITY_STYLE_SUFFIXED; + + if (! in.eof() && ((n = in.peek()) != '\n')) + details.parse(in); + } + } else { + commodity_t::parse_symbol(in, symbol); + + if (! in.eof() && ((n = in.peek()) != '\n')) { + if (std::isspace(in.peek())) + comm_flags |= COMMODITY_STYLE_SEPARATED; + + parse_quantity(in, quant); + + if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n')) + details.parse(in); + } + } + + if (quant.empty()) + throw_(amount_error, "No quantity specified for amount"); + + // Allocate memory for the amount's quantity value. We have to + // monitor the allocation in an auto_ptr because this function gets + // called sometimes from amount_t's constructor; and if there is an + // exeception thrown by any of the function calls after this point, + // the destructor will never be called and the memory never freed. + + std::auto_ptr<bigint_t> safe_holder; + + if (! quantity) { + quantity = new bigint_t; + safe_holder.reset(quantity); + } + else if (quantity->ref > 1) { + _release(); + quantity = new bigint_t; + safe_holder.reset(quantity); + } + + // Create the commodity if has not already been seen, and update the + // precision if something greater was used for the quantity. + + bool newly_created = false; + + if (symbol.empty()) { + commodity_ = NULL; + } else { + commodity_ = current_pool->find(symbol); + if (! commodity_) { + commodity_ = current_pool->create(symbol); + newly_created = true; + } + assert(commodity_); + + if (details) + commodity_ = current_pool->find_or_create(*commodity_, details); + } + + // Determine the precision of the amount, based on the usage of + // comma or period. + + string::size_type last_comma = quant.rfind(','); + string::size_type last_period = quant.rfind('.'); + + if (last_comma != string::npos && last_period != 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 != string::npos && + commodity().has_flags(COMMODITY_STYLE_EUROPEAN)) { + quantity->prec = quant.length() - last_comma - 1; + } + else if (last_period != string::npos && + ! (commodity().has_flags(COMMODITY_STYLE_EUROPEAN))) { + quantity->prec = quant.length() - last_period - 1; + } + else { + quantity->prec = 0; + } + + // Set the commodity's flags and precision accordingly + + if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { + commodity().add_flags(comm_flags); + + if (quantity->prec > commodity().precision()) + commodity().set_precision(quantity->prec); + } + + // Setup the amount's own flags + + if (flags & AMOUNT_PARSE_NO_MIGRATE) + quantity->add_flags(BIGINT_KEEP_PREC); + + // Now we have the final number. Remove commas and periods, if + // necessary. + + if (last_comma != string::npos || last_period != string::npos) { + int len = quant.length(); + char * buf = new char[len + 1]; + const char * p = quant.c_str(); + char * t = buf; + + while (*p) { + if (*p == ',' || *p == '.') + p++; + *t++ = *p++; + } + *t = '\0'; + + mpz_set_str(MPZ(quantity), buf, 10); + checked_array_delete(buf); + } else { + mpz_set_str(MPZ(quantity), quant.c_str(), 10); + } + + if (negative) + in_place_negate(); + + if (! (flags & AMOUNT_PARSE_NO_REDUCE)) + in_place_reduce(); + + safe_holder.release(); // `this->quantity' owns the pointer +} + +void amount_t::parse_conversion(const string& larger_str, + const string& smaller_str) +{ + amount_t larger, smaller; + + larger.parse(larger_str, AMOUNT_PARSE_NO_REDUCE); + smaller.parse(smaller_str, AMOUNT_PARSE_NO_REDUCE); + + larger *= smaller.number(); + + if (larger.commodity()) { + larger.commodity().set_smaller(smaller); + larger.commodity().add_flags(smaller.commodity().flags() | + COMMODITY_STYLE_NOMARKET); + } + if (smaller.commodity()) + smaller.commodity().set_larger(larger); +} + + +void amount_t::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const +{ + if (! quantity) + throw_(amount_error, "Cannot write out an uninitialized amount"); + + amount_t base(*this); + if (! amount_t::keep_base) + base.in_place_unreduce(); + + std::ostringstream out; + + mpz_t quotient; + mpz_t rquotient; + mpz_t remainder; + + mpz_init(quotient); + mpz_init(rquotient); + mpz_init(remainder); + + bool negative = false; + + // Ensure the value is rounded to the commodity's precision before + // outputting it. NOTE: `rquotient' is used here as a temp variable! + + commodity_t& comm(base.commodity()); + precision_t precision = 0; + + if (quantity) { + if (! comm || full_precision || base.quantity->has_flags(BIGINT_KEEP_PREC)) { + mpz_ui_pow_ui(divisor, 10, base.quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); + precision = base.quantity->prec; + } + else if (comm.precision() < base.quantity->prec) { + mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec, + comm.precision()); + mpz_ui_pow_ui(divisor, 10, comm.precision()); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision(); + } + else if (comm.precision() > base.quantity->prec) { + mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec); + mpz_mul(rquotient, MPZ(base.quantity), divisor); + mpz_ui_pow_ui(divisor, 10, comm.precision()); + mpz_tdiv_qr(quotient, remainder, rquotient, divisor); + precision = comm.precision(); + } + else if (base.quantity->prec) { + mpz_ui_pow_ui(divisor, 10, base.quantity->prec); + mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor); + precision = base.quantity->prec; + } + else { + mpz_set(quotient, MPZ(base.quantity)); + mpz_set_ui(remainder, 0); + precision = 0; + } + + if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { + negative = true; + + mpz_abs(quotient, quotient); + mpz_abs(remainder, remainder); + } + mpz_set(rquotient, remainder); + } + + if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + comm.print(out); + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + } + + if (negative) + out << "-"; + + if (! quantity || mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) { + char * p = mpz_get_str(NULL, 10, quotient); + out << p; + std::free(p); + } + else { + std::list<string> strs; + char buf[4]; + + for (int powers = 0; true; powers += 3) { + if (powers > 0) { + mpz_ui_pow_ui(divisor, 10, powers); + mpz_tdiv_q(temp, quotient, divisor); + if (mpz_sgn(temp) == 0) + break; + mpz_tdiv_r_ui(temp, temp, 1000); + } else { + mpz_tdiv_r_ui(temp, quotient, 1000); + } + mpz_get_str(buf, 10, temp); + strs.push_back(buf); + } + + bool printed = false; + + for (std::list<string>::reverse_iterator i = strs.rbegin(); + i != strs.rend(); + i++) { + if (printed) { + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? '.' : ','); + out.width(3); + out.fill('0'); + } + out << *i; + + printed = true; + } + } + + if (quantity && precision) { + std::ostringstream final; + final.width(precision); + final.fill('0'); + char * p = mpz_get_str(NULL, 10, rquotient); + final << p; + std::free(p); + + const string& str(final.str()); + int i, len = str.length(); + const char * q = str.c_str(); + for (i = len; i > 0; i--) + if (q[i - 1] != '0') + break; + + string ender; + if (i == len) + ender = str; + else if (i < comm.precision()) + ender = string(str, 0, comm.precision()); + else + ender = string(str, 0, i); + + if (! ender.empty()) { + if (omit_commodity) + out << '.'; + else + out << (comm.has_flags(COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } + + if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; + comm.print(out); + } + + mpz_clear(quotient); + mpz_clear(rquotient); + mpz_clear(remainder); + + // If there are any annotations associated with this commodity, + // output them now. + + if (! omit_commodity && comm.annotated) { + annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); + assert(&*ann.details.price != this); + ann.write_annotations(out); + } + + // 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(); +} + + +namespace { + char * bigints; + char * bigints_next; + uint_fast32_t bigints_index; + uint_fast32_t bigints_count; + char buf[4096]; +} + +void amount_t::read(std::istream& in) +{ + using ledger::binary; + + // Read in the commodity for this amount + + commodity_t::ident_t ident; + read_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); + } + + // Read in the quantity + + char byte; + in.read(&byte, sizeof(byte)); + + if (byte < 3) { + 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)); + + bigint_t::flags_t tflags; + in.read((char *)&tflags, sizeof(tflags)); + quantity->set_flags(tflags); + } + else { + assert(false); + } +} + +void amount_t::read(const char *& data) +{ + using ledger::binary; + + // Read in the commodity for this amount + + commodity_t::ident_t ident; + read_long(data, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = current_pool->null_commodity; + else { + commodity_ = current_pool->find(ident); + assert(commodity_); + } + + // Read in the quantity + + char byte = *data++;; + + if (byte < 3) { + if (byte == 2) { + quantity = new((bigint_t *)bigints_next) bigint_t; + bigints_next += sizeof(bigint_t); + } else { + quantity = new bigint_t; + } + + 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 = *((precision_t *) data); + data += sizeof(precision_t); + quantity->set_flags(*((flags_t *) data)); + data += sizeof(flags_t); + + if (byte == 2) + quantity->add_flags(BIGINT_BULK_ALLOC); + } else { + uint_fast32_t index = *((uint_fast32_t *) data); + data += sizeof(uint_fast32_t); + + quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t)); + DEBUG("amounts.refs", + quantity << " ref++, now " << (quantity->ref + 1)); + quantity->ref++; + } +} + +void amount_t::write(std::ostream& out, bool optimized) const +{ + using ledger::binary; + + // Write out the commodity for this amount + + if (! quantity) + throw_(amount_error, "Cannot serialize an uninitialized amount"); + + if (commodity_) + write_long(out, commodity_->ident); + else + write_long<commodity_t::ident_t>(out, 0xffffffff); + + // Write out the quantity + + char byte; + + if (! optimized || quantity->index == 0) { + if (optimized) { + quantity->index = ++bigints_index; // if !optimized, this is garbage + bigints_count++; + byte = 2; + } else { + byte = 1; + } + out.write(&byte, sizeof(byte)); + + 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)); + bigint_t::flags_t tflags = quantity->flags() & ~BIGINT_BULK_ALLOC; + assert(sizeof(tflags) == sizeof(bigint_t::flags_t)); + out.write((char *)&tflags, sizeof(tflags)); + } else { + assert(quantity->ref > 1); + + // Since this value has already been written, we simply write + // out a reference to which one it was. + byte = 3; + out.write(&byte, sizeof(byte)); + out.write((char *)&quantity->index, sizeof(quantity->index)); + } +} + + +bool amount_t::valid() const +{ + if (quantity) { + if (quantity->ref == 0) { + DEBUG("ledger.validate", "amount_t: quantity->ref == 0"); + return false; + } + } + else if (commodity_) { + DEBUG("ledger.validate", "amount_t: commodity_ != NULL"); + return false; + } + return true; +} + +} // namespace ledger |