diff options
author | John Wiegley <johnw@newartisans.com> | 2008-08-05 13:17:04 -0400 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2008-08-05 18:05:49 -0400 |
commit | f6f4a46cf5b14f9a2170cd6475958efbf320caec (patch) | |
tree | 05bc1defcdebc201de3dd10477483d906a842821 /amount.cc | |
parent | b7970b29855563e4c67f85af8b31233eda80c22a (diff) | |
download | fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.gz fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.tar.bz2 fork-ledger-f6f4a46cf5b14f9a2170cd6475958efbf320caec.zip |
Moved around most of the files so that source code is in src/, documentation
is in doc/, etc.
Diffstat (limited to 'amount.cc')
-rw-r--r-- | amount.cc | 1433 |
1 files changed, 0 insertions, 1433 deletions
diff --git a/amount.cc b/amount.cc deleted file mode 100644 index 7f849bff..00000000 --- a/amount.cc +++ /dev/null @@ -1,1433 +0,0 @@ -/* - * Copyright (c) 2003-2008, 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_BULK_ALLOC), - 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); - } - - bool valid() const { - if (prec > 128) { - DEBUG("ledger.validate", "amount_t::bigint_t: prec > 128"); - return false; - } - if (ref > 16535) { - DEBUG("ledger.validate", "amount_t::bigint_t: ref > 16535"); - return false; - } -#if 0 - // jww (2008-07-24): How does one check the validity of an mpz_t? - if (val[0]._mp_size < 0 || val[0]._mp_size > 100) { - DEBUG("ledger.validate", "amount_t::bigint_t: val._mp_size is bad"); - return false; - } -#endif - return true; - } -}; - -uint_fast32_t amount_t::sizeof_bigint_t() -{ - return sizeof(bigint_t); -} - -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) -{ - assert(amt.valid()); - - 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_; - - assert(valid()); -} - -void amount_t::_dup() -{ - assert(valid()); - - if (quantity->ref > 1) { - bigint_t * q = new bigint_t(*quantity); - _release(); - quantity = q; - } - - assert(valid()); -} - -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; - - assert(valid()); -} - -void amount_t::_clear() -{ - if (quantity) { - _release(); - quantity = NULL; - commodity_ = NULL; - } else { - assert(! commodity_); - } -} - -void amount_t::_release() -{ - assert(valid()); - - 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); - quantity = NULL; - commodity_ = NULL; - } - - assert(valid()); -} - - -#ifdef HAVE_GDTOA -namespace { - amount_t::precision_t convert_double(mpz_t dest, double val) - { - 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; - } -} - -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); -} -#endif - -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 -{ - assert(amt.valid()); - - 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) -{ - assert(amt.valid()); - - 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) -{ - assert(amt.valid()); - - 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) -{ - assert(amt.valid()); - - 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 0 - 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")); -#endif - - _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) -{ - assert(amt.valid()); - - 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 0 - 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")); -#endif - - 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 -{ - if (! quantity) - throw_(amount_error, "Cannot round an uninitialized amount"); - - if (! has_commodity()) - return *this; - - return round(commodity().precision()); -} - -amount_t amount_t::round(precision_t prec) const -{ - if (! quantity) - throw_(amount_error, "Cannot round an uninitialized amount"); - - 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(1L)) - break; - } - return *this; -} - -optional<amount_t> amount_t::value(const optional<datetime_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 if an uninitialized amount is zero"); - - if (has_commodity()) { - if (quantity->prec <= commodity().precision() || - quantity->has_flags(BIGINT_KEEP_PREC)) - return is_realzero(); - else - return round(commodity().precision()).sign() == 0; - } - return is_realzero(); -} - - -#ifdef HAVE_GDTOA -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; -} -#endif - -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; -} - -#ifdef HAVE_GDTOA -bool amount_t::fits_in_double() const -{ - double value = to_double(true); - return *this == amount_t(value); -} -#endif - -bool amount_t::fits_in_long() const -{ - long value = to_long(true); - return *this == amount_t(value); -} - - -void amount_t::annotate(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 = &as_annotated_commodity(commodity()); - 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::annotated() const -{ - if (! quantity) - throw_(amount_error, - "Cannot determine if an uninitialized amount's commodity is annotated"); - - assert(! commodity().annotated || as_annotated_commodity(commodity()).details); - return commodity().annotated; -} - -annotation_t& amount_t::annotation() -{ - if (! quantity) - throw_(amount_error, - "Cannot return commodity annotation details of an uninitialized amount"); - - if (! commodity().is_annotated()) - throw_(amount_error, - "Request for annotation details from an unannotated amount"); - - annotated_commodity_t& ann_comm(as_annotated_commodity(commodity())); - return ann_comm.details; -} - -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(as_annotated_commodity(commodity()). - 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; - } -} - -bool 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()) { - if (flags & AMOUNT_PARSE_SOFT_FAIL) - return false; - else - 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_ && ! (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 - - assert(valid()); - - return true; -} - -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 -{ - assert(valid()); - - if (! quantity) { - _out << "<null>"; - return; - } - - 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(); -} - -void amount_t::read(std::istream& in) -{ - using namespace 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(reinterpret_cast<char *>(&len), sizeof(len)); - assert(len < 4096); - static char buf[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(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec)); - - bigint_t::flags_t tflags; - in.read(reinterpret_cast<char *>(&tflags), sizeof(tflags)); - quantity->set_flags(tflags); - } - else { - assert(false); - } -} - -void amount_t::read(const char *& data) -{ - using namespace 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) { -#if 0 - quantity = new(reinterpret_cast<bigint_t *>(bigints_next)) bigint_t; - bigints_next += sizeof(bigint_t); -#endif - } else { - quantity = new bigint_t; - } - - unsigned short len = - *reinterpret_cast<unsigned short *>(const_cast<char *>(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 = *reinterpret_cast<precision_t *>(const_cast<char *>(data)); - data += sizeof(precision_t); - quantity->set_flags(*reinterpret_cast<flags_t *>(const_cast<char *>(data))); - data += sizeof(flags_t); - - if (byte == 2) - quantity->add_flags(BIGINT_BULK_ALLOC); - } else { -#if 0 - uint_fast32_t index = *reinterpret_cast<uint_fast32_t *>(const_cast<char *>(data)); - data += sizeof(uint_fast32_t); - - quantity = reinterpret_cast<bigint_t *>(bigints + (index - 1) * sizeof(bigint_t)); -#endif - DEBUG("amounts.refs", - quantity << " ref++, now " << (quantity->ref + 1)); - quantity->ref++; - } -} - -void amount_t::write(std::ostream& out, bool optimized) const -{ - using namespace 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) { -#if 0 - quantity->index = ++bigints_index; // if !optimized, this is garbage - bigints_count++; - byte = 2; -#endif - } else { - byte = 1; - } - out.write(&byte, sizeof(byte)); - - std::size_t size; - static char buf[4096]; - mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity)); - unsigned short len = size * sizeof(short); - out.write(reinterpret_cast<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(reinterpret_cast<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(reinterpret_cast<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(reinterpret_cast<char *>(&quantity->index), sizeof(quantity->index)); - } -} - -bool amount_t::valid() const -{ - if (quantity) { - if (! quantity->valid()) - return false; - - 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 |