diff options
-rw-r--r-- | src/amount.cc | 858 | ||||
-rw-r--r-- | src/amount.h | 523 | ||||
-rw-r--r-- | src/py_amount.cc | 7 | ||||
-rw-r--r-- | src/textual.cc | 2 | ||||
-rw-r--r-- | tests/numerics/BasicAmount.cc | 20 | ||||
-rw-r--r-- | tests/numerics/BasicAmount.h | 2 |
6 files changed, 833 insertions, 579 deletions
diff --git a/src/amount.cc b/src/amount.cc index 7b8b1a91..348fda64 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -45,12 +45,12 @@ namespace ledger { -bool do_cleanup = true; +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::keep_base = false; + bool amount_t::full_strings = false; #define BIGINT_BULK_ALLOC 0x0001 @@ -99,7 +99,7 @@ static amount_t::bigint_t * true_value = NULL; inline amount_t::bigint_t::~bigint_t() { TRACE_DTOR(bigint_t); - assert(ref == 0 || (! do_cleanup && this == true_value)); + assert(ref == 0 || this == true_value); mpz_clear(val); } @@ -164,18 +164,6 @@ void amount_t::shutdown() true_value = NULL; } -void amount_t::_release() -{ - DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); - - if (--quantity->ref == 0) { - if (! (quantity->flags & BIGINT_BULK_ALLOC)) - checked_delete(quantity); - else - quantity->~bigint_t(); - } -} - void amount_t::_init() { if (! quantity) { @@ -187,15 +175,6 @@ void amount_t::_init() } } -void amount_t::_dup() -{ - if (quantity->ref > 1) { - bigint_t * q = new bigint_t(*quantity); - _release(); - quantity = q; - } -} - void amount_t::_copy(const amount_t& amt) { if (quantity != amt.quantity) { @@ -216,6 +195,15 @@ void amount_t::_copy(const amount_t& amt) commodity_ = amt.commodity_; } +void amount_t::_dup() +{ + if (quantity->ref > 1) { + bigint_t * q = new bigint_t(*quantity); + _release(); + quantity = q; + } +} + void amount_t::_resize(precision_t prec) { assert(prec < 256); @@ -247,31 +235,19 @@ void amount_t::_clear() } } - -amount_t::amount_t(const long val) +void amount_t::_release() { - TRACE_CTOR(amount_t, "const long"); - if (val != 0) { - quantity = new bigint_t; - mpz_set_si(MPZ(quantity), val); - } else { - quantity = NULL; - } - commodity_ = NULL; -} + DEBUG("amounts.refs", quantity << " ref--, now " << (quantity->ref - 1)); -amount_t::amount_t(const unsigned long val) -{ - TRACE_CTOR(amount_t, "const unsigned long"); - if (val != 0) { - quantity = new bigint_t; - mpz_set_ui(MPZ(quantity), val); - } else { - quantity = NULL; + if (--quantity->ref == 0) { + if (! (quantity->flags & BIGINT_BULK_ALLOC)) + checked_delete(quantity); + else + quantity->~bigint_t(); } - commodity_ = NULL; } + namespace { amount_t::bigint_t::precision_t convert_double(mpz_t dest, double val) { @@ -366,6 +342,34 @@ amount_t::amount_t(const double val) commodity_ = NULL; } +amount_t::amount_t(const unsigned long val) +{ + TRACE_CTOR(amount_t, "const unsigned long"); + quantity = new bigint_t; + mpz_set_ui(MPZ(quantity), val); + commodity_ = NULL; +} + +amount_t::amount_t(const long val) +{ + TRACE_CTOR(amount_t, "const long"); + quantity = new bigint_t; + mpz_set_si(MPZ(quantity), val); + commodity_ = NULL; +} + + +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 { @@ -398,18 +402,6 @@ int amount_t::compare(const amount_t& amt) const } -// assignment operator -amount_t& amount_t::operator=(const amount_t& amt) -{ - if (this != &amt) { - if (amt.quantity) - _copy(amt); - else if (quantity) - _clear(); - } - return *this; -} - amount_t& amount_t::operator+=(const amount_t& amt) { if (commodity() != amt.commodity()) @@ -622,79 +614,13 @@ amount_t& amount_t::operator/=(const amount_t& amt) return *this; } -// unary negation -void amount_t::in_place_negate() + +amount_t& amount_t::in_place_negate() { if (quantity) { _dup(); mpz_neg(MPZ(quantity), MPZ(quantity)); } -} - -int amount_t::sign() const -{ - return quantity ? mpz_sgn(MPZ(quantity)) : 0; -} - -bool amount_t::zero() const -{ - if (! quantity) - return true; - - if (has_commodity()) { - if (quantity->prec <= commodity().precision()) - return realzero(); - else - return round(commodity().precision()).sign() == 0; - } - return realzero(); -} - -long amount_t::to_long() const -{ - if (! quantity) - return 0; - - mpz_set(temp, MPZ(quantity)); - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_q(temp, temp, divisor); - - return mpz_get_si(temp); -} - -double amount_t::to_double() const -{ - if (! quantity) - return 0.0; - - 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); - - return lexical_cast<double>(num.str()); -} - -amount_t amount_t::value(const moment_t& moment) const -{ - if (quantity) { - amount_t amt(commodity().value(moment)); - if (! amt.realzero()) - return (amt * number()).round(); - } return *this; } @@ -739,281 +665,205 @@ amount_t amount_t::unround() const return t; } -void amount_t::print_quantity(std::ostream& out) const +amount_t& amount_t::in_place_reduce() { - if (! quantity) { - out << "0"; - return; - } - - 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(commodity()); - bigint_t::precision_t precision; - - if (! comm || quantity->flags & BIGINT_KEEP_PREC) { - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); - precision = quantity->prec; - } - else if (comm.precision() < quantity->prec) { - mpz_round(rquotient, MPZ(quantity), 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() > quantity->prec) { - mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec); - mpz_mul(rquotient, MPZ(quantity), divisor); - mpz_ui_pow_ui(divisor, 10, comm.precision()); - mpz_tdiv_qr(quotient, remainder, rquotient, divisor); - precision = comm.precision(); - } - else if (quantity->prec) { - mpz_ui_pow_ui(divisor, 10, quantity->prec); - mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor); - precision = quantity->prec; - } - else { - mpz_set(quotient, MPZ(quantity)); - mpz_set_ui(remainder, 0); - precision = 0; + while (commodity_ && commodity().smaller()) { + *this *= commodity().smaller()->number(); + commodity_ = commodity().smaller()->commodity_; } + return *this; +} - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { - negative = true; - - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); +amount_t& amount_t::in_place_unreduce() +{ + while (commodity_ && commodity().larger()) { + *this /= commodity().larger()->number(); + commodity_ = commodity().larger()->commodity_; + if (abs() < amount_t(1.0)) + break; } - mpz_set(rquotient, remainder); + return *this; +} - if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { - out << "0"; - return; +amount_t amount_t::value(const moment_t& moment) const +{ + if (quantity) { + amount_t amt(commodity().value(moment)); + if (! amt.realzero()) + return (amt * number()).round(); } + return *this; +} - if (negative) - out << "-"; - if (mpz_sgn(quotient) == 0) { - out << '0'; - } else { - char * p = mpz_get_str(NULL, 10, quotient); - out << p; - std::free(p); - } - - if (precision) { - out << '.'; +int amount_t::sign() const +{ + return quantity ? mpz_sgn(MPZ(quantity)) : 0; +} - out.width(precision); - out.fill('0'); +bool amount_t::zero() const +{ + if (! quantity) + return true; - char * p = mpz_get_str(NULL, 10, rquotient); - out << p; - std::free(p); + if (has_commodity()) { + if (quantity->prec <= commodity().precision()) + return realzero(); + else + return round(commodity().precision()).sign() == 0; } - - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); + return realzero(); } -void amount_t::print(std::ostream& _out, bool omit_commodity, - bool full_precision) const -{ - amount_t base(*this); - if (! amount_t::keep_base && commodity().larger()) { - amount_t last(*this); - while (last.commodity().larger()) { - last /= last.commodity().larger()->number(); - last.commodity_ = last.commodity().larger()->commodity_; - if (last.abs() < amount_t(1.0)) - break; - base = last.round(); - } - } - std::ostringstream out; +double amount_t::to_double() const +{ + if (! quantity) + return 0.0; - mpz_t quotient; - mpz_t rquotient; mpz_t remainder; - - mpz_init(quotient); - mpz_init(rquotient); mpz_init(remainder); - bool negative = false; + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_qr(temp, remainder, temp, divisor); - // Ensure the value is rounded to the commodity's precision before - // outputting it. NOTE: `rquotient' is used here as a temp variable! + char * quotient_s = mpz_get_str(NULL, 10, temp); + char * remainder_s = mpz_get_str(NULL, 10, remainder); - commodity_t& comm(base.commodity()); - bigint_t::precision_t precision = 0; + std::ostringstream num; + num << quotient_s << '.' << remainder_s; - if (quantity) { - if (! comm || full_precision || base.quantity->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; - } + std::free(quotient_s); + std::free(remainder_s); - if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) { - negative = true; + mpz_clear(remainder); - mpz_abs(quotient, quotient); - mpz_abs(remainder, remainder); - } - mpz_set(rquotient, remainder); - } + return lexical_cast<double>(num.str()); +} - if (! omit_commodity && ! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { - comm.write(out); +long amount_t::to_long() const +{ + if (! quantity) + return 0; - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; - } + mpz_set(temp, MPZ(quantity)); + mpz_ui_pow_ui(divisor, 10, quantity->prec); + mpz_tdiv_q(temp, temp, divisor); - if (negative) - out << "-"; + return mpz_get_si(temp); +} - if (! quantity || mpz_sgn(quotient) == 0) { - out << '0'; - } - else if (! (comm.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); - } +void amount_t::annotate_commodity(const optional<amount_t>& tprice, + const optional<moment_t>& tdate, + const optional<string>& ttag) +{ + const commodity_t * this_base; + annotated_commodity_t * this_ann = NULL; - bool printed = false; + if (commodity().annotated) { + this_ann = &static_cast<annotated_commodity_t&>(commodity()); + this_base = this_ann->ptr; + } else { + this_base = &commodity(); + } + assert(this_base); - for (std::list<string>::reverse_iterator i = strs.rbegin(); - i != strs.rend(); - i++) { - if (printed) { - out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ','); - out.width(3); - out.fill('0'); - } - out << *i; + DEBUG("amounts.commodities", "Annotating commodity for amount " + << *this << std::endl + << " price " << (tprice ? tprice->to_string() : "NONE") << " " + << " date " << (tdate ? *tdate : moment_t()) << " " + << " ttag " << (ttag ? *ttag : "NONE")); - printed = true; - } - } + if (commodity_t * ann_comm = + annotated_commodity_t::find_or_create + (*this_base, + ! tprice && this_ann ? this_ann->price : tprice, + ! tdate && this_ann ? this_ann->date : tdate, + ! ttag && this_ann ? this_ann->tag : ttag)) + set_commodity(*ann_comm); - 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); + DEBUG("amounts.commodities", " Annotated amount is " << *this); +} - 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; +amount_t amount_t::strip_annotations(const bool _keep_price, + const bool _keep_date, + const bool _keep_tag) const +{ + if (! commodity().annotated || + (_keep_price && _keep_date && _keep_tag)) + return *this; - string ender; - if (i == len) - ender = str; - else if (i < comm.precision()) - ender = string(str, 0, comm.precision()); - else - ender = string(str, 0, i); + DEBUG("amounts.commodities", "Reducing commodity for amount " + << *this << std::endl + << " keep price " << _keep_price << " " + << " keep date " << _keep_date << " " + << " keep tag " << _keep_tag); - if (! ender.empty()) { - out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); - out << ender; - } - } + annotated_commodity_t& + ann_comm(static_cast<annotated_commodity_t&>(commodity())); + assert(ann_comm.base); - if (! omit_commodity && comm.flags() & COMMODITY_STYLE_SUFFIXED) { - if (comm.flags() & COMMODITY_STYLE_SEPARATED) - out << " "; + commodity_t * new_comm; - comm.write(out); + if ((_keep_price && ann_comm.price) || + (_keep_date && ann_comm.date) || + (_keep_tag && ann_comm.tag)) + { + new_comm = annotated_commodity_t::find_or_create + (*ann_comm.ptr, + _keep_price ? ann_comm.price : optional<amount_t>(), + _keep_date ? ann_comm.date : optional<moment_t>(), + _keep_tag ? ann_comm.tag : optional<string>()); + } else { + new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); } + assert(new_comm); - mpz_clear(quotient); - mpz_clear(rquotient); - mpz_clear(remainder); + amount_t t(*this); + t.set_commodity(*new_comm); + DEBUG("amounts.commodities", " Reduced amount is " << t); - // If there are any annotations associated with this commodity, - // output them now. + return t; +} - if (! omit_commodity && comm.annotated) { - annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); - assert(&*ann.price != this); - ann.write_annotations(out); +optional<amount_t> amount_t::price() const +{ + if (commodity_ && commodity_->annotated && + ((annotated_commodity_t *)commodity_)->price) { + amount_t t(*((annotated_commodity_t *)commodity_)->price); + t *= number(); + DEBUG("amounts.commodities", + "Returning price of " << *this << " = " << t); + return t; } + return optional<amount_t>(); +} - // 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(); +optional<moment_t> amount_t::date() const +{ + if (commodity_ && commodity_->annotated) { + DEBUG("amounts.commodities", + "Returning date of " << *this << " = " + << ((annotated_commodity_t *)commodity_)->date); + return ((annotated_commodity_t *)commodity_)->date; + } + return optional<moment_t>(); +} - return; +optional<string> amount_t::tag() const +{ + if (commodity_ && commodity_->annotated) { + DEBUG("amounts.commodities", + "Returning tag of " << *this << " = " + << ((annotated_commodity_t *)commodity_)->tag); + return ((annotated_commodity_t *)commodity_)->tag; + } + return optional<string>(); } + static void parse_quantity(std::istream& in, string& value) { char buf[256]; @@ -1287,16 +1137,8 @@ void amount_t::parse(std::istream& in, uint8_t flags) in_place_reduce(); } -void amount_t::in_place_reduce() -{ - while (commodity_ && commodity().smaller()) { - *this *= commodity().smaller()->number(); - commodity_ = commodity().smaller()->commodity_; - } -} - -void parse_conversion(const string& larger_str, - const string& smaller_str) +void amount_t::parse_conversion(const string& larger_str, + const string& smaller_str) { amount_t larger, smaller; @@ -1314,6 +1156,186 @@ void parse_conversion(const string& larger_str, smaller.commodity().set_larger(larger); } + +void amount_t::print(std::ostream& _out, bool omit_commodity, + bool full_precision) const +{ + 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()); + bigint_t::precision_t precision = 0; + + if (quantity) { + if (! comm || full_precision || base.quantity->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.flags() & COMMODITY_STYLE_SUFFIXED)) { + comm.write(out); + + if (comm.flags() & COMMODITY_STYLE_SEPARATED) + out << " "; + } + + if (negative) + out << "-"; + + if (! quantity || mpz_sgn(quotient) == 0) { + out << '0'; + } + else if (omit_commodity || ! (comm.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.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.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.'); + out << ender; + } + } + + if (! omit_commodity && comm.flags() & COMMODITY_STYLE_SUFFIXED) { + if (comm.flags() & COMMODITY_STYLE_SEPARATED) + out << " "; + + comm.write(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.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(); + + return; +} + + void amount_t::read(std::istream& in) { commodity_t::ident_t ident; @@ -1352,7 +1374,6 @@ void amount_t::write(std::ostream& out) const write_quantity(out); } - #ifndef THREADSAFE static char * bigints; static char * bigints_next; @@ -1476,6 +1497,7 @@ void amount_t::write_quantity(std::ostream& out) const } } + bool amount_t::valid() const { if (quantity) { @@ -1491,112 +1513,4 @@ bool amount_t::valid() const return true; } -void amount_t::annotate_commodity(const optional<amount_t>& tprice, - const optional<moment_t>& tdate, - const optional<string>& ttag) -{ - const commodity_t * this_base; - annotated_commodity_t * this_ann = NULL; - - if (commodity().annotated) { - this_ann = &static_cast<annotated_commodity_t&>(commodity()); - this_base = this_ann->ptr; - } else { - this_base = &commodity(); - } - assert(this_base); - - DEBUG("amounts.commodities", "Annotating commodity for amount " - << *this << std::endl - << " price " << (tprice ? tprice->to_string() : "NONE") << " " - << " date " << (tdate ? *tdate : moment_t()) << " " - << " ttag " << (ttag ? *ttag : "NONE")); - - if (commodity_t * ann_comm = - annotated_commodity_t::find_or_create - (*this_base, - ! tprice && this_ann ? this_ann->price : tprice, - ! tdate && this_ann ? this_ann->date : tdate, - ! ttag && this_ann ? this_ann->tag : ttag)) - set_commodity(*ann_comm); - - DEBUG("amounts.commodities", " Annotated amount is " << *this); -} - -amount_t amount_t::strip_annotations(const bool _keep_price, - const bool _keep_date, - const bool _keep_tag) const -{ - if (! commodity().annotated || - (_keep_price && _keep_date && _keep_tag)) - return *this; - - DEBUG("amounts.commodities", "Reducing commodity for amount " - << *this << std::endl - << " keep price " << _keep_price << " " - << " keep date " << _keep_date << " " - << " keep tag " << _keep_tag); - - annotated_commodity_t& - ann_comm(static_cast<annotated_commodity_t&>(commodity())); - assert(ann_comm.base); - - commodity_t * new_comm; - - if ((_keep_price && ann_comm.price) || - (_keep_date && ann_comm.date) || - (_keep_tag && ann_comm.tag)) - { - new_comm = annotated_commodity_t::find_or_create - (*ann_comm.ptr, - _keep_price ? ann_comm.price : optional<amount_t>(), - _keep_date ? ann_comm.date : optional<moment_t>(), - _keep_tag ? ann_comm.tag : optional<string>()); - } else { - new_comm = commodity_t::find_or_create(ann_comm.base_symbol()); - } - assert(new_comm); - - amount_t t(*this); - t.set_commodity(*new_comm); - DEBUG("amounts.commodities", " Reduced amount is " << t); - - return t; -} - -optional<amount_t> amount_t::price() const -{ - if (commodity_ && commodity_->annotated && - ((annotated_commodity_t *)commodity_)->price) { - amount_t t(*((annotated_commodity_t *)commodity_)->price); - t *= number(); - DEBUG("amounts.commodities", - "Returning price of " << *this << " = " << t); - return t; - } - return optional<amount_t>(); -} - -optional<moment_t> amount_t::date() const -{ - if (commodity_ && commodity_->annotated) { - DEBUG("amounts.commodities", - "Returning date of " << *this << " = " - << ((annotated_commodity_t *)commodity_)->date); - return ((annotated_commodity_t *)commodity_)->date; - } - return optional<moment_t>(); -} - -optional<string> amount_t::tag() const -{ - if (commodity_ && commodity_->annotated) { - DEBUG("amounts.commodities", - "Returning tag of " << *this << " = " - << ((annotated_commodity_t *)commodity_)->tag); - return ((annotated_commodity_t *)commodity_)->tag; - } - return optional<string>(); -} - } // namespace ledger diff --git a/src/amount.h b/src/amount.h index 47fc913d..0c9501bc 100644 --- a/src/amount.h +++ b/src/amount.h @@ -3,14 +3,14 @@ * @author John Wiegley * @date Wed Apr 18 22:05:53 2007 * - * @brief Types for handling commoditized math. + * @brief Basic type for handling commoditized math: amount_t. * - * This file contains two of the most basic types in Ledger: amount_t - * commodity_t, and annotated_commodity_t. Both the commodity types - * share a common base class, commodity_base_t. These four class - * together allow Ledger to handle mathematical expressions involving - * differing commodities, or in some cases math using no commodities - * at all (such as increasing a dollar amount by a multiplier). + * This file contains the most basic numerical type in Ledger: + * amount_t, which relies upon commodity.h (commodity_t) for handling + * commoditized amounts. This class allows Ledger to handle + * mathematical expressions involving differing commodities, as well + * as math using no commodities at all (such as increasing a dollar + * amount by a multiplier). */ /* @@ -51,8 +51,6 @@ namespace ledger { -extern bool do_cleanup; - class commodity_t; DECLARE_EXCEPTION(amount_error); @@ -79,45 +77,95 @@ class amount_t public: class bigint_t; - // jww (2007-05-01): Change my uses of unsigned int to use this type - // for precision values. Or perhaps just std::size_t? typedef uint_least16_t precision_t; + /** + * The initialize and shutdown methods ready the amount subsystem + * for use. Normally they are called by `ledger::initialize' and + * `ledger::shutdown'. + */ static void initialize(); static void shutdown(); + /** + * The `keep_base' member determines whether scalable commodities + * are automatically converted to their most reduced form when + * printing. The default is true. + * + * For example, Ledger supports time values specified in seconds + * (10s), hours (5.2h) or minutes. Internally, such amounts are + * always kept as quantities of seconds. However, when streaming + * the amount Ledger will convert it to its "least representation", + * which is "5.2h" in the second case. If `keep_base' is true, this + * amount is displayed as "18720s". + */ + static bool keep_base; + + /** + * The following three members determine whether lot details are + * maintained when working with commoditized values. The default is + * false for all three. + * + * Let's say a user adds two values of the following form: + * 10 AAPL + 10 AAPL {$20} + * + * This expression adds ten shares of Apple stock with another ten + * shares that were purchased for $20 a share. If `keep_price' is + * false, the result of this expression will be an amount equal to + * 20 AAPL. If `keep_price' is true, the expression yields an + * exception for adding amounts with different commodities. In that + * case, a balance_t object must be used to store the combined sum. + */ static bool keep_price; static bool keep_date; static bool keep_tag; - static bool keep_base; + + /** + * The `full-strings' static member is currently only used by the + * unit testing code. It causes amounts written to streams to use + * the `to_fullstring' method rather than the `to_string' method, so + * that complete precision is always displayed, no matter what the + * precision of an individual commodity might be. + * @see to_string + * @see to_fullstring + */ static bool full_strings; protected: void _init(); void _copy(const amount_t& amt); - void _release(); void _dup(); void _resize(precision_t prec); void _clear(); + void _release(); bigint_t * quantity; commodity_t * commodity_; public: - // constructors + /** + * Constructors. amount_t supports several forms of construction: + * + * amount_t() creates a value for which `is_null' is true, and which + * has no value or commodity. If used in value situations it will + * be zero, and its commodity equals `commodity_t::null_commodity'. + * + * amount_t(double), amount_t(unsigned long), amount_t(long) all + * convert from the respective numerics type to an amount. No + * precision or sign is lost in any of these conversions. The + * resulting commodity is always `commodity_t::null_commodity'. + * + * amount_t(string), amount_t(char*) both convert from a string + * representation of an amount, which may or may not include a + * commodity. This is the proper way to initialize an amount like + * '$100.00'. + */ amount_t() : quantity(NULL), commodity_(NULL) { TRACE_CTOR(amount_t, ""); } - amount_t(const amount_t& amt) : quantity(NULL) { - TRACE_CTOR(amount_t, "copy"); - if (amt.quantity) - _copy(amt); - else - commodity_ = NULL; - } - amount_t(const long val); - amount_t(const unsigned long val); amount_t(const double val); + amount_t(const unsigned long val); + amount_t(const long val); amount_t(const string& val) : quantity(NULL) { TRACE_CTOR(amount_t, "const string&"); @@ -128,19 +176,63 @@ public: parse(val); } + /** + * Static creator function. Calling amount_t::exact(string) will + * create an amount whose display precision is never truncated, even + * if the amount uses a commodity (which normally causes "round on + * streaming" to occur). This function is mostly used by the + * debugging code. It is the proper way to initialize '$100.005', + * where display of the extra precision is required. If a regular + * constructor is used, this amount will stream as '$100.01', even + * though its internal value always equals $100.005. + */ + static amount_t exact(const string& value); + + /** + * Destructor. Releases the reference count held for the underlying + * bigint_t object. + */ ~amount_t() { TRACE_DTOR(amount_t); if (quantity) _release(); } - static amount_t exact(const string& value); - - // assignment operator + /** + * Assignment and copy operators. An amount may be assigned or + * copied. If a double, long or unsigned long is assigned to an + * amount, a temporary is constructed, and then the temporary is + * assigned to `this'. Both the value and the commodity are copied, + * causing the result to compare equal to the reference amount. + * + * Note: `quantity' must be initialized to NULL first, otherwise the + * `_copy' function will attempt to release the unitialized pointer. + */ + amount_t(const amount_t& amt) : quantity(NULL) { + TRACE_CTOR(amount_t, "copy"); + if (amt.quantity) + _copy(amt); + else + commodity_ = NULL; + } amount_t& operator=(const amount_t& amt); - // comparisons between amounts + /** + * Comparison operators. The fundamental comparison operation for + * amounts is `compare', which returns a value less than, greater + * than or equal to zero. All the other comparison operators are + * defined in terms of this method. The only special detail is that + * `operator==' will fail immediately if amounts with different + * commodities are being compared. Otherwise, if the commodities + * are equivalent (@see keep_price, et al), then the amount + * quantities are compared numerically. + * + * Comparison between an amount and a double, long or unsigned long + * is allowed. In such cases the non-amount value is constructed as + * an amount temporary, which is then compared to `this'. + */ int compare(const amount_t& amt) const; + bool operator==(const amount_t& amt) const; template <typename T> @@ -156,24 +248,125 @@ public: return compare(amt) > 0; } - // in-place arithmetic + /** + * Binary arithmetic operators. Amounts support addition, + * subtraction, multiplication and division -- but not modulus, + * bitwise operations, or shifting. Arithmetic is also supported + * between amounts, double, long and unsigned long, in which case + * temporary amount are constructed for the life of the expression. + * + * Although only in-place operators are defined here, the remainder + * are provided by `boost::ordered_field_operators<>'. + */ amount_t& operator+=(const amount_t& amt); amount_t& operator-=(const amount_t& amt); amount_t& operator*=(const amount_t& amt); amount_t& operator/=(const amount_t& amt); - // unary negation - amount_t operator-() const { - return negate(); - } + /** + * Unary arithmetic operators. There are several unary methods + * support on amounts: + * + * negate(), also unary minus (- x), returns the negated value of an + * amount. + * + * abs() returns the absolute value of an amount. It is equivalent + * to: `(x < 0) ? - x : x'. + * + * round(precision_t) rounds an amount's internal value to the given + * precision. + * + * round() rounds an amount to its commodity's current display + * precision. This also changes the internal value of the amount. + * + * unround() yields an amount whose display precision is never + * truncated, even though its commodity normally displays only + * rounded values. + * + * reduce() reduces a value to its most basic commodity form, for + * amounts that utilize "scaling commodities". For example, an + * amount of 1h after reduction will be 3600s. + * + * unreduce(), if used with a "scaling commodity", yields the most + * compact form greater than 1.0. That is, 3599s will unreduce to + * 59.98m, while 3601 unreduces to 1h. + * + * value(moment_t) returns the history value of an amount, based on + * the price history of its commodity. For example, if the amount + * were 10 AAPL, and on Apr 10, 2000 each share of AAPL was worth + * $10, then call value() for that moment in time would yield the + * amount $100.00. + * + * Further, for the sake of efficiency and avoiding temporary + * objects, the following methods support "in-place" variants that + * act on the value itself and return a reference to the result + * (`*this'): + * + * in_place_negate() + * in_place_reduce() + * in_place_unreduce() + */ amount_t negate() const { amount_t temp = *this; temp.in_place_negate(); return temp; } - void in_place_negate(); + amount_t& in_place_negate(); + + amount_t operator-() const { + return negate(); + } + + amount_t abs() const { + if (sign() < 0) + return negate(); + return *this; + } + + amount_t round(precision_t prec) const; + amount_t round() const; + amount_t unround() const; + + amount_t reduce() const { + amount_t temp(*this); + temp.in_place_reduce(); + return temp; + } + amount_t& in_place_reduce(); + + amount_t unreduce() const { + amount_t temp(*this); + temp.in_place_unreduce(); + return temp; + } + amount_t& in_place_unreduce(); + + amount_t value(const moment_t& moment) const; + + /** + * Truth tests. An amount may be truth test in several ways: + * + * sign() returns an integer less than, greater than, or equal to + * zero depending on whether an amount is negative, zero, or greater + * than zero. Note that this function tests the actual value of the + * amount -- using its internal precision -- and not the display + * value. To test its display value, use: `round().sign()'. + * + * nonzero(), or operator bool, returns true if an amount's display + * value is not zero. + * + * zero() returns true if an amount's display value is zero. Thus, + * $0.0001 is considered zero(). + * + * realzero() returns true if an amount's actual value is zero. + * $0.0001 is not considered realzero(). + * + * is_null() returns true if an amount has no value and no + * commodity. This occurs only if an unitialized amount has never + * been assigned a value. + */ + int sign() const; - // test for truth, zero and non-zero operator bool() const { return nonzero(); } @@ -181,22 +374,77 @@ public: return ! zero(); } - int sign() const; bool zero() const; bool realzero() const { return sign() == 0; } - // conversion methods - long to_long() const; + bool is_null() const { + return ! quantity && ! has_commodity(); + } + + /** + * Conversion methods. An amount may be converted to the same types + * it can be constructed from -- with the exception of unsigned + * long. Implicit conversions are not allowed in C++ (though they + * are in Python), rather the following conversion methods must be + * called explicitly: + * + * to_double() returns an amount as a double. Note: precision is + * very likely to be lost in this conversion! + * + * to_long() returns an amount as a long integer. This is only + * useful if the amount is know to be of a small, integral value. + * + * to_string() returns an amount'ss "display value" as a string -- + * after rounding the value according to the commodity's default + * precision. It is equivalent to: `round().to_fullstring()'. + * + * to_fullstring() returns an amount's "internal value" as a string, + * without any rounding. + * + * quantity_string() returns an amount's "display value", but + * without any commodity. Note that this is different from + * `number().to_string()', because in that case the commodity has + * been stripped and the full, internal precision of the amount + * would be displayed. + */ double to_double() const; + long to_long() const; string to_string() const; string to_fullstring() const; string quantity_string() const; - // methods relating to the commodity - bool is_null() const { - return ! quantity && ! has_commodity(); + /** + * Commodity-related methods. The following methods relate to an + * amount's commodity: + * + * has_commodity() returns true if the amount has a commodity. + * + * commodity() returns an amount's commodity. If the amount has no + * commodity, then the value returned will be equal to + * `commodity_t::null_commodity'. + * + * set_commodity(commodity_t) sets an amount's commodity to the + * given value. Note that this merely sets the current amount to + * that commodity, it does not "observe" the amount for possible + * changes in the maximum display precision of the commodity, the + * way that `parse' does. + * + * clear_commodity() sets an amount's commodity to null, such that + * has_commodity() afterwards returns false. + * + * number() returns a commodity-less version of an amount. This is + * useful for accessing just the numeric portion of an amount. + */ + bool has_commodity() const; + commodity_t& commodity() const; + + void set_commodity(commodity_t& comm) { + commodity_ = &comm; + } + void clear_commodity() { + commodity_ = NULL; } amount_t number() const { @@ -207,16 +455,35 @@ public: return temp; } - bool has_commodity() const; - void set_commodity(commodity_t& comm) { - commodity_ = &comm; - } - void clear_commodity() { - commodity_ = NULL; - } - - commodity_t& commodity() const; - + /** + * Annotated commodity methods. An amount's commodity may be + * annotated with special details, such as the price it was + * purchased for, when it was acquired, or an arbitrary note, + * identifying perhaps the lot number of an item. + * + * annotate_commodity(amount_t price, [moment_t date, string tag]) + * sets the annotations for the current amount's commodity. Only + * the price argument is required, although it can be passed as + * `optional<amount_t>()' if no price is desired. + * + * strip_annotations([keep_price, keep_date, keep_tag]) returns an + * amount whose commodity's annotations have been stripped. The + * three `keep_' arguments determine which annotation detailed are + * kept, meaning that the default is to follow whatever + * amount_t::keep_price, amount_t::keep_date and amount_t::keep_tag + * have been set to (which all default to false). + * + * price() returns an amount's annotated commodity's price. This + * return value is of type `optional<amount_t>', so it must be + * tested for boolean truth to determine if an annotated price even + * existed. + * + * date() returns an amount's annotated commodity's date. This + * return value is of type `optional<moment_t>'. + * + * tag() returns an amount's annotated commodity's tag. This return + * value is of type `optional<string>'. + */ void annotate_commodity(const optional<amount_t>& tprice, const optional<moment_t>& tdate = optional<moment_t>(), const optional<string>& ttag = optional<string>()); @@ -229,67 +496,128 @@ public: optional<moment_t> date() const; optional<string> tag() const; - // general methods - amount_t round(precision_t prec) const; - amount_t round() const; - amount_t unround() const; - amount_t value(const moment_t& moment) const; - - amount_t abs() const { - if (sign() < 0) - return negate(); - return *this; - } - - amount_t reduce() const { - amount_t temp(*this); - temp.in_place_reduce(); - return temp; - } - void in_place_reduce(); - - bool valid() const; - - // This function is special, and exists only to support a custom - // optimization in binary.cc (which offers a significant enough gain - // to be worth the trouble). - - friend void clean_commodity_history(char * item_pool, - char * item_pool_end); - - friend void parse_annotations(std::istream& in, - optional<amount_t>& price, - optional<moment_t>& date, - optional<string>& tag); - - // Streaming interface - - void dump(std::ostream& out) const { - out << "AMOUNT("; - print(out); - out << ")"; - } - #define AMOUNT_PARSE_NO_MIGRATE 0x01 #define AMOUNT_PARSE_NO_REDUCE 0x02 - void print(std::ostream& out, bool omit_commodity = false, - bool full_precision = false) const; + /** + * Parsing methods. The method `parse' is used to parse an amount + * from an input stream or a string. A global operator>> is also + * defined which simply calls parse on the input stream. The + * `parse' method has two forms: + * + * parse(istream, unsigned char flags) parses an amount from the + * given input stream. + * + * parse(string, unsigned char flags) parses an amount from the + * given string. + * + * The `flags' argument of both parsing may be one or more of the + * following: + * + * AMOUNT_PARSE_NO_MIGRATE means to not pay attention to the way an + * amount is used. Ordinarily, if an amount were $100.001, for + * example, it would cause the default display precision for $ to be + * "widened" to three decimal places. If AMOUNT_PARSE_NO_MIGRATE is + * used, the commodity's default display precision is not changed. + * + * AMOUNT_PARSE_NO_REDUCE means not to call in_place_reduce() on the + * resulting amount after it is parsed. + * + * These parsing methods observe the amounts they parse (unless + * AMOUNT_PARSE_NO_MIGRATE is true), and set the display details of + * the corresponding commodity accordingly. This way, amounts do + * not require commodities to be pre-defined in any way, but merely + * displays them back to the user in the same fashion as it saw them + * used. + * + * There is also a static convenience method called + * `parse_conversion' which can be used to define a relationship + * between scaling commodity values. For example, Ledger uses it to + * define the relationships among various time values: + * + * amount_t::parse_conversion("1.0m", "60s"); // a minute is 60 seconds + * amount_t::parse_conversion("1.0h", "60m"); // an hour is 60 minutes + */ void parse(std::istream& in, unsigned char flags = 0); void parse(const string& str, unsigned char flags = 0) { std::istringstream stream(str); parse(stream, flags); } - void print_quantity(std::ostream& out) const; + static void parse_conversion(const string& larger_str, + const string& smaller_str); + + /** + * Printing methods. An amount may be output to a stream using the + * `print' method. There is also a global operator<< defined which + * simply calls print for an amount on the given stream. There is + * one form of the print method, which takes one required argument + * and two arguments with default values: + * + * print(ostream, bool omit_commodity = false, bool full_precision = + * false) prits an amounts to the given output stream, using its + * commodity's default display characteristics. If `omit_commodity' + * is true, the commodity will not be displayed, only the amount + * (although the commodity's display precision is still used). If + * `full_precision' is true, the full internal precision of the + * amount is displayed, regardless of its commodity's display + * precision. + */ + void print(std::ostream& out, bool omit_commodity = false, + bool full_precision = false) const; - void write(std::ostream& out) const; + /** + * Serialization methods. An amount may be deserialized from an + * input stream or a character pointer, and it may be serialized to + * an output stream. The methods used are: + * + * read(istream) reads an amount from the given input stream. It + * must have been put there using `write(ostream)'. + * + * read(char *&) reads an amount from data which has been read from + * an input stream into a buffer. it advances the pointer passed in + * to the end of the deserialized amount. + * + * write(ostream) writes an amount to an output stream in a compact + * binary format. + */ void read(std::istream& in); void read(char *& data); - void write_quantity(std::ostream& out) const; + void write(std::ostream& out) const; + +private: void read_quantity(std::istream& in); void read_quantity(char *& data); + void write_quantity(std::ostream& out) const; + +public: + /** + * Debugging methods. There are two methods defined to help with + * debugging: + * + * dump(ostream) dumps an amount to an output stream. There is + * little different from print(), it simply surrounds the display + * value with a marker, for example "AMOUNT($1.00)". This code is + * used by other dumping code elsewhere in Ledger. + * + * valid() returns true if an amount is valid. This ensures that if + * an amount has a commodity, it has a valid value pointer, for + * example, even if that pointer simply points to a zero value. + */ + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } + + bool valid() const; + +private: + friend void parse_annotations(std::istream& in, + optional<amount_t>& price, + optional<moment_t>& date, + optional<string>& tag); }; inline amount_t amount_t::exact(const string& value) { @@ -351,9 +679,6 @@ inline commodity_t& amount_t::commodity() const { return has_commodity() ? *commodity_ : *commodity_t::null_commodity; } -void parse_conversion(const string& larger_str, - const string& smaller_str); - } // namespace ledger #endif // _AMOUNT_H diff --git a/src/py_amount.cc b/src/py_amount.cc index 89960fdf..e39f32e0 100644 --- a/src/py_amount.cc +++ b/src/py_amount.cc @@ -7,13 +7,6 @@ namespace ledger { using namespace boost::python; -int py_amount_quantity(amount_t& amount) -{ - std::ostringstream quant; - amount.print_quantity(quant); - return std::atol(quant.str().c_str()); -} - void py_parse_1(amount_t& amount, const string& str, unsigned char flags) { amount.parse(str, flags); diff --git a/src/textual.cc b/src/textual.cc index da7cfbd6..0a4c7333 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -730,7 +730,7 @@ unsigned int textual_parser_t::parse(std::istream& in, case 'C': // a set of conversions if (char * p = std::strchr(line + 1, '=')) { *p++ = '\0'; - parse_conversion(line + 1, p); + amount_t::parse_conversion(line + 1, p); } break; diff --git a/tests/numerics/BasicAmount.cc b/tests/numerics/BasicAmount.cc index bcc5c2b5..6ce39d1d 100644 --- a/tests/numerics/BasicAmount.cc +++ b/tests/numerics/BasicAmount.cc @@ -600,6 +600,26 @@ void BasicAmountTestCase::testAbs() CPPUNIT_ASSERT(x2.valid()); } +void BasicAmountTestCase::testReduction() +{ + amount_t x1("60s"); + amount_t x2("600s"); + amount_t x3("6000s"); + amount_t x4("360000s"); + amount_t x5("10m"); // 600s + amount_t x6("100m"); // 6000s + amount_t x7("1000m"); // 60000s + amount_t x8("10000m"); // 600000s + amount_t x9("10h"); // 36000s + amount_t x10("100h"); // 360000s + amount_t x11("1000h"); // 3600000s + amount_t x12("10000h"); // 36000000s + + assertEqual(x2, x5); + assertEqual(x3, x6); + assertEqual(x4, x10); +} + void BasicAmountTestCase::testPrinting() { amount_t x0; diff --git a/tests/numerics/BasicAmount.h b/tests/numerics/BasicAmount.h index 2c107f45..a6c8aff7 100644 --- a/tests/numerics/BasicAmount.h +++ b/tests/numerics/BasicAmount.h @@ -27,6 +27,7 @@ class BasicAmountTestCase : public CPPUNIT_NS::TestCase CPPUNIT_TEST(testComparisons); CPPUNIT_TEST(testSign); CPPUNIT_TEST(testAbs); + CPPUNIT_TEST(testReduction); CPPUNIT_TEST(testPrinting); CPPUNIT_TEST_SUITE_END(); @@ -58,6 +59,7 @@ public: void testComparisons(); void testSign(); void testAbs(); + void testReduction(); void testPrinting(); private: |