Rationals based math is now passing the unit tests.

This commit is contained in:
John Wiegley 2009-01-31 04:25:05 -04:00
parent c96ab6cb0f
commit e9ff5caa13
14 changed files with 157 additions and 892 deletions

View file

@ -41,29 +41,6 @@ namespace ledger {
using namespace boost::python;
#ifdef INTEGER_MATH
amount_t py_round_0(const amount_t& amount) {
return amount.round();
}
amount_t py_round_1(const amount_t& amount, amount_t::precision_t prec) {
return amount.round(prec);
}
#endif
double py_to_double_0(amount_t& amount) {
return amount.to_double();
}
double py_to_double_1(amount_t& amount, bool no_check) {
return amount.to_double(no_check);
}
long py_to_long_0(amount_t& amount) {
return amount.to_long();
}
long py_to_long_1(amount_t& amount, bool no_check) {
return amount.to_long(no_check);
}
boost::optional<amount_t> py_value_0(const amount_t& amount) {
return amount.value();
}
@ -169,7 +146,9 @@ void export_amount()
make_getter(&amount_t::stream_fullstrings),
make_setter(&amount_t::stream_fullstrings))
#if 0
.def(init<double>())
#endif
.def(init<long>())
.def(init<std::string>())
@ -185,80 +164,108 @@ internal precision.")
.def(self == self)
.def(self == long())
.def(long() == self)
#if 0
.def(self == double())
.def(double() == self)
#endif
.def(self != self)
.def(self != long())
.def(long() != self)
#if 0
.def(self != double())
.def(double() != self)
#endif
.def(! self)
.def(self < self)
.def(self < long())
.def(long() < self)
#if 0
.def(self < double())
.def(double() < self)
#endif
.def(self <= self)
.def(self <= long())
.def(long() <= self)
#if 0
.def(self <= double())
.def(double() <= self)
#endif
.def(self > self)
.def(self > long())
.def(long() > self)
#if 0
.def(self > double())
.def(double() > self)
#endif
.def(self >= self)
.def(self >= long())
.def(long() >= self)
#if 0
.def(self >= double())
.def(double() >= self)
#endif
.def(self += self)
.def(self += long())
#if 0
.def(self += double())
#endif
.def(self + self)
.def(self + long())
.def(long() + self)
#if 0
.def(self + double())
.def(double() + self)
#endif
.def(self -= self)
.def(self -= long())
#if 0
.def(self -= double())
#endif
.def(self - self)
.def(self - long())
.def(long() - self)
#if 0
.def(self - double())
.def(double() - self)
#endif
.def(self *= self)
.def(self *= long())
#if 0
.def(self *= double())
#endif
.def(self * self)
.def(self * long())
.def(long() * self)
#if 0
.def(self * double())
.def(double() * self)
#endif
.def(self /= self)
.def(self /= long())
#if 0
.def(self /= double())
#endif
.def(self / self)
.def(self / long())
.def(long() / self)
#if 0
.def(self / double())
.def(double() / self)
#endif
.def("precision", &amount_t::precision)
@ -270,11 +277,8 @@ internal precision.")
.def("abs", &amount_t::abs)
.def("__abs__", &amount_t::abs)
#ifdef INTEGER_MATH
.def("round", py_round_0)
.def("round", py_round_1)
#endif
.def("unround", &amount_t::unround)
.def("rounded", &amount_t::rounded)
.def("unrounded", &amount_t::unrounded)
.def("reduce", &amount_t::reduce)
.def("in_place_reduce", &amount_t::in_place_reduce,
@ -295,18 +299,15 @@ internal precision.")
.def("is_realzero", &amount_t::is_realzero)
.def("is_null", &amount_t::is_null)
.def("to_double", py_to_double_0)
.def("to_double", py_to_double_1)
.def("__float__", py_to_double_0)
.def("to_long", py_to_long_0)
.def("to_long", py_to_long_1)
.def("__int__", py_to_long_0)
.def("to_double", &amount_t::to_double)
.def("__float__", &amount_t::to_double)
.def("to_long", &amount_t::to_long)
.def("__int__", &amount_t::to_long)
.def("to_string", &amount_t::to_string)
.def("__str__", &amount_t::to_string)
.def("to_fullstring", &amount_t::to_fullstring)
.def("__repr__", &amount_t::to_fullstring)
.def("fits_in_double", &amount_t::fits_in_double)
.def("fits_in_long", &amount_t::fits_in_long)
.def("quantity_string", &amount_t::quantity_string)
@ -359,7 +360,9 @@ internal precision.")
register_optional_to_python<amount_t>();
#if 0
implicitly_convertible<double, amount_t>();
#endif
implicitly_convertible<long, amount_t>();
implicitly_convertible<string, amount_t>();

View file

@ -195,11 +195,8 @@ void export_value()
.def("abs", &value_t::abs)
.def("__abs__", &value_t::abs)
#ifdef INTEGER_MATH
.def("round", &value_t::round)
.def("in_place_round", &value_t::in_place_round)
#endif
.def("unround", &value_t::unround)
.def("rounded", &value_t::rounded)
.def("unrounded", &value_t::unrounded)
.def("reduce", &value_t::reduce)
.def("in_place_reduce", &value_t::in_place_reduce)

View file

@ -47,26 +47,18 @@ bool amount_t::stream_fullstrings = false;
#if !defined(THREADSAFE)
// These global temporaries are pre-initialized for the sake of
// efficiency, and are reused over and over again.
static mpz_t temp;
static mpz_t temp;
static mpq_t tempq;
static mpfr_t tempf;
#ifdef INTEGER_MATH
static mpz_t divisor;
#else
static mpq_t tempq;
static mpfr_t tempfb;
#endif
#endif
struct amount_t::bigint_t : public supports_flags<>
{
#define BIGINT_BULK_ALLOC 0x01
#define BIGINT_KEEP_PREC 0x02
#ifdef INTEGER_MATH
mpz_t val;
#else
mpq_t val;
#endif
precision_t prec;
uint_least16_t ref;
uint_fast32_t index;
@ -75,31 +67,19 @@ struct amount_t::bigint_t : public supports_flags<>
bigint_t() : prec(0), ref(1), index(0) {
TRACE_CTOR(bigint_t, "");
#ifdef INTEGER_MATH
mpz_init(val);
#else
mpq_init(val);
#endif
}
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");
#ifdef INTEGER_MATH
mpz_init_set(val, other.val);
#else
mpq_init(val);
mpq_set(val, other.val);
#endif
}
~bigint_t() {
TRACE_DTOR(bigint_t);
assert(ref == 0);
#ifdef INTEGER_MATH
mpz_clear(val);
#else
mpq_clear(val);
#endif
}
bool valid() const {
@ -125,15 +105,11 @@ amount_t * one = NULL;
void amount_t::initialize()
{
mpz_init(temp);
mpfr_init(tempf);
#ifdef INTEGER_MATH
mpz_init(divisor);
#else
mpq_init(tempq);
mpfr_init(tempf);
mpfr_init(tempfb);
#endif
one = new amount_t(amount_t(1L).unround());
one = new amount_t(amount_t(1L).unrounded());
if (! current_pool)
current_pool = new commodity_pool_t;
@ -153,13 +129,9 @@ void amount_t::initialize()
void amount_t::shutdown()
{
mpz_clear(temp);
mpfr_clear(tempf);
#ifdef INTEGER_MATH
mpz_clear(divisor);
#else
mpq_clear(tempq);
mpfr_clear(tempf);
mpfr_clear(tempfb);
#endif
checked_delete(one);
@ -206,28 +178,6 @@ void amount_t::_dup()
assert(valid());
}
#ifdef INTEGER_MATH
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(MP(quantity), MP(quantity), divisor);
quantity->prec = prec;
assert(valid());
}
#endif // INTEGER_MATH
void amount_t::_clear()
{
if (quantity) {
@ -262,12 +212,7 @@ amount_t::amount_t(const double val) : commodity_(NULL)
{
TRACE_CTOR(amount_t, "const double");
quantity = new bigint_t;
#ifdef INTEGER_MATH
mpfr_set_d(tempf, val, GMP_RNDN);
mpfr_get_z(MP(quantity), tempf);
#else
mpq_set_d(MP(quantity), val);
#endif
quantity->prec = extend_by_digits; // an approximation
}
@ -275,22 +220,14 @@ amount_t::amount_t(const unsigned long val) : commodity_(NULL)
{
TRACE_CTOR(amount_t, "const unsigned long");
quantity = new bigint_t;
#ifdef INTEGER_MATH
mpz_set_ui(MP(quantity), val);
#else
mpq_set_ui(MP(quantity), val, 1);
#endif
}
amount_t::amount_t(const long val) : commodity_(NULL)
{
TRACE_CTOR(amount_t, "const long");
quantity = new bigint_t;
#ifdef INTEGER_MATH
mpz_set_si(MP(quantity), val);
#else
mpq_set_si(MP(quantity), val, 1);
#endif
}
@ -325,23 +262,7 @@ int amount_t::compare(const amount_t& amt) const
"Cannot compare amounts with different commodities: " <<
commodity().symbol() << " and " << amt.commodity().symbol());
#ifdef INTEGER_MATH
if (quantity->prec == amt.quantity->prec) {
return mpz_cmp(MP(quantity), MP(amt.quantity));
}
else if (quantity->prec < amt.quantity->prec) {
amount_t t(*this);
t._resize(amt.quantity->prec);
return mpz_cmp(MP(t.quantity), MP(amt.quantity));
}
else {
amount_t t = amt;
t._resize(quantity->prec);
return mpz_cmp(MP(quantity), MP(t.quantity));
}
#else
return mpq_cmp(MP(quantity), MP(amt.quantity));
#endif
}
@ -367,25 +288,10 @@ amount_t& amount_t::operator+=(const amount_t& amt)
_dup();
#ifdef INTEGER_MATH
if (quantity->prec == amt.quantity->prec) {
mpz_add(MP(quantity), MP(quantity), MP(amt.quantity));
}
else if (quantity->prec < amt.quantity->prec) {
_resize(amt.quantity->prec);
mpz_add(MP(quantity), MP(quantity), MP(amt.quantity));
}
else {
amount_t t = amt;
t._resize(quantity->prec);
mpz_add(MP(quantity), MP(quantity), MP(t.quantity));
}
#else
mpq_add(MP(quantity), MP(quantity), MP(amt.quantity));
if (quantity->prec < amt.quantity->prec)
quantity->prec = amt.quantity->prec;
#endif
return *this;
}
@ -412,81 +318,14 @@ amount_t& amount_t::operator-=(const amount_t& amt)
_dup();
#ifdef INTEGER_MATH
if (quantity->prec == amt.quantity->prec) {
mpz_sub(MP(quantity), MP(quantity), MP(amt.quantity));
}
else if (quantity->prec < amt.quantity->prec) {
_resize(amt.quantity->prec);
mpz_sub(MP(quantity), MP(quantity), MP(amt.quantity));
}
else {
amount_t t = amt;
t._resize(quantity->prec);
mpz_sub(MP(quantity), MP(quantity), MP(t.quantity));
}
#else
mpq_sub(MP(quantity), MP(quantity), MP(amt.quantity));
if (quantity->prec < amt.quantity->prec)
quantity->prec = amt.quantity->prec;
#endif
return *this;
}
#ifdef INTEGER_MATH
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);
}
}
#endif // INTEGER_MATH
amount_t& amount_t::operator*=(const amount_t& amt)
{
assert(amt.valid());
@ -502,11 +341,7 @@ amount_t& amount_t::operator*=(const amount_t& amt)
_dup();
#ifdef INTEGER_MATH
mpz_mul(MP(quantity), MP(quantity), MP(amt.quantity));
#else
mpq_mul(MP(quantity), MP(quantity), MP(amt.quantity));
#endif
quantity->prec += amt.quantity->prec;
if (! has_commodity())
@ -514,13 +349,8 @@ amount_t& amount_t::operator*=(const amount_t& amt)
if (has_commodity() && ! keep_precision()) {
precision_t comm_prec = commodity().precision();
if (quantity->prec > comm_prec + extend_by_digits) {
#ifdef INTEGER_MATH
mpz_round(MP(quantity), MP(quantity), quantity->prec,
comm_prec + extend_by_digits);
#endif // INTEGER_MATH
if (quantity->prec > comm_prec + extend_by_digits)
quantity->prec = comm_prec + extend_by_digits;
}
}
return *this;
@ -547,19 +377,8 @@ amount_t& amount_t::operator/=(const amount_t& amt)
// Increase the value's precision, to capture fractional parts after
// the divide. Round up in the last position.
#ifdef INTEGER_MATH
mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec +
extend_by_digits + 1U);
mpz_mul(MP(quantity), MP(quantity), divisor);
mpz_tdiv_q(MP(quantity), MP(quantity), MP(amt.quantity));
quantity->prec += amt.quantity->prec + quantity->prec + extend_by_digits + 1U;
mpz_round(MP(quantity), MP(quantity), quantity->prec, quantity->prec - 1);
quantity->prec -= 1;
#else
mpq_div(MP(quantity), MP(quantity), MP(amt.quantity));
quantity->prec += amt.quantity->prec + quantity->prec + extend_by_digits;
#endif
if (! has_commodity())
commodity_ = amt.commodity_;
@ -571,13 +390,8 @@ amount_t& amount_t::operator/=(const amount_t& amt)
if (has_commodity() && ! keep_precision()) {
precision_t comm_prec = commodity().precision();
if (quantity->prec > comm_prec + extend_by_digits) {
#ifdef INTEGER_MATH
mpz_round(MP(quantity), MP(quantity), quantity->prec,
comm_prec + extend_by_digits);
#endif // INTEGER_MATH
if (quantity->prec > comm_prec + extend_by_digits)
quantity->prec = comm_prec + extend_by_digits;
}
}
return *this;
@ -634,59 +448,28 @@ amount_t& amount_t::in_place_negate()
{
if (quantity) {
_dup();
#ifdef INTEGER_MATH
mpz_neg(MP(quantity), MP(quantity));
#else
mpq_neg(MP(quantity), MP(quantity));
#endif
} else {
throw_(amount_error, "Cannot negate an uninitialized amount");
}
return *this;
}
#ifdef INTEGER_MATH
amount_t& amount_t::in_place_round()
amount_t amount_t::rounded() const
{
if (! quantity)
throw_(amount_error, "Cannot round an uninitialized amount");
if (has_commodity())
in_place_round(commodity().precision());
return *this;
}
amount_t& amount_t::in_place_round(precision_t prec)
{
if (! quantity)
throw_(amount_error, "Cannot round an uninitialized amount");
if (quantity && quantity->prec <= prec) {
if (keep_precision()) {
_dup();
set_keep_precision(false);
}
throw_(amount_error, "Cannot set rounding for an uninitialized amount");
else if (! keep_precision())
return *this;
}
DEBUG("amount.round", "Rounding " << *this << " to precision " << prec);
amount_t t(*this);
t._dup();
t.set_keep_precision(false);
_dup();
mpz_round(MP(quantity), MP(quantity), quantity->prec, prec);
quantity->prec = prec;
set_keep_precision(false);
DEBUG("amount.round", " result = " << *this);
return *this;
return t;
}
#endif // INTEGER_MATH
amount_t amount_t::unround() const
amount_t amount_t::unrounded() const
{
if (! quantity)
throw_(amount_error, "Cannot unround an uninitialized amount");
@ -732,11 +515,7 @@ optional<amount_t> amount_t::value(const optional<datetime_t>& moment,
if (quantity) {
optional<price_point_t> point(commodity().find_price(in_terms_of, moment));
if (point)
#ifdef INTEGER_MATH
return (point->price * number()).round();
#else
return point->price * number();
#endif
return (point->price * number()).rounded();
} else {
throw_(amount_error, "Cannot determine value of an uninitialized amount");
}
@ -749,15 +528,9 @@ int amount_t::sign() const
if (! quantity)
throw_(amount_error, "Cannot determine sign of an uninitialized amount");
#ifdef INTEGER_MATH
return mpz_sgn(MP(quantity));
#else
return mpq_sgn(MP(quantity));
#endif
}
#ifndef INTEGER_MATH
namespace {
void stream_out_mpq(std::ostream& out, mpq_t quant,
amount_t::precision_t prec,
@ -826,8 +599,6 @@ namespace {
}
}
#endif // INTEGER_MATH
bool amount_t::is_zero() const
{
if (! quantity)
@ -837,9 +608,6 @@ bool amount_t::is_zero() const
if (keep_precision() || quantity->prec <= commodity().precision()) {
return is_realzero();
} else {
#ifdef INTEGER_MATH
return round(commodity().precision()).sign() == 0;
#else
std::ostringstream out;
stream_out_mpq(out, MP(quantity), commodity().precision());
@ -847,80 +615,34 @@ bool amount_t::is_zero() const
if (*p != '0' && *p != '.')
return false;
return true;
#endif
}
}
return is_realzero();
}
double amount_t::to_double(bool no_check) const
double amount_t::to_double() const
{
if (! quantity)
throw_(amount_error, "Cannot convert an uninitialized amount to a double");
#ifdef INTEGER_MATH
mpz_t remainder;
mpz_init(remainder);
mpz_set(temp, MP(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());
#else
mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
double value = mpfr_get_d(tempf, GMP_RNDN);
#endif
if (! no_check && *this != value)
throw_(amount_error, "Conversion of amount to_double loses precision");
return value;
return mpfr_get_d(tempf, GMP_RNDN);
}
long amount_t::to_long(bool no_check) const
long amount_t::to_long() const
{
if (! quantity)
throw_(amount_error, "Cannot convert an uninitialized amount to a long");
#ifdef INTEGER_MATH
mpz_set(temp, MP(quantity));
mpz_ui_pow_ui(divisor, 10, quantity->prec);
mpz_tdiv_q(temp, temp, divisor);
long value = mpz_get_si(temp);
#else
mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
long value = mpfr_get_si(tempf, GMP_RNDN);
#endif
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);
return mpfr_get_si(tempf, GMP_RNDN);
}
bool amount_t::fits_in_long() const
{
long value = to_long(true);
return *this == amount_t(value);
mpfr_set_q(tempf, MP(quantity), GMP_RNDN);
return mpfr_fits_slong_p(tempf, GMP_RNDN);
}
@ -1170,9 +892,6 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
}
*t = '\0';
#ifdef INTEGER_MATH
mpz_set_str(MP(quantity), buf.get(), 10);
#else
mpq_set_str(MP(quantity), buf.get(), 10);
mpz_ui_pow_ui(temp, 10, quantity->prec);
mpq_set_z(tempq, temp);
@ -1183,13 +902,8 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags)
DEBUG("amount.parse", "Rational parsed = " << buf);
std::free(buf);
}
#endif
} else {
#ifdef INTEGER_MATH
mpz_set_str(MP(quantity), quant.c_str(), 10);
#else
mpq_set_str(MP(quantity), quant.c_str(), 10);
#endif
}
if (negative)
@ -1243,156 +957,6 @@ void amount_t::print(std::ostream& _out, bool omit_commodity,
commodity_t& comm(base.commodity());
precision_t precision = 0;
#ifdef INTEGER_MATH
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!
if (quantity) {
if (! comm || full_precision || base.keep_precision()) {
mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
mpz_tdiv_qr(quotient, remainder, MP(base.quantity), divisor);
precision = base.quantity->prec;
}
else if (comm.precision() < base.quantity->prec) {
mpz_round(rquotient, MP(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, MP(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, MP(base.quantity), divisor);
precision = base.quantity->prec;
}
else {
mpz_set(quotient, MP(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);
#else // INTEGER_MATH
if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) {
comm.print(out);
if (comm.has_flags(COMMODITY_STYLE_SEPARATED))
@ -1408,8 +972,6 @@ void amount_t::print(std::ostream& _out, bool omit_commodity,
comm.print(out);
}
#endif // INTEGER_MATH
// If there are any annotations associated with this commodity,
// output them now.
@ -1451,43 +1013,27 @@ void amount_t::read(std::istream& in)
if (byte < 3) {
quantity = new bigint_t;
#ifndef INTEGER_MATH
mpz_t numerator;
mpz_t denominator;
#endif
unsigned short len;
in.read(reinterpret_cast<char *>(&len), sizeof(len));
assert(len < 4096);
static char buf[4096];
in.read(buf, len);
#ifdef INTEGER_MATH
mpz_import(MP(quantity), len / sizeof(short), 1, sizeof(short),
0, 0, buf);
#else
mpz_init(numerator);
mpz_import(numerator, len / sizeof(short), 1, sizeof(short),
mpz_import(temp, len / sizeof(short), 1, sizeof(short),
0, 0, buf);
mpq_set_num(MP(quantity), temp);
in.read(reinterpret_cast<char *>(&len), sizeof(len));
assert(len < 4096);
in.read(buf, len);
mpz_init(denominator);
mpz_import(denominator, len / sizeof(short), 1, sizeof(short),
mpz_import(temp, len / sizeof(short), 1, sizeof(short),
0, 0, buf);
mpq_set_num(MP(quantity), numerator);
mpq_set_den(MP(quantity), denominator);
#endif
mpq_set_den(MP(quantity), temp);
char negative;
in.read(&negative, sizeof(negative));
if (negative)
#ifdef INTEGER_MATH
mpz_neg(MP(quantity), MP(quantity));
#else
mpq_neg(MP(quantity), MP(quantity));
#endif
in.read(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec));
@ -1531,40 +1077,28 @@ void amount_t::read(const char *& data,
quantity = new bigint_t;
}
#ifndef INTEGER_MATH
mpz_t numerator;
mpz_t denominator;
#endif
unsigned short len =
*reinterpret_cast<unsigned short *>(const_cast<char *>(data));
data += sizeof(unsigned short);
#ifdef INTEGER_MATH
mpz_import(MP(quantity), len / sizeof(short), 1, sizeof(short),
0, 0, data);
#else
mpz_init(numerator);
mpz_import(numerator, len / sizeof(short), 1, sizeof(short),
mpz_init(temp);
mpz_import(temp, len / sizeof(short), 1, sizeof(short),
0, 0, data);
data += len;
mpq_set_num(MP(quantity), temp);
len = *reinterpret_cast<unsigned short *>(const_cast<char *>(data));
data += sizeof(unsigned short);
mpz_init(denominator);
mpz_import(denominator, len / sizeof(short), 1, sizeof(short),
mpz_init(temp);
mpz_import(temp, len / sizeof(short), 1, sizeof(short),
0, 0, data);
mpq_set_num(MP(quantity), numerator);
mpq_set_den(MP(quantity), denominator);
#endif
data += len;
mpq_set_den(MP(quantity), temp);
char negative = *data++;
if (negative)
#ifdef INTEGER_MATH
mpz_neg(MP(quantity), MP(quantity));
#else
mpq_neg(MP(quantity), MP(quantity));
#endif
quantity->prec = *reinterpret_cast<precision_t *>(const_cast<char *>(data));
data += sizeof(precision_t);
@ -1585,6 +1119,20 @@ void amount_t::read(const char *& data,
}
}
namespace {
void write_bytes(std::ostream& out,
const char * buf,
const std::size_t size)
{
unsigned short len = size * sizeof(short);
out.write(reinterpret_cast<char *>(&len), sizeof(len));
if (len) {
assert(len < 4096);
out.write(buf, len);
}
}
}
void amount_t::write(std::ostream& out, std::size_t index) const
{
using namespace ledger::binary;
@ -1614,32 +1162,16 @@ void amount_t::write(std::ostream& out, std::size_t index) const
std::size_t size;
static char buf[4096];
#ifdef INTEGER_MATH
mpz_export(buf, &size, 1, sizeof(short), 0, 0, MP(quantity));
#else
mpz_t numerator;
mpz_t denominator;
mpz_init(numerator);
mpq_get_num(numerator, MP(quantity));
mpz_export(buf, &size, 1, sizeof(short), 0, 0, numerator);
mpq_get_num(temp, MP(quantity));
mpz_export(buf, &size, 1, sizeof(short), 0, 0, temp);
write_bytes(out, buf, size);
mpz_init(denominator);
mpq_get_den(denominator, MP(quantity));
mpz_export(buf, &size, 1, sizeof(short), 0, 0, denominator);
#endif
unsigned short len = size * sizeof(short);
out.write(reinterpret_cast<char *>(&len), sizeof(len));
if (len) {
assert(len < 4096);
out.write(buf, len);
}
mpq_get_den(temp, MP(quantity));
mpz_export(buf, &size, 1, sizeof(short), 0, 0, temp);
write_bytes(out, buf, size);
#ifdef INTEGER_MATH
byte = mpz_sgn(MP(quantity)) < 0 ? 1 : 0;
#else
byte = mpq_sgn(MP(quantity)) < 0 ? 1 : 0;
#endif
out.write(&byte, sizeof(byte));
out.write(reinterpret_cast<char *>(&quantity->prec), sizeof(quantity->prec));

View file

@ -57,11 +57,6 @@
namespace ledger {
// If defined, amount.cc uses GMP integers rather than rationals.
// Ledger 3.0 uses rationals, but 2.6 and before used integers, so this
// provides a quick way of testing against past numerical behavior.
//#define INTEGER_MATH 1
class commodity_t;
class annotation_t;
class commodity_pool_t;
@ -147,9 +142,6 @@ public:
protected:
void _copy(const amount_t& amt);
void _dup();
#ifdef INTEGER_MATH
void _resize(precision_t prec);
#endif
void _clear();
void _release();
@ -354,33 +346,16 @@ public:
return *this;
}
#ifdef INTEGER_MATH
/** An amount's internal value to the given precision, or to the
commodity's current display precision if no precision value is
given. This method changes the internal value of the amount, if
it's internal precision was greater than the rounding precision.
*/
amount_t round() const {
amount_t temp(*this);
temp.in_place_round();
return temp;
}
amount_t& in_place_round();
amount_t round(precision_t prec) const {
amount_t temp(*this);
temp.in_place_round(prec);
return temp;
}
amount_t& in_place_round(precision_t prec);
#endif // INTEGER_MATH
/** Yields an amount whose display precision when output is truncated
to the display precision of its commodity. This is normally the
default state of an amount, but if one has become unrounded, this
sets the "keep precision" state back to false.
@see set_keep_precision */
amount_t rounded() const;
/** Yields an amount whose display precision is never truncated, even
though its commodity normally displays only rounded values.
*/
amount_t unround() const;
though its commodity normally displays only rounded values. */
amount_t unrounded() const;
/** reduces a value to its most basic commodity form, for amounts that
utilize "scaling commodities". For example, an amount of \c 1h
@ -487,9 +462,6 @@ public:
optional boolean argument is true (the default), an exception is
thrown if the conversion would lose information.
fits_in_double() returns true if to_double() would not lose
precision.
fits_in_long() returns true if to_long() would not lose
precision.
@ -506,15 +478,14 @@ public:
been stripped and the full, internal precision of the amount
would be displayed.
*/
double to_double(bool no_check = false) const;
long to_long(bool no_check = false) const;
double to_double() const;
long to_long() const;
bool fits_in_long() const;
string to_string() const;
string to_fullstring() const;
string quantity_string() const;
bool fits_in_double() const;
bool fits_in_long() const;
/*@}*/
/** @name Commodity methods

View file

@ -311,38 +311,17 @@ public:
return temp;
}
#ifdef INTEGER_MATH
balance_t round() const {
balance_t rounded() const {
balance_t temp;
foreach (const amounts_map::value_type& pair, amounts)
temp += pair.second.round();
temp += pair.second.rounded();
return temp;
}
balance_t& in_place_round() {
foreach (amounts_map::value_type& pair, amounts)
pair.second.in_place_round();
return *this;
}
balance_t round(amount_t::precision_t prec) const {
balance_t unrounded() const {
balance_t temp;
foreach (const amounts_map::value_type& pair, amounts)
temp += pair.second.round(prec);
return temp;
}
balance_t& in_place_round(amount_t::precision_t prec) {
foreach (amounts_map::value_type& pair, amounts)
pair.second.in_place_round(prec);
return *this;
}
#endif // INTEGER_MATH
balance_t unround() const {
balance_t temp;
foreach (const amounts_map::value_type& pair, amounts)
temp += pair.second.unround();
temp += pair.second.unrounded();
return temp;
}

View file

@ -657,15 +657,13 @@ void annotation_t::parse(std::istream& in)
temp.parse(buf, amount_t::PARSE_NO_MIGRATE);
temp.in_place_reduce();
#ifdef INTEGER_MATH
// Since this price will maintain its own precision, make sure
// it is at least as large as the base commodity, since the user
// may have only specified {$1} or something similar.
if (temp.has_commodity() &&
temp.precision() > temp.commodity().precision())
temp = temp.round(); // no need to retain individual precision
#endif
temp = temp.rounded(); // no need to retain individual precision
price = temp;
}

View file

@ -231,7 +231,7 @@ bool entry_base_t::finalize()
add_error_context(entry_context(*this));
#endif
add_error_context("Unbalanced remainder is: ");
add_error_context(value_context(balance.unround()));
add_error_context(value_context(balance.unrounded()));
throw_(balance_error, "Entry does not balance");
}

View file

@ -367,11 +367,7 @@ void changed_value_xacts::output_diff(const date_t& date)
value_t cur_bal;
last_xact->xdata().date = date;
#ifdef INTEGER_MATH
cur_bal = total_expr.calc(*last_xact).round();
#else
cur_bal = total_expr.calc(*last_xact);
#endif
cur_bal = total_expr.calc(*last_xact).rounded();
if (value_t diff = cur_bal - last_balance) {
entry_temps.push_back(entry_t());
@ -394,11 +390,7 @@ void changed_value_xacts::operator()(xact_t& xact)
item_handler<xact_t>::operator()(xact);
#ifdef INTEGER_MATH
last_balance = total_expr.calc(xact).round();
#else
last_balance = total_expr.calc(xact);
#endif
last_balance = total_expr.calc(xact).rounded();
last_xact = &xact;
}

View file

@ -187,11 +187,7 @@ static void endElement(void *, const char *name)
if (default_commodity) {
curr_quant.set_commodity(*default_commodity);
#ifdef INTEGER_MATH
value = curr_quant.round();
#else
value = curr_quant;
#endif
value = curr_quant.rounded();
if (curr_value.commodity() == *default_commodity)
curr_value = value;

View file

@ -1400,56 +1400,28 @@ value_t value_t::abs() const
return NULL_VALUE;
}
#ifdef INTEGER_MATH
value_t value_t::round() const
value_t value_t::rounded() const
{
switch (type()) {
case INTEGER:
return *this;
case AMOUNT:
return as_amount().round();
case BALANCE:
return as_balance().round();
case BALANCE_PAIR:
return as_balance_pair().round();
return as_amount().rounded();
default:
break;
}
throw_(value_error, "Cannot round " << label());
throw_(value_error, "Cannot set rounding for " << label());
return NULL_VALUE;
}
void value_t::in_place_round()
{
switch (type()) {
case INTEGER:
break;
case AMOUNT:
as_amount_lval().in_place_round();
break;
case BALANCE:
as_balance_lval().in_place_round();
break;
case BALANCE_PAIR:
as_balance_pair_lval().in_place_round();
break;
default:
throw_(value_error, "Cannot round " << label());
break;
}
}
#endif // INTEGER_MATH
value_t value_t::unround() const
value_t value_t::unrounded() const
{
switch (type()) {
case INTEGER:
return *this;
case AMOUNT:
return as_amount().unround();
return as_amount().unrounded();
default:
break;
}

View file

@ -404,11 +404,9 @@ public:
}
value_t abs() const;
#ifdef INTEGER_MATH
value_t round() const;
void in_place_round();
#endif
value_t unround() const;
value_t rounded() const;
value_t unrounded() const;
value_t reduce() const {
value_t temp(*this);

View file

@ -213,6 +213,7 @@ void AmountTestCase::testCommodityConstructors()
}
#ifndef NOT_FOR_PYTHON
void AmountTestCase::testAssignment()
{
amount_t x0;
@ -318,6 +319,7 @@ void AmountTestCase::testCommodityAssignment()
assertValid(x9);
assertValid(x10);
}
#endif // NOT_FOR_PYTHON
void AmountTestCase::testEquality()
@ -423,8 +425,10 @@ void AmountTestCase::testComparisons()
assertTrue(100L > x1);
assertTrue(x1 < 100UL);
assertTrue(100UL > x1);
#ifndef NOT_FOR_PYTHON
assertTrue(x1 < 100.0);
assertTrue(100.0 > x1);
#endif // NOT_FOR_PYTHON
assertValid(x0);
assertValid(x1);
@ -538,7 +542,9 @@ void AmountTestCase::testCommodityAddition()
assertThrow(x1 + x4, amount_error);
assertThrow(x1 + x5, amount_error);
assertThrow(x1 + x6, amount_error);
#ifndef NOT_FOR_PYTHON
assertThrow(x1 + 123.45, amount_error);
#endif // NOT_FOR_PYTHON
assertThrow(x1 + 123L, amount_error);
assertEqual(amount_t("DM 246.90"), x3 + x3);
@ -652,7 +658,9 @@ void AmountTestCase::testCommoditySubtraction()
assertThrow(x1 - x4, amount_error);
assertThrow(x1 - x5, amount_error);
assertThrow(x1 - x6, amount_error);
#ifndef NOT_FOR_PYTHON
assertThrow(x1 - 123.45, amount_error);
#endif // NOT_FOR_PYTHON
assertThrow(x1 - 123L, amount_error);
assertEqual(amount_t("DM 0.00"), x3 - x3);
@ -857,11 +865,7 @@ void AmountTestCase::testIntegerDivision()
x1 /= amount_t(456L);
assertEqual(string("0.269737"), x1.to_string());
x1 /= 456L;
#ifdef INTEGER_MATH
assertEqual(string("0.00059152850877193"), x1.to_string());
#else
assertEqual(string("0.000591528162511542"), x1.to_string());
#endif
amount_t x4("123456789123456789123456789");
amount_t y4("56");
@ -901,13 +905,9 @@ void AmountTestCase::testFractionalDivision()
x1 /= amount_t("456.456");
assertEqual(string("0.269736842105263"), x1.to_string());
x1 /= amount_t("456.456");
#ifdef INTEGER_MATH
assertEqual(string("0.000590937225286255411255411255411255411"), x1.to_string());
#else
assertEqual(string("0.000590937225286255757169884601508201951"), x1.to_string());
#endif
x1 /= 456L;
assertEqual(string("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1.to_string());
assertEqual(string("0.000001295914967733017011337466214297678193292890066687335298289595263924317558590360"), x1.to_string());
amount_t x4("1234567891234567.89123456789");
amount_t y4("56.789");
@ -934,18 +934,18 @@ void AmountTestCase::testCommodityDivision()
assertThrow(x1 / 0L, amount_error);
assertEqual(amount_t("$0.00"), 0L / x1);
assertEqual(x1, x1 / 1L);
assertEqual(string("$0.00812216"), (1L / x1).to_string());
assertEqual(string("$0.00812216"), (1L / x1).to_fullstring());
assertEqual(- x1, x1 / -1L);
assertEqual(string("$-0.00812216"), (-1L / x1).to_string());
assertEqual(string("$0.26973382"), (x1 / y1).to_string());
assertEqual(string("$-0.00812216"), (-1L / x1).to_fullstring());
assertEqual(string("$0.26973382"), (x1 / y1).to_fullstring());
assertEqual(string("$0.27"), (x1 / y1).to_string());
assertEqual(string("$3.70735867"), (y1 / x1).to_string());
assertEqual(string("$3.70735867"), (y1 / x1).to_fullstring());
assertEqual(string("$3.71"), (y1 / x1).to_string());
// Internal amounts retain their precision, even when being
// converted to strings
assertEqual(string("$0.99727201"), (x1 / x2).to_string());
assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string());
assertEqual(string("$0.99727201"), (x1 / x2).to_fullstring());
assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_fullstring());
assertEqual(string("$1.00"), (x1 / x2).to_string());
assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string());
@ -959,10 +959,10 @@ void AmountTestCase::testCommodityDivision()
x1 /= amount_t("123.12");
assertEqual(string("$1.00"), x1.to_string());
x1 /= amount_t("123.12");
assertEqual(string("$0.00812216"), x1.to_string());
assertEqual(string("$0.00812216"), x1.to_fullstring());
assertEqual(string("$0.01"), x1.to_string());
x1 /= 123L;
assertEqual(string("$0.00006603"), x1.to_string());
assertEqual(string("$0.00006603"), x1.to_fullstring());
assertEqual(string("$0.00"), x1.to_string());
amount_t x6(internalAmount("$237235987235987.98723987235978"));
@ -970,9 +970,9 @@ void AmountTestCase::testCommodityDivision()
assertEqual(amount_t("$1"), x7 / x7);
assertEqual(string("$0.0019216115121765559608381226612019501046413574469262"),
(x6 / x7).to_string());
(x6 / x7).to_fullstring());
assertEqual(string("$520.39654928343335571379527154924040947271699678158689736256"),
(x7 / x6).to_string());
(x7 / x6).to_fullstring());
assertValid(x1);
assertValid(x2);
@ -1098,177 +1098,6 @@ void AmountTestCase::testCommodityAbs()
assertValid(x2);
}
#ifndef NOT_FOR_PYTHON
#ifdef INTEGER_MATH
void AmountTestCase::testFractionalRound()
{
amount_t x0;
amount_t x1("1234.567890");
assertThrow(x0.precision(), amount_error);
assertThrow(x0.round(), amount_error);
assertThrow(x0.round(2), amount_error);
assertThrow(x0.unround(), amount_error);
assertEqual(amount_t::precision_t(6), x1.precision());
amount_t x1b(x1.unround());
assertEqual(x1b.precision(), x1b.unround().precision());
amount_t y7(x1.round(7));
amount_t y6(x1.round(6));
amount_t y5(x1.round(5));
amount_t y4(x1.round(4));
amount_t y3(x1.round(3));
amount_t y2(x1.round(2));
amount_t y1(x1.round(1));
amount_t y0(x1.round(0));
assertEqual(amount_t::precision_t(6), y7.precision());
assertEqual(amount_t::precision_t(6), y6.precision());
assertEqual(amount_t::precision_t(5), y5.precision());
assertEqual(amount_t::precision_t(4), y4.precision());
assertEqual(amount_t::precision_t(3), y3.precision());
assertEqual(amount_t::precision_t(2), y2.precision());
assertEqual(amount_t::precision_t(1), y1.precision());
assertEqual(amount_t::precision_t(0), y0.precision());
assertEqual(amount_t("1234.56789"), y7);
assertEqual(amount_t("1234.56789"), y6);
assertEqual(amount_t("1234.56789"), y5);
assertEqual(amount_t("1234.5679"), y4);
assertEqual(amount_t("1234.568"), y3);
assertEqual(amount_t("1234.57"), y2);
assertEqual(amount_t("1234.6"), y1);
assertEqual(amount_t("1235"), y0);
amount_t x2("9876.543210");
assertEqual(amount_t("9876.543210"), x2.round(6));
assertEqual(amount_t("9876.54321"), x2.round(5));
assertEqual(amount_t("9876.5432"), x2.round(4));
assertEqual(amount_t("9876.543"), x2.round(3));
assertEqual(amount_t("9876.54"), x2.round(2));
assertEqual(amount_t("9876.5"), x2.round(1));
assertEqual(amount_t("9877"), x2.round(0));
amount_t x3("-1234.567890");
assertEqual(amount_t("-1234.56789"), x3.round(6));
assertEqual(amount_t("-1234.56789"), x3.round(5));
assertEqual(amount_t("-1234.5679"), x3.round(4));
assertEqual(amount_t("-1234.568"), x3.round(3));
assertEqual(amount_t("-1234.57"), x3.round(2));
assertEqual(amount_t("-1234.6"), x3.round(1));
assertEqual(amount_t("-1235"), x3.round(0));
amount_t x4("-9876.543210");
assertEqual(amount_t("-9876.543210"), x4.round(6));
assertEqual(amount_t("-9876.54321"), x4.round(5));
assertEqual(amount_t("-9876.5432"), x4.round(4));
assertEqual(amount_t("-9876.543"), x4.round(3));
assertEqual(amount_t("-9876.54"), x4.round(2));
assertEqual(amount_t("-9876.5"), x4.round(1));
assertEqual(amount_t("-9877"), x4.round(0));
amount_t x5("0.0000000000000000000000000000000000001");
assertEqual(amount_t("0.0000000000000000000000000000000000001"),
x5.round(37));
assertEqual(amount_t(0L), x5.round(36));
assertValid(x1);
assertValid(x2);
assertValid(x3);
assertValid(x4);
assertValid(x5);
}
void AmountTestCase::testCommodityRound()
{
amount_t x1(internalAmount("$1234.567890"));
assertEqual(internalAmount("$1234.56789"), x1.round(6));
assertEqual(internalAmount("$1234.56789"), x1.round(5));
assertEqual(internalAmount("$1234.5679"), x1.round(4));
assertEqual(internalAmount("$1234.568"), x1.round(3));
assertEqual(amount_t("$1234.57"), x1.round(2));
assertEqual(amount_t("$1234.6"), x1.round(1));
assertEqual(amount_t("$1235"), x1.round(0));
amount_t x2(internalAmount("$9876.543210"));
assertEqual(internalAmount("$9876.543210"), x2.round(6));
assertEqual(internalAmount("$9876.54321"), x2.round(5));
assertEqual(internalAmount("$9876.5432"), x2.round(4));
assertEqual(internalAmount("$9876.543"), x2.round(3));
assertEqual(amount_t("$9876.54"), x2.round(2));
assertEqual(amount_t("$9876.5"), x2.round(1));
assertEqual(amount_t("$9877"), x2.round(0));
amount_t x3(internalAmount("$-1234.567890"));
assertEqual(internalAmount("$-1234.56789"), x3.round(6));
assertEqual(internalAmount("$-1234.56789"), x3.round(5));
assertEqual(internalAmount("$-1234.5679"), x3.round(4));
assertEqual(internalAmount("$-1234.568"), x3.round(3));
assertEqual(amount_t("$-1234.57"), x3.round(2));
assertEqual(amount_t("$-1234.6"), x3.round(1));
assertEqual(amount_t("$-1235"), x3.round(0));
amount_t x4(internalAmount("$-9876.543210"));
assertEqual(internalAmount("$-9876.543210"), x4.round(6));
assertEqual(internalAmount("$-9876.54321"), x4.round(5));
assertEqual(internalAmount("$-9876.5432"), x4.round(4));
assertEqual(internalAmount("$-9876.543"), x4.round(3));
assertEqual(amount_t("$-9876.54"), x4.round(2));
assertEqual(amount_t("$-9876.5"), x4.round(1));
assertEqual(amount_t("$-9877"), x4.round(0));
amount_t x5("$123.45");
x5 *= amount_t("100.12");
assertEqual(internalAmount("$12359.814"), x5);
assertEqual(string("$12359.81"), x5.to_string());
assertEqual(string("$12359.814"), x5.to_fullstring());
assertEqual(string("$12359.814"), x5.unround().to_string());
assertValid(x1);
assertValid(x2);
assertValid(x3);
assertValid(x4);
assertValid(x5);
}
void AmountTestCase::testCommodityDisplayRound()
{
amount_t x1("$0.85");
amount_t x2("$0.1");
x1 *= amount_t("0.19");
assertNotEqual(amount_t("$0.16"), x1);
assertEqual(internalAmount("$0.1615"), x1);
assertEqual(string("$0.16"), x1.to_string());
assertEqual(amount_t("$0.10"), x2);
assertNotEqual(internalAmount("$0.101"), x2);
assertEqual(string("$0.10"), x2.to_string());
x1 *= 7L;
assertNotEqual(amount_t("$1.13"), x1);
assertEqual(internalAmount("$1.1305"), x1);
assertEqual(string("$1.13"), x1.to_string());
}
#endif // INTEGER_MATH
#endif
void AmountTestCase::testReduction()
{
amount_t x0;
@ -1411,10 +1240,7 @@ void AmountTestCase::testFractionalConversion()
amount_t x1("1234.56");
amount_t x2("1234.5683787634678348734");
assertThrow(x1.to_long(), amount_error); // loses precision
assertThrow(x2.to_double(), amount_error); // loses precision
assertFalse(x2.fits_in_double());
assertEqual(1234L, x1.to_long(true));
assertEqual(1235L, x1.to_long());
assertEqual(1234.56, x1.to_double());
assertEqual(string("1234.56"), x1.to_string());
assertEqual(string("1234.56"), x1.quantity_string());
@ -1426,8 +1252,7 @@ void AmountTestCase::testCommodityConversion()
{
amount_t x1("$1234.56");
assertThrow(x1.to_long(), amount_error); // loses precision
assertEqual(1234L, x1.to_long(true));
assertEqual(1235L, x1.to_long());
assertEqual(1234.56, x1.to_double());
assertEqual(string("$1234.56"), x1.to_string());
assertEqual(string("1234.56"), x1.quantity_string());
@ -1436,6 +1261,7 @@ void AmountTestCase::testCommodityConversion()
}
#ifndef NOT_FOR_PYTHON
void AmountTestCase::testPrinting()
{
amount_t x0;
@ -1579,4 +1405,5 @@ void AmountTestCase::testXmlSerialization()
assertEqual(std::string("<amount>\n <commodity flags=\"PT\">\n <symbol>$</symbol>\n </commodity>\n <quantity>8192.34</quantity>\n</amount>\n"), storage.str());
}
#endif // NOT_FOR_PYTHON

View file

@ -32,11 +32,6 @@ class AmountTestCase : public CPPUNIT_NS::TestCase
CPPUNIT_TEST(testCommodityNegation);
CPPUNIT_TEST(testAbs);
CPPUNIT_TEST(testCommodityAbs);
#ifdef INTEGER_MATH
CPPUNIT_TEST(testFractionalRound);
CPPUNIT_TEST(testCommodityRound);
CPPUNIT_TEST(testCommodityDisplayRound);
#endif
CPPUNIT_TEST(testReduction);
CPPUNIT_TEST(testSign);
CPPUNIT_TEST(testCommoditySign);

View file

@ -75,22 +75,27 @@ void CommodityTestCase::testPriceHistory()
amt = x1.value(current_time);
assertTrue(amt);
assertEqual(amount_t("$2124.12"), *amt);
assertEqual(string("$2124.12"), amt->to_string());
#ifdef INTEGER_MATH
assertEqual(string("$2124.12"), amt->to_fullstring());
#else
assertEqual(string("$2124.1220"), amt->to_fullstring());
#endif
amt = x1.value(current_time, euro);
assertTrue(amt);
assertEqual(amount_t("EUR 1366.87"), *amt);
assertEqual(string("EUR 1366.87"), amt->rounded().to_string());
// Add a newer Euro pricing
aapl.add_price(jan17_07, amount_t("EUR 23.00"));
amt = x1.value(current_time, euro);
assertTrue(amt);
assertEqual(amount_t("EUR 2302.30"), *amt);
assertEqual(string("EUR 2302.30"), amt->to_string());
amt = x1.value(current_time, cad);
assertTrue(amt);
assertEqual(amount_t("CAD 3223.22"), *amt);
assertEqual(string("CAD 3223.22"), amt->to_string());
#endif // NOT_FOR_PYTHON
assertValid(x1);