ledger/amount.cc
John Wiegley 4518ea9540 Value expression architecture is now rewritten, but the functionality of the
old system (for example, the meaning of 'a') has yet to be restored.  In the
new scheme, this will be done by definition a function outside of the value
expression logic, rather than the tight coupling between journal innards and
value expressions that occurred in 2.x.
2008-07-29 05:59:38 -04:00

1438 lines
35 KiB
C++

/*
* 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 > 128) {
DEBUG("ledger.validate", "amount_t::bigint_t: ref > 128");
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())
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_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 = &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::commodity_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_details()
{
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();
}
#if 0
// jww (2008-07-29): Should these be static?
namespace {
#endif
char * bigints;
char * bigints_next;
uint_fast32_t bigints_index;
uint_fast32_t bigints_count;
char buf[4096];
#if 0
}
#endif
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((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 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) {
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 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) {
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->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