diff options
-rw-r--r-- | src/amount.cc | 196 | ||||
-rw-r--r-- | src/amount.h | 2 | ||||
-rw-r--r-- | src/commodity.cc | 2 | ||||
-rw-r--r-- | test/unit/t_amount.cc | 87 |
4 files changed, 157 insertions, 130 deletions
diff --git a/src/amount.cc b/src/amount.cc index 9c41885e..bc7d5a6a 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -53,6 +53,7 @@ static mpfr_t tempf; static mpz_t divisor; #else static mpq_t tempq; +static mpfr_t tempfb; #endif #endif @@ -66,7 +67,7 @@ struct amount_t::bigint_t : public supports_flags<> #else mpq_t val; #endif - precision_t prec; // this is only an estimate + precision_t prec; uint_least16_t ref; uint_fast32_t index; @@ -80,20 +81,6 @@ struct amount_t::bigint_t : public supports_flags<> mpq_init(val); #endif } -#ifdef INTEGER_MATH - bigint_t(mpz_t _val) : prec(0), ref(1), index(0) { - TRACE_CTOR(bigint_t, "mpz_t"); - mpz_init_set(val, _val); - } -#else -#if 0 - bigint_t(mpq_t _val) : prec(0), ref(1), index(0) { - TRACE_CTOR(bigint_t, "mpq_t"); - mpq_init(val, _val); - mpq_set(val, _val); - } -#endif -#endif bigint_t(const bigint_t& other) : supports_flags<>(other.flags() & ~BIGINT_BULK_ALLOC), prec(other.prec), ref(1), index(0) { @@ -143,6 +130,7 @@ void amount_t::initialize() mpz_init(divisor); #else mpq_init(tempq); + mpfr_init(tempfb); #endif one = new amount_t(amount_t(1L).unround()); @@ -170,6 +158,7 @@ void amount_t::shutdown() mpz_clear(divisor); #else mpq_clear(tempq); + mpfr_clear(tempfb); #endif checked_delete(one); @@ -275,6 +264,7 @@ amount_t::amount_t(const double val) : commodity_(NULL) 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 @@ -593,7 +583,6 @@ amount_t& amount_t::operator/=(const amount_t& amt) return *this; } - amount_t::precision_t amount_t::precision() const { if (! quantity) @@ -624,7 +613,8 @@ void amount_t::set_keep_precision(const bool keep) const quantity->drop_flags(BIGINT_KEEP_PREC); } -amount_t::precision_t amount_t::display_precision(const bool full_precision) const +amount_t::precision_t +amount_t::display_precision(const bool full_precision) const { if (! quantity) throw_(amount_error, @@ -636,10 +626,8 @@ amount_t::precision_t amount_t::display_precision(const bool full_precision) con return quantity->prec; else if (comm.precision() != quantity->prec) return comm.precision(); - else if (quantity->prec) + else return quantity->prec; - - return 0; } amount_t& amount_t::in_place_negate() @@ -768,33 +756,97 @@ int amount_t::sign() const #endif } +#ifndef INTEGER_MATH + +namespace { + void stream_out_mpq(std::ostream& out, mpq_t quant, + amount_t::precision_t prec, + const optional<commodity_t&>& comm = none) + { + char * buf = NULL; + try { + IF_DEBUG("amount.convert") { + char * tbuf = mpq_get_str(NULL, 10, quant); + DEBUG("amount.convert", "Rational to convert = " << tbuf); + std::free(tbuf); + } + + // Convert the rational number to a floating-point, extending the + // floating-point to a large enough size to get a precise answer. + const std::size_t bits = (mpz_sizeinbase(mpq_numref(quant), 2) + + mpz_sizeinbase(mpq_denref(quant), 2)); + mpfr_set_prec(tempfb, bits + amount_t::extend_by_digits*8); + mpfr_set_q(tempfb, quant, GMP_RNDN); + + mpfr_asprintf(&buf, "%.*Rf", prec, tempfb); + DEBUG("amount.convert", + "mpfr_print = " << buf << " (precision " << prec << ")"); + + if (comm) { + int integer_digits = 0; + if (comm && comm->has_flags(COMMODITY_STYLE_THOUSANDS)) { + // Count the number of integer digits + for (const char * p = buf; *p; p++) { + if (*p == '.') + break; + else if (std::isdigit(*p)) + integer_digits++; + } + } + + for (const char * p = buf; *p; p++) { + if (*p == '.') { + if (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)) + out << ','; + else + out << *p; + assert(integer_digits < 3); + } else { + if (integer_digits >= 3 && std::isdigit(*p) && + integer_digits-- % 3 == 0) { + if (comm && comm->has_flags(COMMODITY_STYLE_EUROPEAN)) + out << '.'; + else + out << ','; + } + out << *p; + } + } + } else { + out << buf; + } + } + catch (...) { + if (buf != NULL) + mpfr_free_str(buf); + throw; + } + if (buf != NULL) + mpfr_free_str(buf); + } +} + +#endif // INTEGER_MATH + bool amount_t::is_zero() const { if (! quantity) throw_(amount_error, "Cannot determine if an uninitialized amount is zero"); if (has_commodity()) { - if (quantity->prec <= commodity().precision() || keep_precision()) { + if (keep_precision() || quantity->prec <= commodity().precision()) { return is_realzero(); } else { #ifdef INTEGER_MATH return round(commodity().precision()).sign() == 0; #else - char * buf; - - mpfr_set_q(tempf, MP(quantity), GMP_RNDN); - mpfr_asprintf(&buf, "%.*RNf", commodity().precision(), tempf); - - bool all_zeroes = true; - for (const char * p = buf; *p; p++) { - if (*p != '0' || *p != '.') { - all_zeroes = false; - break; - } - } - - mpfr_free_str(buf); - return all_zeroes; + std::ostringstream out; + stream_out_mpq(out, MP(quantity), commodity().precision()); + + for (const char * p = out.str().c_str(); *p; p++) + if (*p != '0' && *p != '.') + return false; + return true; #endif } } @@ -827,15 +879,15 @@ double amount_t::to_double(bool no_check) const 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; -#else - mpfr_set_q(tempf, MP(quantity), GMP_RNDN); - return mpfr_get_d(tempf, GMP_RNDN); -#endif } long amount_t::to_long(bool no_check) const @@ -847,11 +899,11 @@ long amount_t::to_long(bool no_check) const 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); - mpfr_get_z(temp, tempf, GMP_RNDN); + long value = mpfr_get_si(tempf, GMP_RNDN); #endif - long value = mpz_get_si(temp); if (! no_check && *this != value) throw_(amount_error, "Conversion of amount to_long loses precision"); @@ -1125,6 +1177,12 @@ bool amount_t::parse(std::istream& in, const parse_flags_t& flags) mpz_ui_pow_ui(temp, 10, quantity->prec); mpq_set_z(tempq, temp); mpq_div(MP(quantity), MP(quantity), tempq); + + IF_DEBUG("amount.parse") { + char * buf = mpq_get_str(NULL, 10, MP(quantity)); + DEBUG("amount.parse", "Rational parsed = " << buf); + std::free(buf); + } #endif } else { #ifdef INTEGER_MATH @@ -1335,54 +1393,14 @@ void amount_t::print(std::ostream& _out, bool omit_commodity, #else // INTEGER_MATH - char * buf; - mpfr_set_q(tempf, MP(quantity), GMP_RNDN); - mpfr_asprintf(&buf, "%.*RNf", base.display_precision(full_precision), tempf); - DEBUG("amount.print", "mpfr_print = " << buf); - - try { - if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { - comm.print(out); - if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) - out << " "; - } - - if (omit_commodity || ! comm.has_flags(COMMODITY_STYLE_THOUSANDS)) { - if (! comm.has_flags(COMMODITY_STYLE_EUROPEAN)) - out << buf; - else - for (const char * p = buf; *p; p++) - if (*p == '.') - out << ','; - else - out << *p; - } else { - // Count the number of integer digits - int integer_digits = 0; - for (const char * p = buf; *p; p++) { - if (*p == '.') - break; - else if (std::isdigit(*p)) - integer_digits++; - } - - for (const char * p = buf; *p; p++) { - if (*p == '.' && comm.has_flags(COMMODITY_STYLE_EUROPEAN)) - out << ','; - else - out << *p; - - if (std::isdigit(*p) && integer_digits > 3 && - --integer_digits % 3 == 0) - out << ','; - } - } - } - catch (...) { - mpfr_free_str(buf); - throw; + if (! omit_commodity && ! comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { + comm.print(out); + if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) + out << " "; } - mpfr_free_str(buf); + + stream_out_mpq(out, MP(quantity), base.display_precision(full_precision), + omit_commodity ? optional<commodity_t&>() : comm); if (! omit_commodity && comm.has_flags(COMMODITY_STYLE_SUFFIXED)) { if (comm.has_flags(COMMODITY_STYLE_SEPARATED)) diff --git a/src/amount.h b/src/amount.h index 906aef0b..d15a0205 100644 --- a/src/amount.h +++ b/src/amount.h @@ -147,7 +147,9 @@ public: protected: void _copy(const amount_t& amt); void _dup(); +#ifdef INTEGER_MATH void _resize(precision_t prec); +#endif void _clear(); void _release(); diff --git a/src/commodity.cc b/src/commodity.cc index 60ddaec6..0a751941 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -663,7 +663,7 @@ void annotation_t::parse(std::istream& in) // may have only specified {$1} or something similar. if (temp.has_commodity() && - temp.precision() < temp.commodity().precision()) + temp.precision() > temp.commodity().precision()) temp = temp.round(); // no need to retain individual precision #endif diff --git a/test/unit/t_amount.cc b/test/unit/t_amount.cc index 484a51bc..3b199f5b 100644 --- a/test/unit/t_amount.cc +++ b/test/unit/t_amount.cc @@ -843,27 +843,31 @@ void AmountTestCase::testIntegerDivision() assertEqual(amount_t(0L), amount_t(0L) / x1); assertEqual(amount_t(0L), 0L / x1); assertEqual(x1, x1 / 1L); - assertEqual(amount_t("0.008130"), amount_t(1L) / x1); - assertEqual(amount_t("0.008130"), 1L / x1); + assertEqual(string("0.008130"), (amount_t(1L) / x1).to_string()); + assertEqual(string("0.008130"), (1L / x1).to_string()); assertEqual(- x1, x1 / -1L); - assertEqual(- amount_t("0.008130"), amount_t(-1L) / x1); - assertEqual(- amount_t("0.008130"), -1L / x1); - assertEqual(amount_t("0.269737"), x1 / y1); - assertEqual(amount_t("3.707317"), y1 / x1); - assertEqual(amount_t("0.269737"), x1 / 456L); - assertEqual(amount_t("3.707317"), amount_t(456L) / x1); - assertEqual(amount_t("3.707317"), 456L / x1); + assertEqual(string("-0.008130"), (amount_t(-1L) / x1).to_string()); + assertEqual(string("-0.008130"), (-1L / x1).to_string()); + assertEqual(string("0.269737"), (x1 / y1).to_string()); + assertEqual(string("3.707317"), (y1 / x1).to_string()); + assertEqual(string("0.269737"), (x1 / 456L).to_string()); + assertEqual(string("3.707317"), (amount_t(456L) / x1).to_string()); + assertEqual(string("3.707317"), (456L / x1).to_string()); x1 /= amount_t(456L); - assertEqual(amount_t("0.269737"), x1); + assertEqual(string("0.269737"), x1.to_string()); x1 /= 456L; - assertEqual(amount_t("0.00059152850877193"), x1); +#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"); assertEqual(amount_t(1L), x4 / x4); - assertEqual(amount_t("2204585520061728377204585.517857"), x4 / y4); + assertEqual(string("2204585520061728377204585.517857"), (x4 / y4).to_string()); assertEqual(amount_t("0.000000000000000000000000000001"), amount_t("10") / amount_t("10000000000000000000000000000000")); @@ -880,32 +884,36 @@ void AmountTestCase::testFractionalDivision() amount_t y1("456.456"); assertThrow(x1 / 0L, amount_error); - assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1); - assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1); + assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string()); + assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string()); assertEqual(x1, x1 / amount_t("1.0")); - assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1); - assertEqual(amount_t("0.00812195934"), amount_t("1.0") / x1); + assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string()); + assertEqual(string("0.00812195934"), (amount_t("1.0") / x1).to_string()); assertEqual(- x1, x1 / amount_t("-1.0")); - assertEqual(- amount_t("0.00812195934"), amount_t("-1.0") / x1); - assertEqual(- amount_t("0.00812195934"), amount_t("-1.0") / x1); - assertEqual(amount_t("0.269736842105263"), x1 / y1); - assertEqual(amount_t("3.707317073170732"), y1 / x1); - assertEqual(amount_t("0.269736842105263"), x1 / amount_t("456.456")); - assertEqual(amount_t("3.707317073170732"), amount_t("456.456") / x1); - assertEqual(amount_t("3.707317073170732"), amount_t("456.456") / x1); + assertEqual(string("-0.00812195934"), (amount_t("-1.0") / x1).to_string()); + assertEqual(string("-0.00812195934"), (amount_t("-1.0") / x1).to_string()); + assertEqual(string("0.269736842105263"), (x1 / y1).to_string()); + assertEqual(string("3.707317073170732"), (y1 / x1).to_string()); + assertEqual(string("0.269736842105263"), (x1 / amount_t("456.456")).to_string()); + assertEqual(string("3.707317073170732"), (amount_t("456.456") / x1).to_string()); + assertEqual(string("3.707317073170732"), (amount_t("456.456") / x1).to_string()); x1 /= amount_t("456.456"); - assertEqual(amount_t("0.269736842105263"), x1); + assertEqual(string("0.269736842105263"), x1.to_string()); x1 /= amount_t("456.456"); - assertEqual(amount_t("0.000590937225286255411255411255411255411"), x1); +#ifdef INTEGER_MATH + assertEqual(string("0.000590937225286255411255411255411255411"), x1.to_string()); +#else + assertEqual(string("0.000590937225286255757169884601508201951"), x1.to_string()); +#endif x1 /= 456L; - assertEqual(amount_t("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1); + assertEqual(string("0.000001295914967733016252753094858358016252192982456140350877192982456140350877192982"), x1.to_string()); amount_t x4("1234567891234567.89123456789"); amount_t y4("56.789"); assertEqual(amount_t("1.0"), x4 / x4); - assertEqual(amount_t("21739560323910.7554497273748437197344556164046"), x4 / y4); + assertEqual(string("21739560323910.7554497273748437197344556164046"), (x4 / y4).to_string()); assertValid(x1); assertValid(y1); @@ -926,18 +934,18 @@ void AmountTestCase::testCommodityDivision() assertThrow(x1 / 0L, amount_error); assertEqual(amount_t("$0.00"), 0L / x1); assertEqual(x1, x1 / 1L); - assertEqual(internalAmount("$0.00812216"), 1L / x1); + assertEqual(string("$0.00812216"), (1L / x1).to_string()); assertEqual(- x1, x1 / -1L); - assertEqual(internalAmount("$-0.00812216"), -1L / x1); - assertEqual(internalAmount("$0.26973382"), x1 / y1); + assertEqual(string("$-0.00812216"), (-1L / x1).to_string()); + assertEqual(string("$0.26973382"), (x1 / y1).to_string()); assertEqual(string("$0.27"), (x1 / y1).to_string()); - assertEqual(internalAmount("$3.70735867"), y1 / x1); + assertEqual(string("$3.70735867"), (y1 / x1).to_string()); assertEqual(string("$3.71"), (y1 / x1).to_string()); // Internal amounts retain their precision, even when being // converted to strings - assertEqual(internalAmount("$0.99727201"), x1 / x2); - assertEqual(internalAmount("$1.00273545321637426901"), x2 / x1); + assertEqual(string("$0.99727201"), (x1 / x2).to_string()); + assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string()); assertEqual(string("$1.00"), (x1 / x2).to_string()); assertEqual(string("$1.00273545321637426901"), (x2 / x1).to_string()); @@ -949,23 +957,22 @@ void AmountTestCase::testCommodityDivision() //assertThrow(x1 / x5, amount_error); x1 /= amount_t("123.12"); - assertEqual(internalAmount("$1.00"), x1); assertEqual(string("$1.00"), x1.to_string()); x1 /= amount_t("123.12"); - assertEqual(internalAmount("$0.00812216"), x1); + assertEqual(string("$0.00812216"), x1.to_string()); assertEqual(string("$0.01"), x1.to_string()); x1 /= 123L; - assertEqual(internalAmount("$0.00006603"), x1); + assertEqual(string("$0.00006603"), x1.to_string()); assertEqual(string("$0.00"), x1.to_string()); amount_t x6(internalAmount("$237235987235987.98723987235978")); amount_t x7(internalAmount("$123456789123456789.123456789123456789")); assertEqual(amount_t("$1"), x7 / x7); - assertEqual(internalAmount("$0.0019216115121765559608381226612019501046413574469262"), - x6 / x7); - assertEqual(internalAmount("$520.39654928343335571379527154924040947271699678158689736256"), - x7 / x6); + assertEqual(string("$0.0019216115121765559608381226612019501046413574469262"), + (x6 / x7).to_string()); + assertEqual(string("$520.39654928343335571379527154924040947271699678158689736256"), + (x7 / x6).to_string()); assertValid(x1); assertValid(x2); |