gnucash master: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Mon Feb 20 19:06:19 EST 2017
Updated via https://github.com/Gnucash/gnucash/commit/cbe52dad (commit)
via https://github.com/Gnucash/gnucash/commit/e322457e (commit)
via https://github.com/Gnucash/gnucash/commit/a193b9a2 (commit)
via https://github.com/Gnucash/gnucash/commit/0b09b58c (commit)
via https://github.com/Gnucash/gnucash/commit/d0726de3 (commit)
via https://github.com/Gnucash/gnucash/commit/b60d6a84 (commit)
via https://github.com/Gnucash/gnucash/commit/bff0e745 (commit)
via https://github.com/Gnucash/gnucash/commit/6220b850 (commit)
via https://github.com/Gnucash/gnucash/commit/ea44b16f (commit)
via https://github.com/Gnucash/gnucash/commit/739c91a4 (commit)
via https://github.com/Gnucash/gnucash/commit/e506f9a4 (commit)
via https://github.com/Gnucash/gnucash/commit/0403a666 (commit)
via https://github.com/Gnucash/gnucash/commit/c633e80a (commit)
via https://github.com/Gnucash/gnucash/commit/ff7e6a37 (commit)
via https://github.com/Gnucash/gnucash/commit/c3d22c42 (commit)
via https://github.com/Gnucash/gnucash/commit/4a46ae3d (commit)
via https://github.com/Gnucash/gnucash/commit/a54edf1a (commit)
via https://github.com/Gnucash/gnucash/commit/82fe06e3 (commit)
via https://github.com/Gnucash/gnucash/commit/4fef04c1 (commit)
via https://github.com/Gnucash/gnucash/commit/570c8a8d (commit)
via https://github.com/Gnucash/gnucash/commit/4a134ae0 (commit)
via https://github.com/Gnucash/gnucash/commit/06d22718 (commit)
via https://github.com/Gnucash/gnucash/commit/3975b0b4 (commit)
via https://github.com/Gnucash/gnucash/commit/6f5d628b (commit)
via https://github.com/Gnucash/gnucash/commit/b0dfd96a (commit)
via https://github.com/Gnucash/gnucash/commit/340fb976 (commit)
via https://github.com/Gnucash/gnucash/commit/a88d2124 (commit)
via https://github.com/Gnucash/gnucash/commit/e1b280b3 (commit)
via https://github.com/Gnucash/gnucash/commit/b1995932 (commit)
via https://github.com/Gnucash/gnucash/commit/a852dfb4 (commit)
via https://github.com/Gnucash/gnucash/commit/d9aa5e1a (commit)
via https://github.com/Gnucash/gnucash/commit/b5f06ab6 (commit)
via https://github.com/Gnucash/gnucash/commit/43fbb338 (commit)
via https://github.com/Gnucash/gnucash/commit/848f77da (commit)
via https://github.com/Gnucash/gnucash/commit/b30a547d (commit)
from https://github.com/Gnucash/gnucash/commit/2c5f6b9c (commit)
commit cbe52dad34d7c9c5ab54df36ac836054d61eaaa5
Merge: b30a547 e322457
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Feb 20 16:05:30 2017 -0800
Merge fetaure-branch 'numeric', provides GncNumeric C++ API.
commit e322457e45cfe199ce67b18c8abce683b791acec
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Feb 20 15:02:20 2017 -0800
Fix Geertâs code review comments.
Except the big one about string IO not being flexible enough.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 2c20646..907d8bc 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -44,6 +44,8 @@ extern "C"
#include "gnc-rational.hpp"
static QofLogModule log_module = "qof";
+
+static const uint8_t max_leg_digits{17};
static const int64_t pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000,
INT64_C(10000000000), INT64_C(100000000000),
@@ -57,8 +59,8 @@ static const int64_t pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
int64_t
powten (unsigned int exp)
{
- if (exp > 17)
- exp = 17;
+ if (exp > max_leg_digits)
+ exp = max_leg_digits;
return pten[exp];
}
@@ -80,7 +82,8 @@ GncNumeric::GncNumeric(GncRational rr)
GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
{
- if (isnan(d) || fabs(d) > 1e18)
+ static uint64_t max_leg_value{INT64_C(1000000000000000000)};
+ if (isnan(d) || fabs(d) > max_leg_value)
{
std::ostringstream msg;
msg << "Unable to construct a GncNumeric from " << d << ".\n";
@@ -90,9 +93,9 @@ GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
auto logval = log10(fabs(d));
int64_t den;
if (logval > 0.0)
- den = powten(18 - static_cast<int>(floor(logval) + 1.0));
+ den = powten((max_leg_digits + 1) - static_cast<int>(floor(logval) + 1.0));
else
- den = powten(17);
+ den = powten(max_leg_digits);
auto num = static_cast<int64_t>(floor(static_cast<double>(den) * d));
if (num == 0)
@@ -297,23 +300,28 @@ GncNumeric::to_string() const noexcept
return out.str();
}
+bool
+GncNumeric::is_decimal() const noexcept
+{
+ for (unsigned pwr = 0; pwr < max_leg_digits && m_den >= pten[pwr]; ++pwr)
+ {
+ if (m_den == pten[pwr])
+ return true;
+ if (m_den % pten[pwr])
+ return false;
+ }
+ return false;
+}
+
GncNumeric
GncNumeric::to_decimal(unsigned int max_places) const
{
- if (max_places > 17)
- max_places = 17;
- bool is_pwr_ten = true;
- for (int pwr = 1; pwr <= 17 && m_den > powten(pwr); ++pwr)
- if (m_den % powten(pwr))
- {
- is_pwr_ten = false;
- break;
- }
-
- if (m_num == 0 || (is_pwr_ten && m_den < powten(max_places)))
- return *this; // Nothing to do.
- if (is_pwr_ten)
+ if (max_places > max_leg_digits)
+ max_places = max_leg_digits;
+ if (is_decimal())
{
+ if (m_num == 0 || m_den < powten(max_places))
+ return *this; // Nothing to do.
/* See if we can reduce m_num to fit in max_places */
auto excess = m_den / powten(max_places);
if (m_num % excess)
@@ -1029,7 +1037,8 @@ gnc_numeric_reduce(gnc_numeric in)
gboolean
gnc_numeric_to_decimal(gnc_numeric *a, guint8 *max_decimal_places)
{
- int max_places = max_decimal_places == NULL ? 17 : *max_decimal_places;
+ int max_places = max_decimal_places == NULL ? max_leg_digits :
+ *max_decimal_places;
try
{
GncNumeric an (*a);
diff --git a/src/libqof/qof/gnc-numeric.hpp b/src/libqof/qof/gnc-numeric.hpp
index a29f447..b72eef6 100644
--- a/src/libqof/qof/gnc-numeric.hpp
+++ b/src/libqof/qof/gnc-numeric.hpp
@@ -25,6 +25,7 @@
#include <string>
#include <iostream>
+#include <locale>
#include "gnc-rational-rounding.hpp"
class GncRational;
@@ -170,8 +171,8 @@ public:
*/
GncNumeric abs() const noexcept;
/**
- * Reduce this to an equivalent fraction with the least common multiple as
- * the denominator.
+ * Return an equivalent fraction with all common factors between the
+ * numerator and the denominator removed.
*
* @return reduced GncNumeric
*/
@@ -227,8 +228,12 @@ public:
*/
std::string to_string() const noexcept;
/**
+ * @return true if the denominator is a power of ten, false otherwise.
+ */
+ bool is_decimal() const noexcept;
+ /**
* Convert the numeric to have a power-of-10 denominator if possible without
- * rounding. Throws a std::rane_error on failure; the message will explain
+ * rounding. Throws a std::range_error on failure; the message will explain
* the details.
*
* @param max_places exponent of the largest permissible denominator.
@@ -250,12 +255,14 @@ public:
void operator/=(GncNumeric b);
/* @} */
/** Compare function
- *
+ * \defgroup gnc_numeric_comparison
* @param b GncNumeric or int to compare to.
* @return -1 if this < b, 0 if ==, 1 if this > b.
+ * @{
*/
int cmp(GncNumeric b);
int cmp(int64_t b) { return cmp(GncNumeric(b, 1)); }
+ /** @} */
private:
struct round_param
{
@@ -339,12 +346,21 @@ template <typename charT, typename traits>
std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, GncNumeric n)
{
std::basic_ostringstream<charT, traits> ss;
+ std::locale loc = s.getloc();
+ ss.imbue(loc);
+ char dec_pt = '.';
+ try
+ {
+ dec_pt = std::use_facet<std::numpunct<char>>(loc).decimal_point();
+ }
+ catch(const std::bad_cast& err) {} //Don't do anything, num_sep is already set.
+
ss.copyfmt(s);
ss.width(0);
if (n.denom() == 1)
ss << n.num();
- else if (n.denom() % 10 == 0)
- ss << n.num() / n.denom() << "."
+ else if (n.is_decimal())
+ ss << n.num() / n.denom() << dec_pt
<< (n.num() > 0 ? n.num() : -n.num()) % n.denom();
else
ss << n.num() << "/" << n.denom();
@@ -369,7 +385,6 @@ std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>&
template <typename charT, typename traits>
std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& s, GncNumeric& n)
{
-
std::string instr;
s >> instr;
if (s)
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 1d04b9b..6eb1198 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -90,8 +90,8 @@ public:
/** Make a new GncRational with the opposite sign. */
GncRational operator-() const noexcept;
/**
- * Reduce this to an equivalent fraction with the least common multiple as
- * the denominator.
+ * Return an equivalent fraction with all common factors between the
+ * numerator and the denominator removed.
*
* @return reduced GncRational
*/
@@ -246,7 +246,7 @@ inline GncRational operator+(GncRational a, GncInt128 b)
}
inline GncRational operator+(GncInt128 a, GncRational b)
{
- return b + GncRational(a, 1);
+ return GncRational(a, 1) + a;
}
GncRational operator-(GncRational a, GncRational b);
inline GncRational operator-(GncRational a, GncInt128 b)
@@ -255,7 +255,7 @@ inline GncRational operator-(GncRational a, GncInt128 b)
}
inline GncRational operator-(GncInt128 a, GncRational b)
{
- return b - GncRational(a, 1);
+ return GncRational(a, 1) - b;
}
GncRational operator*(GncRational a, GncRational b);
inline GncRational operator*(GncRational a, GncInt128 b)
@@ -264,7 +264,7 @@ inline GncRational operator*(GncRational a, GncInt128 b)
}
inline GncRational operator*(GncInt128 a, GncRational b)
{
- return b * GncRational(a, 1);
+ return GncRational(a, 1) * b;
}
GncRational operator/(GncRational a, GncRational b);
inline GncRational operator/(GncRational a, GncInt128 b)
@@ -273,7 +273,7 @@ inline GncRational operator/(GncRational a, GncInt128 b)
}
inline GncRational operator/(GncInt128 a, GncRational b)
{
- return b / GncRational(a, 1);
+ return GncRational(a, 1) / b;
}
/** @} */
#endif //__GNC_RATIONAL_HPP__
diff --git a/src/libqof/qof/test/gtest-gnc-numeric.cpp b/src/libqof/qof/test/gtest-gnc-numeric.cpp
index 9ba7a68..34f56b1 100644
--- a/src/libqof/qof/test/gtest-gnc-numeric.cpp
+++ b/src/libqof/qof/test/gtest-gnc-numeric.cpp
@@ -211,6 +211,16 @@ TEST(gncnumeric_stream, output_stream)
GncNumeric rational_string(123, 456);
output << rational_string;
EXPECT_EQ("123/456", output.str());
+ output.imbue(std::locale("de_DE"));
+ output.str("");
+ output << simple_int;
+ EXPECT_EQ("123456", output.str());
+ output.str("");
+ output << decimal_string;
+ EXPECT_EQ("123,456", output.str());
+ output.str("");
+ output << rational_string;
+ EXPECT_EQ("123/456", output.str());
}
TEST(gncnumeric_stream, input_stream)
@@ -491,6 +501,14 @@ TEST(gncnumeric_functions, test_convert)
EXPECT_EQ(100, c.denom());
}
+TEST(gnc_numeric_functions, test_is_decimal)
+{
+ EXPECT_TRUE(GncNumeric(123, 1).is_decimal());
+ EXPECT_FALSE(GncNumeric(123, 3).is_decimal());
+ EXPECT_TRUE(GncNumeric(123, 1000).is_decimal());
+ EXPECT_FALSE(GncNumeric(123, 3200).is_decimal());
+}
+
TEST(gnc_numeric_functions, test_conversion_to_decimal)
{
GncNumeric a(123456789, 1000), r;
commit a193b9a2c12d60c34e24facf0c324ab55a9e00ee
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Feb 6 09:40:00 2017 -0800
Fix documentation errors.
diff --git a/src/libqof/qof/gnc-numeric.hpp b/src/libqof/qof/gnc-numeric.hpp
index fdb0107..a29f447 100644
--- a/src/libqof/qof/gnc-numeric.hpp
+++ b/src/libqof/qof/gnc-numeric.hpp
@@ -66,7 +66,7 @@ public:
*
* Unfortunately specifying a default for denom causes ambiguity errors with
* the other single-argument constructors on older versions of gcc, so one
- * must always specify both argumets.
+ * must always specify both arguments.
*
* \param num The Numerator
* \param denom The Denominator
@@ -145,7 +145,7 @@ public:
*/
operator gnc_numeric() const noexcept;
/**
- * gnc_numeric conversion. Use static_cast<double>(foo)
+ * double conversion. Use static_cast<double>(foo)
*/
operator double() const noexcept;
commit 0b09b58cf640aaeb5b995eccf76ea685c554fdf4
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Feb 3 10:49:09 2017 -0800
Relax comparison test for GncRational::round_to_numeric
To allow for a difference of 1 on very large numbers (>1e16). At the
same time made the test harder with a wider range, skipping cases where
the result overflows. Use GncRational::valid() instead of testing
components for both overflow and NaN.
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 382ebf5..8b002b8 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -133,12 +133,25 @@ TEST(gncrational_operators, test_division)
});
}
+static bool
+rounding_predicate(GncInt128 expected, GncInt128 result)
+{
+ static const uint64_t threshold{0x1fffffffffffff};
+
+ if (expected < threshold)
+ return expected == result;
+ auto difference = expected - result;
+ if (difference >= -1 && difference <= 1)
+ return true;
+ return false;
+}
+
TEST(gncrational_functions, test_round_to_numeric)
{
std::default_random_engine dre;
- std::uniform_int_distribution<int64_t> di{INT64_C(0x10000000000000),
+ std::uniform_int_distribution<int64_t> di{INT64_C(0x100000000000),
INT64_C(0x7fffffffffffff)};
- static const int reps{25};
+ static const int reps{100};
for (auto i = 0; i < reps; ++i)
{
GncRational a(di(dre), di(dre));
@@ -148,12 +161,13 @@ TEST(gncrational_functions, test_round_to_numeric)
expected = expected.convert<RoundType::bankers>(100);
auto rounded = c.round_to_numeric();
rounded = rounded.convert<RoundType::bankers>(100);
- EXPECT_EQ(0, expected.num() - rounded.num());
- EXPECT_FALSE(rounded.num().isBig());
- EXPECT_FALSE(rounded.denom().isBig());
- EXPECT_FALSE(rounded.num().isNan());
- EXPECT_FALSE(rounded.denom().isNan());
- EXPECT_FALSE(rounded.num().isOverflow());
- EXPECT_FALSE(rounded.denom().isOverflow());
+ if (rounded.is_big())
+ {
+ --i;
+ continue;
+ }
+ EXPECT_PRED2(rounding_predicate, expected.num(), rounded.num());
+ EXPECT_TRUE(rounded.valid());
+
}
}
commit d0726de39131ce50412407af89c611792933f1a0
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Feb 3 09:41:35 2017 -0800
Replace std::regex with boost::regex.
Gcc's std::regex compiler doesn't correctly recognize capture blocks.
diff --git a/src/libqof/CMakeLists.txt b/src/libqof/CMakeLists.txt
index adcaa6d..19b9ae0 100644
--- a/src/libqof/CMakeLists.txt
+++ b/src/libqof/CMakeLists.txt
@@ -95,7 +95,7 @@ ADD_LIBRARY (gnc-qof
${gnc_qof_noinst_HEADERS}
)
-TARGET_LINK_LIBRARIES(gnc-qof ${Boost_DATE_TIME_LIBRARIES} ${REGEX_LDFLAGS} ${GMODULE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS})
+TARGET_LINK_LIBRARIES(gnc-qof ${Boost_DATE_TIME_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${REGEX_LDFLAGS} ${GMODULE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS})
TARGET_COMPILE_DEFINITIONS (gnc-qof PRIVATE -DG_LOG_DOMAIN=\"qof\")
diff --git a/src/libqof/qof/Makefile.am b/src/libqof/qof/Makefile.am
index 6b77c4f..a526cd1 100644
--- a/src/libqof/qof/Makefile.am
+++ b/src/libqof/qof/Makefile.am
@@ -11,6 +11,7 @@ libgnc_qof_la_LDFLAGS= \
libgnc_qof_common_libs = \
$(GLIB_LIBS) \
$(REGEX_LIBS) \
+ ${BOOST_LDFLAGS} -lboost_regex \
$(top_builddir)/lib/libc/libc-missing.la
libgnc_qof_la_LIBADD = $(libgnc_qof_common_libs)
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 58697fd..2c20646 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -36,7 +36,7 @@ extern "C"
}
#include <stdint.h>
-#include <regex>
+#include <boost/regex.hpp>
#include <sstream>
#include <cstdlib>
@@ -103,6 +103,9 @@ GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
m_den = r.denom();
}
+using boost::regex;
+using boost::smatch;
+using boost::regex_search;
GncNumeric::GncNumeric(const std::string& str, bool autoround)
{
static const std::string numer_frag("(-?[0-9]+)");
@@ -113,26 +116,21 @@ GncNumeric::GncNumeric(const std::string& str, bool autoround)
* numer_frag patter with the default ECMAScript syntax so we use the awk
* syntax.
*/
- static const std::regex numeral(numer_frag, std::regex::awk);
- static const std::regex hex(hex_frag, std::regex::awk);
- static const std::regex numeral_rational(numer_frag + slash + denom_frag,
- std::regex::awk);
- static const std::regex hex_rational(hex_frag + slash + hex_frag,
- std::regex::awk);
- static const std::regex hex_over_num(hex_frag + slash + denom_frag,
- std::regex::awk);
- static const std::regex num_over_hex(numer_frag + slash + hex_frag,
- std::regex::awk);
- static const std::regex decimal(numer_frag + "[.,]" + denom_frag,
- std::regex::awk);
- std::smatch m;
+ static const regex numeral(numer_frag);
+ static const regex hex(hex_frag);
+ static const regex numeral_rational(numer_frag + slash + denom_frag);
+ static const regex hex_rational(hex_frag + slash + hex_frag);
+ static const regex hex_over_num(hex_frag + slash + denom_frag);
+ static const regex num_over_hex(numer_frag + slash + hex_frag);
+ static const regex decimal(numer_frag + "[.,]" + denom_frag);
+ smatch m;
/* The order of testing the regexes is from the more restrictve to the less
* restrictive, as less-restrictive ones will match patterns that would also
* match the more-restrictive and so invoke the wrong construction.
*/
if (str.empty())
throw std::invalid_argument("Can't construct a GncNumeric from an empty string.");
- if (std::regex_search(str, m, hex_rational))
+ if (regex_search(str, m, hex_rational))
{
GncNumeric n(stoll(m[1].str(), nullptr, 16),
stoll(m[2].str(), nullptr, 16));
@@ -140,7 +138,7 @@ GncNumeric::GncNumeric(const std::string& str, bool autoround)
m_den = n.denom();
return;
}
- if (std::regex_search(str, m, hex_over_num))
+ if (regex_search(str, m, hex_over_num))
{
GncNumeric n(stoll(m[1].str(), nullptr, 16),
stoll(m[2].str()));
@@ -148,7 +146,7 @@ GncNumeric::GncNumeric(const std::string& str, bool autoround)
m_den = n.denom();
return;
}
- if (std::regex_search(str, m, num_over_hex))
+ if (regex_search(str, m, num_over_hex))
{
GncNumeric n(stoll(m[1].str()),
stoll(m[2].str(), nullptr, 16));
@@ -156,14 +154,14 @@ GncNumeric::GncNumeric(const std::string& str, bool autoround)
m_den = n.denom();
return;
}
- if (std::regex_search(str, m, numeral_rational))
+ if (regex_search(str, m, numeral_rational))
{
GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
m_num = n.num();
m_den = n.denom();
return;
}
- if (std::regex_search(str, m, decimal))
+ if (regex_search(str, m, decimal))
{
GncInt128 high(stoll(m[1].str()));
GncInt128 low(stoll(m[2].str()));
@@ -193,14 +191,14 @@ GncNumeric::GncNumeric(const std::string& str, bool autoround)
m_den = gncn.denom();
return;
}
- if (std::regex_search(str, m, hex))
+ if (regex_search(str, m, hex))
{
GncNumeric n(stoll(m[1].str(), nullptr, 16),INT64_C(1));
m_num = n.num();
m_den = n.denom();
return;
}
- if (std::regex_search(str, m, numeral))
+ if (regex_search(str, m, numeral))
{
GncNumeric n(stoll(m[1].str()), INT64_C(1));
m_num = n.num();
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index 5fee07c..53f3cec 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -9,21 +9,21 @@ include $(top_srcdir)/test-templates/Makefile.decl
MODULEPATH = src/libqof/qof
test_qof_SOURCES = \
- test-gnc-date.c \
- test-qof.c \
- test-qofbook.c \
- test-qofinstance.cpp \
- test-qofobject.c \
- test-qof-string-cache.c \
- ${top_srcdir}/src/test-core/unittest-support.c
+ test-gnc-date.c \
+ test-qof.c \
+ test-qofbook.c \
+ test-qofinstance.cpp \
+ test-qofobject.c \
+ test-qof-string-cache.c \
+ ${top_srcdir}/src/test-core/unittest-support.c
test_qof_HEADERS = \
- $(top_srcdir)/${MODULEPATH}/qofbook.h \
- $(top_srcdir)/${MODULEPATH}/qofinstance.h \
- $(top_srcdir)/${MODULEPATH}/kvp_frame.hpp \
- $(top_srcdir)/${MODULEPATH}/qofobject.h \
- $(top_srcdir)/${MODULEPATH}/qofsession.h \
- $(top_srcdir)/src/test-core/unittest-support.h
+ $(top_srcdir)/${MODULEPATH}/qofbook.h \
+ $(top_srcdir)/${MODULEPATH}/qofinstance.h \
+ $(top_srcdir)/${MODULEPATH}/kvp_frame.hpp \
+ $(top_srcdir)/${MODULEPATH}/qofobject.h \
+ $(top_srcdir)/${MODULEPATH}/qofsession.h \
+ $(top_srcdir)/src/test-core/unittest-support.h
test_numeric_SOURCES = \
${top_srcdir}/src/engine/cashobjects.c \
@@ -40,11 +40,13 @@ test_numeric_CPPFLAGS = \
-I${top_srcdir}/src/engine/test-core \
-I${top_srcdir}/src \
-I${top_srcdir}/${MODULEPATH} \
+ ${BOOST_CPPFLAGS} \
${GLIB_CFLAGS}
test_numeric_LDADD = \
${top_builddir}/src/engine/libgncmod-engine.la \
${top_builddir}/${MODULEPATH}/libgnc-qof.la \
+ ${BOOST_LDFLAGS} -lboost_regex \
${GLIB_LIBS}
check_PROGRAMS = \
@@ -54,13 +56,13 @@ check_PROGRAMS = \
TESTS = ${check_PROGRAMS}
test_gnc_guid_SOURCES = \
- $(top_srcdir)/$(MODULEPATH)/guid.cpp \
- test-gnc-guid.cpp
+ $(top_srcdir)/$(MODULEPATH)/guid.cpp \
+ test-gnc-guid.cpp
test_gnc_guid_LDADD = \
- $(top_builddir)/$(MODULEPATH)/libgnc-qof.la \
+ $(top_builddir)/$(MODULEPATH)/libgnc-qof.la \
$(GLIB_LIBS) \
- $(GTEST_LIBS) \
- $(BOOST_LDFLAGS)
+ $(GTEST_LIBS) \
+ $(BOOST_LDFLAGS)
if !GOOGLE_TEST_LIBS
nodist_test_gnc_guid_SOURCES = \
${GTEST_SRC}/src/gtest_main.cc
@@ -79,10 +81,10 @@ test_kvp_value_SOURCES = \
test-kvp-value.cpp \
test-kvp-frame.cpp
test_kvp_value_LDADD = \
- $(top_builddir)/$(MODULEPATH)/libgnc-qof.la \
+ $(top_builddir)/$(MODULEPATH)/libgnc-qof.la \
$(GLIB_LIBS) \
- $(GTEST_LIBS) \
- $(BOOST_LDFLAGS)
+ $(GTEST_LIBS) \
+ $(BOOST_LDFLAGS)
if !GOOGLE_TEST_LIBS
nodist_test_kvp_value_SOURCES = \
@@ -98,24 +100,24 @@ test_kvp_value_CPPFLAGS = \
check_PROGRAMS += test-kvp-value
test_qofsession_SOURCES = \
- $(top_srcdir)/$(MODULEPATH)/qofsession.cpp \
- test-qofsession.cpp
+ $(top_srcdir)/$(MODULEPATH)/qofsession.cpp \
+ test-qofsession.cpp
test_qofsession_LDADD = \
- $(top_builddir)/$(MODULEPATH)/libgnc-qof.la \
- $(GLIB_LIBS) \
- $(GTEST_LIBS) \
- $(BOOST_LDFLAGS)
+ $(top_builddir)/$(MODULEPATH)/libgnc-qof.la \
+ $(GLIB_LIBS) \
+ $(GTEST_LIBS) \
+ $(BOOST_LDFLAGS) -lboost_regex
if !GOOGLE_TEST_LIBS
nodist_test_qofsession_SOURCES = \
- ${GTEST_SRC}/src/gtest_main.cc
+ ${GTEST_SRC}/src/gtest_main.cc
endif
test_qofsession_CPPFLAGS = \
- -I$(GTEST_HEADERS) \
- -I$(top_srcdir)/$(MODULEPATH) \
- $(GLIB_CFLAGS) \
- $(BOOST_CPPFLAGS)
+ -I$(GTEST_HEADERS) \
+ -I$(top_srcdir)/$(MODULEPATH) \
+ $(GLIB_CFLAGS) \
+ $(BOOST_CPPFLAGS)
check_PROGRAMS += test-qofsession
@@ -135,9 +137,9 @@ test_gnc_rational_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
$(top_srcdir)/${MODULEPATH}/qoflog.cpp \
gtest-gnc-rational.cpp
@@ -145,10 +147,12 @@ test_gnc_rational_CPPFLAGS = \
-I${top_srcdir}/src \
-I${top_srcdir}/src/libqof/qof \
-I${GTEST_HEADERS} \
+ ${BOOST_CPPFLAGS} \
${GLIB_CFLAGS}
test_gnc_rational_LDADD = \
${GTEST_LIBS} \
+ ${BOOST_LDFLAGS} -lboost_regex \
${GLIB_LIBS}
if !GOOGLE_TEST_LIBS
@@ -162,19 +166,21 @@ test_gnc_numeric_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
$(top_srcdir)/${MODULEPATH}/qoflog.cpp \
gtest-gnc-numeric.cpp
test_gnc_numeric_CPPFLAGS = \
-I${top_srcdir}/src \
-I${top_srcdir}/src/libqof/qof \
-I${GTEST_HEADERS} \
+ ${BOOST_CPPFLAGS} \
${GLIB_CFLAGS}
test_gnc_numeric_LDADD = \
${GTEST_LIBS} \
+ ${BOOST_LDFLAGS} -lboost_regex \
${GLIB_LIBS}
if !GOOGLE_TEST_LIBS
nodist_test_gnc_numeric_SOURCES = \
@@ -189,13 +195,13 @@ test_gnc_timezone_SOURCES = \
test_gnc_timezone_CPPFLAGS = \
-I${GTEST_HEADERS} \
-I$(top_srcdir)/$(MODULEPATH) \
- -I${top_srcdir}/src \
+ -I${top_srcdir}/src \
$(GLIB_CFLAGS) \
- $(BOOST_CPPFLAGS)
+ $(BOOST_CPPFLAGS)
test_gnc_timezone_LDADD = \
- ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
- $(GLIB_LIBS) \
+ ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
+ $(GLIB_LIBS) \
$(GTEST_LIBS)
if !GOOGLE_TEST_LIBS
nodist_test_gnc_timezone_SOURCES = \
@@ -204,24 +210,24 @@ endif
check_PROGRAMS += test-gnc-timezone
test_gnc_datetime_SOURCES = \
- $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
- $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
- gtest-gnc-datetime.cpp
+ $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
+ gtest-gnc-datetime.cpp
test_gnc_datetime_CPPFLAGS =\
- -I$(GTEST_HEADERS) \
+ -I$(GTEST_HEADERS) \
-I$(top_srcdir)/$(MODULEPATH) \
- -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src \
$(GLIB_CFLAGS) \
- $(BOOST_CPPFLAGS)
+ $(BOOST_CPPFLAGS)
test_gnc_datetime_LDADD = \
- -lboost_date_time \
- ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
- $(GLIB_LIBS) \
- $(GTEST_LIBS)
+ -lboost_date_time \
+ ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
+ $(GLIB_LIBS) \
+ $(GTEST_LIBS)
if !GOOGLE_TEST_LIBS
nodist_test_gnc_datetime_SOURCES = \
- $(GTEST_SRC)/src/gtest_main.cc
+ $(GTEST_SRC)/src/gtest_main.cc
endif
check_PROGRAMS += test-gnc-datetime
@@ -231,13 +237,13 @@ test_qofdir = ${GNC_LIBEXECDIR}/${MODULEPATH}/test
#The tests might require more libraries, but try to keep them
#as independent as possible.
test_qof_LDADD = \
- ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
- $(GLIB_LIBS)
+ ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
+ $(GLIB_LIBS)
test_qof_CPPFLAGS = \
- ${DEFAULT_INCLUDES} \
- -I$(top_srcdir)/${MODULEPATH} \
- -I$(top_srcdir)/src/test-core \
- -DTESTPROG=test_qof \
- -I$(top_srcdir)/lib/libc \
- ${GLIB_CFLAGS}
+ ${DEFAULT_INCLUDES} \
+ -I$(top_srcdir)/${MODULEPATH} \
+ -I$(top_srcdir)/src/test-core \
+ -DTESTPROG=test_qof \
+ -I$(top_srcdir)/lib/libc \
+ ${GLIB_CFLAGS}
commit b60d6a84666756b516faf614fa9da86a7ac3391f
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Feb 3 08:55:01 2017 -0800
Add libgnc-qof to dependencies for test-numeric.
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index 596157b..5fee07c 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -44,6 +44,7 @@ test_numeric_CPPFLAGS = \
test_numeric_LDADD = \
${top_builddir}/src/engine/libgncmod-engine.la \
+ ${top_builddir}/${MODULEPATH}/libgnc-qof.la \
${GLIB_LIBS}
check_PROGRAMS = \
commit bff0e745fc07e8b6a7fe1ae6c7c7145a1f46416d
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Feb 2 21:53:06 2017 -0800
Fix indentation error.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 1793491..58697fd 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -1052,7 +1052,7 @@ gnc_numeric_invert(gnc_numeric num)
{
if (num.num == 0)
return gnc_numeric_zero();
- try
+ try
{
return static_cast<gnc_numeric>(GncNumeric(num).inv());
}
commit 6220b850453848f13619ece6c5c55f85f2990a61
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Feb 2 15:14:23 2017 -0800
Remove a commented-out diagnostic.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 921886e..1793491 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -455,7 +455,6 @@ operator/(GncNumeric a, GncNumeric b)
template <typename T, typename I> T
convert(T num, I new_denom, int how)
{
-// std::cout << "Converting " << num << ".\n";
auto rtype = static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK);
unsigned int figs = GNC_HOW_GET_SIGFIGS(how);
commit ea44b16f548f702fff62171fe9f5104ac0c0a222
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Feb 2 15:13:10 2017 -0800
Make the GncRational and GncNumeric APIs mostly identical.
Leaving string conversion and stream operators out of GncRational.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 3312b84..921886e 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -64,17 +64,18 @@ powten (unsigned int exp)
GncNumeric::GncNumeric(GncRational rr)
{
- if (rr.m_num.isNan() || rr.m_den.isNan())
+ /* Can't use isValid here because we want to throw different exceptions. */
+ if (rr.num().isNan() || rr.denom().isNan())
throw std::underflow_error("Operation resulted in NaN.");
- if (rr.m_num.isOverflow() || rr.m_den.isOverflow())
+ if (rr.num().isOverflow() || rr.denom().isOverflow())
throw std::overflow_error("Operation overflowed a 128-bit int.");
- if (rr.m_num.isBig() || rr.m_den.isBig())
+ if (rr.num().isBig() || rr.denom().isBig())
{
GncRational reduced(rr.reduce());
rr = reduced.round_to_numeric(); // A no-op if it's already small.
}
- m_num = static_cast<int64_t>(rr.m_num);
- m_den = static_cast<int64_t>(rr.m_den);
+ m_num = static_cast<int64_t>(rr.num());
+ m_den = static_cast<int64_t>(rr.denom());
}
GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
@@ -261,18 +262,18 @@ GncNumeric::prepare_conversion(int64_t new_denom) const
GncRational conversion(new_denom, m_den);
auto red_conv = conversion.reduce();
GncInt128 old_num(m_num);
- auto new_num = old_num * red_conv.m_num;
- auto rem = new_num % red_conv.m_den;
- new_num /= red_conv.m_den;
+ auto new_num = old_num * red_conv.num();
+ auto rem = new_num % red_conv.denom();
+ new_num /= red_conv.denom();
if (new_num.isBig())
{
GncRational rr(new_num, new_denom);
GncNumeric nn(rr);
rr = rr.convert<RoundType::truncate>(new_denom);
- return {static_cast<int64_t>(rr.m_num), new_denom, 0};
+ return {static_cast<int64_t>(rr.num()), new_denom, 0};
}
- return {static_cast<int64_t>(new_num), static_cast<int64_t>(red_conv.m_den),
- static_cast<int64_t>(rem)};
+ return {static_cast<int64_t>(new_num),
+ static_cast<int64_t>(red_conv.denom()), static_cast<int64_t>(rem)};
}
int64_t
@@ -331,25 +332,26 @@ GncNumeric::to_decimal(unsigned int max_places) const
rr = rr.convert<RoundType::never>(powten(max_places)); //May throw
/* rr might have gotten reduced a bit too much; if so, put it back: */
unsigned int pwr{1};
- for (; pwr <= max_places && !(rr.m_den % powten(pwr)); ++pwr);
+ for (; pwr <= max_places && !(rr.denom() % powten(pwr)); ++pwr);
auto reduce_to = powten(pwr);
- if (rr.m_den % reduce_to)
+ GncInt128 rr_num(rr.num()), rr_den(rr.denom());
+ if (rr_den % reduce_to)
{
- auto factor(reduce_to / rr.m_den);
- rr.m_num *= factor;
- rr.m_den *= factor;
+ auto factor(reduce_to / rr.denom());
+ rr_num *= factor;
+ rr_den *= factor;
}
- while (rr.m_num % 10 == 0)
+ while (rr_num % 10 == 0)
{
- rr.m_num /= 10;
- rr.m_den /= 10;
+ rr_num /= 10;
+ rr_den /= 10;
}
try
{
/* Construct from the parts to avoid the GncRational constructor's
* automatic rounding.
*/
- return {static_cast<int64_t>(rr.m_num), static_cast<int64_t>(rr.m_den)};
+ return {static_cast<int64_t>(rr_num), static_cast<int64_t>(rr_den)};
}
catch (const std::invalid_argument& err)
{
@@ -399,12 +401,8 @@ GncNumeric::cmp(GncNumeric b)
auto b_num = b.num();
return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
}
-// GncInt128 a_den(m_den), b_den(b.denom());
-// auto lcm = a_den.gcd(b_den);
-// GncInt128 a_num(m_num * gcd / a_den), b_num(b.num() * gcd / b_den);
-// return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
GncRational an(*this), bn(b);
- return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
+ return an.cmp(bn);
}
GncNumeric
@@ -454,49 +452,6 @@ operator/(GncNumeric a, GncNumeric b)
return static_cast<GncNumeric>(rr);
}
-int
-cmp(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b);
-}
-
-bool
-operator<(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b) < 0;
-}
-
-bool
-operator>(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b) > 0;
-}
-
-bool
-operator==(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b) == 0;
-}
-
-bool
-operator<=(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b) <= 0;
-}
-
-bool
-operator>=(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b) >= 0;
-}
-
-bool
-operator!=(GncNumeric a, GncNumeric b)
-{
- return a.cmp(b) != 0;
-}
-
-
template <typename T, typename I> T
convert(T num, I new_denom, int how)
{
diff --git a/src/libqof/qof/gnc-numeric.hpp b/src/libqof/qof/gnc-numeric.hpp
index 2fce5eb..fdb0107 100644
--- a/src/libqof/qof/gnc-numeric.hpp
+++ b/src/libqof/qof/gnc-numeric.hpp
@@ -29,8 +29,8 @@
class GncRational;
-/**
- * The primary numeric class for representing amounts and values.
+/**@ingroup QOF
+ * @brief The primary numeric class for representing amounts and values.
*
* Calculations are generally performed in 128-bit (by converting to
* GncRational) and reducing the result. If the result would overflow a 64-bit
@@ -255,6 +255,7 @@ public:
* @return -1 if this < b, 0 if ==, 1 if this > b.
*/
int cmp(GncNumeric b);
+ int cmp(int64_t b) { return cmp(GncNumeric(b, 1)); }
private:
struct round_param
{
@@ -274,7 +275,7 @@ private:
/**
* \defgroup gnc_numeric_arithmetic_operators
- *
+ * @{
* Normal arithmetic operators. The class arithmetic operators are implemented
* in terms of these operators. They use GncRational operators internally then
* call the GncNumeric(GncRational&) constructor which will silently round
@@ -285,12 +286,45 @@ private:
*
* \param a The right-side operand
* \param b The left-side operand
- * \return A new GncNumeric computed from the sum.
+ * \return A GncNumeric computed from the operation.
*/
GncNumeric operator+(GncNumeric a, GncNumeric b);
+inline GncNumeric operator+(GncNumeric a, int64_t b)
+{
+ return a + GncNumeric(b, 1);
+}
+inline GncNumeric operator+(int64_t a, GncNumeric b)
+{
+ return b + GncNumeric(a, 1);
+}
GncNumeric operator-(GncNumeric a, GncNumeric b);
+inline GncNumeric operator-(GncNumeric a, int64_t b)
+{
+ return a - GncNumeric(b, 1);
+}
+inline GncNumeric operator-(int64_t a, GncNumeric b)
+{
+ return b - GncNumeric(a, 1);
+}
GncNumeric operator*(GncNumeric a, GncNumeric b);
+inline GncNumeric operator*(GncNumeric a, int64_t b)
+{
+ return a * GncNumeric(b, 1);
+}
+inline GncNumeric operator*(int64_t a, GncNumeric b)
+{
+ return b * GncNumeric(a, 1);
+}
GncNumeric operator/(GncNumeric a, GncNumeric b);
+inline GncNumeric operator/(GncNumeric a, int64_t b)
+{
+ return a / GncNumeric(b, 1);
+}
+inline GncNumeric operator/(int64_t a, GncNumeric b)
+{
+ return b / GncNumeric(a, 1);
+}
+/** @} */
/**
* std::stream output operator. Uses standard integer operator<< so should obey
* locale rules. Numbers are presented as integers if the denominator is 1, as a
@@ -346,18 +380,33 @@ std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>&
/**
* @return -1 if a < b, 0 if a == b, 1 if a > b.
*/
-int cmp(GncNumeric a, GncNumeric b);
+inline int cmp(GncNumeric a, GncNumeric b) { return a.cmp(b); }
+inline int cmp(GncNumeric a, int64_t b) { return a.cmp(b); }
+inline int cmp(int64_t a, GncNumeric b) { return GncNumeric(a, 1).cmp(b); }
+
/**
* \defgroup gnc_numeric_comparison_operators
* @{
* Standard comparison operators, which do what one would expect.
*/
-bool operator<(GncNumeric a, GncNumeric b);
-bool operator>(GncNumeric a, GncNumeric b);
-bool operator==(GncNumeric a, GncNumeric b);
-bool operator<=(GncNumeric a, GncNumeric b);
-bool operator>=(GncNumeric a, GncNumeric b);
-bool operator!=(GncNumeric a, GncNumeric b);
+inline bool operator<(GncNumeric a, GncNumeric b) { return cmp(a, b) < 0; }
+inline bool operator<(GncNumeric a, int64_t b) { return cmp(a, b) < 0; }
+inline bool operator<(int64_t a, GncNumeric b) { return cmp(a, b) < 0; }
+inline bool operator>(GncNumeric a, GncNumeric b) { return cmp(a, b) > 0; }
+inline bool operator>(GncNumeric a, int64_t b) { return cmp(a, b) > 0; }
+inline bool operator>(int64_t a, GncNumeric b) { return cmp(a, b) > 0; }
+inline bool operator==(GncNumeric a, GncNumeric b) { return cmp(a, b) == 0; }
+inline bool operator==(GncNumeric a, int64_t b) { return cmp(a, b) == 0; }
+inline bool operator==(int64_t a, GncNumeric b) { return cmp(a, b) == 0; }
+inline bool operator<=(GncNumeric a, GncNumeric b) { return cmp(a, b) <= 0; }
+inline bool operator<=(GncNumeric a, int64_t b) { return cmp(a, b) <= 0; }
+inline bool operator<=(int64_t a, GncNumeric b) { return cmp(a, b) <= 0; }
+inline bool operator>=(GncNumeric a, GncNumeric b) { return cmp(a, b) >= 0; }
+inline bool operator>=(GncNumeric a, int64_t b) { return cmp(a, b) >= 0; }
+inline bool operator>=(int64_t a, GncNumeric b) { return cmp(a, b) >= 0; }
+inline bool operator!=(GncNumeric a, GncNumeric b) { return cmp(a, b) != 0; }
+inline bool operator!=(GncNumeric a, int64_t b) { return cmp(a, b) != 0; }
+inline bool operator!=(int64_t a, GncNumeric b) { return cmp(a, b) != 0; }
/** @} */
/**
* Convenience function to quickly return 10**digits.
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 635b4ca..e5878a9 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -78,89 +78,25 @@ GncRational::operator gnc_numeric () const noexcept
GncRational
GncRational::operator-() const noexcept
{
- GncRational b(*this);
- b.m_num = - b.m_num;
- return b;
-}
-
-GncRational&
-GncRational::inv () noexcept
-{
- if (m_den < 0)
- {
- m_num *= -m_den;
- m_den = 1;
- }
- std::swap(m_num, m_den);
-
- reduce();
- return *this;
-}
-
-GncRational
-operator+(GncRational a, GncRational b)
-{
- if (!(a.valid() && b.valid()))
- throw std::range_error("Operator+ called with out-of-range operand.");
- GncInt128 lcm = a.m_den.lcm(b.m_den);
- GncInt128 num(a.m_num * lcm / a.m_den + b.m_num * lcm / b.m_den);
- if (!(lcm.valid() && num.valid()))
- throw std::overflow_error("Operator+ overflowed.");
- GncRational retval(num, lcm);
- return retval;
-}
-
-GncRational
-operator-(GncRational a, GncRational b)
-{
- GncRational retval = a + (-b);
- return retval;
+ return GncRational(-m_num, m_den);
}
GncRational
-operator*(GncRational a, GncRational b)
+GncRational::inv () const noexcept
{
- if (!(a.valid() && b.valid()))
- throw std::range_error("Operator* called with out-of-range operand.");
- GncInt128 num (a.m_num * b.m_num), den(a.m_den * b.m_den);
- if (!(num.valid() && den.valid()))
- throw std::overflow_error("Operator* overflowed.");
- GncRational retval(num, den);
- return retval;
+ if (m_num == 0)
+ return *this;
+ if (m_num < 0)
+ return GncRational(-m_den, -m_num);
+ return GncRational(m_den, m_num);
}
GncRational
-operator/(GncRational a, GncRational b)
+GncRational::abs() const noexcept
{
- if (!(a.valid() && b.valid()))
- throw std::range_error("Operator/ called with out-of-range operand.");
- if (b.m_num == 0)
- throw std::underflow_error("Divide by 0.");
- if (b.m_num.isNeg())
- {
- a.m_num = -a.m_num;
- b.m_num = -b.m_num;
- }
-
- /* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
- * and it's just a_num/b_num.
- */
- if (a.m_den == b.m_den)
- return GncRational(a.m_num, b.m_num);
-
- /* Protect against possibly preventable overflow: */
- if (a.m_num.isBig() || a.m_den.isBig() ||
- b.m_num.isBig() || b.m_den.isBig())
- {
- GncInt128 gcd = b.m_den.gcd(a.m_den);
- b.m_den /= gcd;
- a.m_den /= gcd;
- }
-
- GncInt128 num(a.m_num * b.m_den), den(a.m_den * b.m_num);
- if (!(num.valid() && den.valid()))
- throw std::overflow_error("Operator/ overflowed.");
- return GncRational(num, den);
+ if (m_num < 0)
+ return -*this;
+ return *this;
}
void
@@ -191,6 +127,19 @@ GncRational::operator/=(GncRational b)
*this = std::move(new_val);
}
+int
+GncRational::cmp(GncRational b)
+{
+ if (m_den == b.denom())
+ {
+ auto b_num = b.num();
+ return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
+ }
+ auto gcd = m_den.gcd(b.denom());
+ GncInt128 a_num(m_num * b.denom() / gcd), b_num(b.num() * m_den / gcd);
+ return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
+}
+
GncRational::round_param
GncRational::prepare_conversion (GncInt128 new_denom) const
{
@@ -199,10 +148,10 @@ GncRational::prepare_conversion (GncInt128 new_denom) const
GncRational conversion(new_denom, m_den);
auto red_conv = conversion.reduce();
GncInt128 old_num(m_num);
- auto new_num = old_num * red_conv.m_num;
- auto rem = new_num % red_conv.m_den;
- new_num /= red_conv.m_den;
- return {new_num, red_conv.m_den, rem};
+ auto new_num = old_num * red_conv.num();
+ auto rem = new_num % red_conv.denom();
+ new_num /= red_conv.denom();
+ return {new_num, red_conv.denom(), rem};
}
GncInt128
@@ -267,3 +216,70 @@ GncRational::round_to_numeric() const
new_v = new_v.convert<RoundType::half_down>(m_den / divisor);
return new_v;
}
+
+GncRational
+operator+(GncRational a, GncRational b)
+{
+ if (!(a.valid() && b.valid()))
+ throw std::range_error("Operator+ called with out-of-range operand.");
+ GncInt128 lcm = a.denom().lcm(b.denom());
+ GncInt128 num(a.num() * lcm / a.denom() + b.num() * lcm / b.denom());
+ if (!(lcm.valid() && num.valid()))
+ throw std::overflow_error("Operator+ overflowed.");
+ GncRational retval(num, lcm);
+ return retval;
+}
+
+GncRational
+operator-(GncRational a, GncRational b)
+{
+ GncRational retval = a + (-b);
+ return retval;
+}
+
+GncRational
+operator*(GncRational a, GncRational b)
+{
+ if (!(a.valid() && b.valid()))
+ throw std::range_error("Operator* called with out-of-range operand.");
+ GncInt128 num (a.num() * b.num()), den(a.denom() * b.denom());
+ if (!(num.valid() && den.valid()))
+ throw std::overflow_error("Operator* overflowed.");
+ GncRational retval(num, den);
+ return retval;
+}
+
+GncRational
+operator/(GncRational a, GncRational b)
+{
+ if (!(a.valid() && b.valid()))
+ throw std::range_error("Operator/ called with out-of-range operand.");
+ auto a_num = a.num(), b_num = b.num(), a_den = a.denom(), b_den = b.denom();
+ if (b_num == 0)
+ throw std::underflow_error("Divide by 0.");
+ if (b_num.isNeg())
+ {
+ a_num = -a_num;
+ b_num = -b_num;
+ }
+
+ /* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
+ * and it's just a_num/b_num.
+ */
+ if (a_den == b_den)
+ return GncRational(a_num, b_num);
+
+ /* Protect against possibly preventable overflow: */
+ if (a_num.isBig() || a_den.isBig() ||
+ b_num.isBig() || b_den.isBig())
+ {
+ GncInt128 gcd = b_den.gcd(a_den);
+ b_den /= gcd;
+ a_den /= gcd;
+ }
+
+ GncInt128 num(a_num * b_den), den(a_den * b_num);
+ if (!(num.valid() && den.valid()))
+ throw std::overflow_error("Operator/ overflowed.");
+ return GncRational(num, den);
+}
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 013730f..1d04b9b 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -34,16 +34,43 @@ enum class DenomType;
/** @ingroup QOF
* @brief Rational number class using GncInt128 for the numerator
* and denominator.
+ *
+ * This class provides far greater overflow protection compared to GncNumeric at
+ * the expense of doubling the size, so GncNumeric is preferred for storage into
+ * objects. Furthermore the backends are not able to store GncRational numbers;
+ * storage in SQL would require using BLOBs which would preclude calculations in
+ * queries. GncRational exists *primarily* as a more overflow-resistant
+ * calculation facility for GncNumeric. It's available for cases where one needs
+ * an error instead of an automatically rounded value for a calculation that
+ * produces a result that won't fit into an int64 without rounding.
+
+ * Errors: Errors are signalled by exceptions as follows:
+ * * A zero denominator will raise a std::invalid_argument.
+ * * Division by zero will raise a std::underflow_error.
+ * * Overflowing 128 bits will raise a std::overflow_error.
+ * * Failure to convert a number as specified by the arguments to convert() will
+ * raise a std::domain_error.
+ *
*/
+
class GncRational
{
public:
+ /**
+ * Default constructor provides the zero value.
+ */
GncRational() : m_num(0), m_den(1) {}
- GncRational (gnc_numeric n) noexcept;
- GncRational(GncNumeric n) noexcept;
+ /**
+ * GncInt128 constructor. This will take any flavor of built-in integer
+ * thanks to implicit construction of the GncInt128s.
+ */
GncRational (GncInt128 num, GncInt128 den) noexcept
: m_num(num), m_den(den) {}
+ /** Convenience constructor from the C API's gnc_numeric. */
+ GncRational (gnc_numeric n) noexcept;
+ /** GncNumeric constructor. */
+ GncRational(GncNumeric n) noexcept;
GncRational(const GncRational& rhs) = default;
GncRational(GncRational&& rhs) = default;
GncRational& operator=(const GncRational& rhs) = default;
@@ -122,6 +149,10 @@ public:
params.rem, RT2T<RT>()), new_denom);
}
+ /** Numerator accessor */
+ GncInt128 num() { return m_num; }
+ /** Denominator accessor */
+ GncInt128 denom() { return m_den; }
/** @defgroup gnc_rational_mutators
* @{
* Standard mutating arithmetic operators.
@@ -132,10 +163,17 @@ public:
void operator/=(GncRational b);
/** @} */
/** Inverts the number, equivalent of /= {1, 1} */
- GncRational& inv() noexcept;
+ GncRational inv() const noexcept;
+ /** Absolute value; return value is always >= 0 and of same magnitude. */
+ GncRational abs() const noexcept;
+ /** Compare function
+ *
+ * @param b GncNumeric or integer value to compare to.
+ * @return -1 if < b, 0 if equal, 1 if > b.
+ */
+ int cmp(GncRational b);
+ int cmp(GncInt128 b) { return cmp(GncRational(b, 1)); }
- GncInt128 m_num;
- GncInt128 m_den;
private:
struct round_param
{
@@ -152,11 +190,90 @@ private:
* finish computing a GncNumeric with the new denominator.
*/
round_param prepare_conversion(GncInt128 new_denom) const;
+ GncInt128 m_num;
+ GncInt128 m_den;
};
+/**
+ * @return -1 if a < b, 0 if a == b, 1 if a > b.
+ */
+inline int cmp(GncRational a, GncRational b) { return a.cmp(b); }
+inline int cmp(GncRational a, GncInt128 b) { return a.cmp(b); }
+inline int cmp(GncInt128 a, GncRational b) { return GncRational(a, 1).cmp(b); }
+
+/**
+ * \defgroup gnc_rational_comparison_operators
+ * @{
+ * Standard comparison operators, which do what one would expect.
+ */
+inline bool operator<(GncRational a, GncRational b) { return cmp(a, b) < 0; }
+inline bool operator<(GncRational a, GncInt128 b) { return cmp(a, b) < 0; }
+inline bool operator<(GncInt128 a, GncRational b) { return cmp(a, b) < 0; }
+inline bool operator>(GncRational a, GncRational b) { return cmp(a, b) > 0; }
+inline bool operator>(GncRational a, GncInt128 b) { return cmp(a, b) > 0; }
+inline bool operator>(GncInt128 a, GncRational b) { return cmp(a, b) > 0; }
+inline bool operator==(GncRational a, GncRational b) { return cmp(a, b) == 0; }
+inline bool operator==(GncRational a, GncInt128 b) { return cmp(a, b) == 0; }
+inline bool operator==(GncInt128 a, GncRational b) { return cmp(a, b) == 0; }
+inline bool operator<=(GncRational a, GncRational b) { return cmp(a, b) <= 0; }
+inline bool operator<=(GncRational a, GncInt128 b) { return cmp(a, b) <= 0; }
+inline bool operator<=(GncInt128 a, GncRational b) { return cmp(a, b) <= 0; }
+inline bool operator>=(GncRational a, GncRational b) { return cmp(a, b) >= 0; }
+inline bool operator>=(GncRational a, GncInt128 b) { return cmp(a, b) >= 0; }
+inline bool operator>=(GncInt128 a, GncRational b) { return cmp(a, b) >= 0; }
+inline bool operator!=(GncRational a, GncRational b) { return cmp(a, b) != 0; }
+inline bool operator!=(GncRational a, GncInt128 b) { return cmp(a, b) != 0; }
+inline bool operator!=(GncInt128 a, GncRational b) { return cmp(a, b) != 0; }
+/** @} */
+
+/**
+ * \defgroup gnc_rational_arithmetic_operators
+ *
+ * Normal arithmetic operators. The class arithmetic operators are implemented
+ * in terms of these operators.
+ *
+ * These operators can throw std::overflow_error, std::underflow_error, or
+ * std::invalid argument as indicated in the class documentation.
+ *
+ * \param a The right-side operand
+ * \param b The left-side operand
+ * \return A GncRational computed from the operation.
+ */
GncRational operator+(GncRational a, GncRational b);
+inline GncRational operator+(GncRational a, GncInt128 b)
+{
+ return a + GncRational(b, 1);
+}
+inline GncRational operator+(GncInt128 a, GncRational b)
+{
+ return b + GncRational(a, 1);
+}
GncRational operator-(GncRational a, GncRational b);
+inline GncRational operator-(GncRational a, GncInt128 b)
+{
+ return a - GncRational(b, 1);
+}
+inline GncRational operator-(GncInt128 a, GncRational b)
+{
+ return b - GncRational(a, 1);
+}
GncRational operator*(GncRational a, GncRational b);
+inline GncRational operator*(GncRational a, GncInt128 b)
+{
+ return a * GncRational(b, 1);
+}
+inline GncRational operator*(GncInt128 a, GncRational b)
+{
+ return b * GncRational(a, 1);
+}
GncRational operator/(GncRational a, GncRational b);
-
+inline GncRational operator/(GncRational a, GncInt128 b)
+{
+ return a / GncRational(b, 1);
+}
+inline GncRational operator/(GncInt128 a, GncRational b)
+{
+ return b / GncRational(a, 1);
+}
+/** @} */
#endif //__GNC_RATIONAL_HPP__
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 04f4c24..382ebf5 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -30,8 +30,8 @@ TEST(gncrational_constructors, test_default_constructor)
{
EXPECT_NO_THROW({
GncRational value;
- EXPECT_EQ(value.m_num, 0);
- EXPECT_EQ(value.m_den, 1);
+ EXPECT_EQ(value.num(), 0);
+ EXPECT_EQ(value.denom(), 1);
});
}
@@ -40,8 +40,8 @@ TEST(gncrational_constructors, test_gnc_numeric_constructor)
gnc_numeric input = gnc_numeric_create(123, 456);
EXPECT_NO_THROW({
GncRational value(input);
- EXPECT_EQ(input.num, value.m_num);
- EXPECT_EQ(input.denom, value.m_den);
+ EXPECT_EQ(input.num, value.num());
+ EXPECT_EQ(input.denom, value.denom());
});
}
@@ -50,8 +50,8 @@ TEST(gncrational_constructors, test_gnc_int128_constructor)
GncInt128 num(123), denom(456);
EXPECT_NO_THROW({
GncRational value(num, denom);
- EXPECT_EQ(123, value.m_num);
- EXPECT_EQ(456, value.m_den);
+ EXPECT_EQ(123, value.num());
+ EXPECT_EQ(456, value.denom());
});
}
@@ -60,8 +60,8 @@ TEST(gncrational_constructors, test_implicit_int_constructor)
int num(123), denom(456);
EXPECT_NO_THROW({
GncRational value(num, denom);
- EXPECT_EQ(123, value.m_num);
- EXPECT_EQ(456, value.m_den);
+ EXPECT_EQ(123, value.num());
+ EXPECT_EQ(456, value.denom());
});
}
@@ -71,11 +71,11 @@ TEST(gncrational_operators, test_addition)
GncRational a(123456789987654321, 1000000000);
GncRational b(65432198765432198, 100000000);
GncRational c = a + b;
- EXPECT_EQ (777778777641976301, c.m_num);
- EXPECT_EQ (1000000000, c.m_den);
+ EXPECT_EQ (777778777641976301, c.num());
+ EXPECT_EQ (1000000000, c.denom());
a += b;
- EXPECT_EQ (777778777641976301, a.m_num);
- EXPECT_EQ (1000000000, a.m_den);
+ EXPECT_EQ (777778777641976301, a.num());
+ EXPECT_EQ (1000000000, a.denom());
});
}
@@ -85,17 +85,17 @@ TEST(gncrational_operators, test_subtraction)
GncRational a(123456789987654321, 1000000000);
GncRational b(65432198765432198, 100000000);
GncRational c = a - b;
- EXPECT_EQ (-530865197666667659, c.m_num);
- EXPECT_TRUE(c.m_num.isNeg());
- EXPECT_EQ (1000000000, c.m_den);
+ EXPECT_EQ (-530865197666667659, c.num());
+ EXPECT_TRUE(c.num().isNeg());
+ EXPECT_EQ (1000000000, c.denom());
c = b - a;
- EXPECT_EQ (530865197666667659, c.m_num);
- EXPECT_FALSE(c.m_num.isNeg());
- EXPECT_EQ (1000000000, c.m_den);
+ EXPECT_EQ (530865197666667659, c.num());
+ EXPECT_FALSE(c.num().isNeg());
+ EXPECT_EQ (1000000000, c.denom());
a -= b;
- EXPECT_EQ (-530865197666667659, a.m_num);
- EXPECT_TRUE(a.m_num.isNeg());
- EXPECT_EQ (1000000000, a.m_den);
+ EXPECT_EQ (-530865197666667659, a.num());
+ EXPECT_TRUE(a.num().isNeg());
+ EXPECT_EQ (1000000000, a.denom());
});
}
@@ -106,12 +106,12 @@ TEST(gncrational_operators, test_multiplication)
GncRational b(65432198765432198, 100000000);
GncRational c = a * b;
EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
- UINT64_C(8081008345983448486)), c.m_num);
- EXPECT_EQ (100000000000000000, c.m_den);
+ UINT64_C(8081008345983448486)), c.num());
+ EXPECT_EQ (100000000000000000, c.denom());
a *= b;
EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
- UINT64_C(8081008345983448486)), a.m_num);
- EXPECT_EQ (100000000000000000, a.m_den);
+ UINT64_C(8081008345983448486)), a.num());
+ EXPECT_EQ (100000000000000000, a.denom());
});
}
@@ -122,14 +122,14 @@ TEST(gncrational_operators, test_division)
GncRational b(65432198765432198, 100000000);
GncRational c = a / b;
EXPECT_EQ (GncInt128(UINT64_C(669260),
- UINT64_C(11059994577585475840)), c.m_num);
+ UINT64_C(11059994577585475840)), c.num());
EXPECT_EQ (GncInt128(UINT64_C(3547086),
- UINT64_C(11115994079396609024)), c.m_den);
+ UINT64_C(11115994079396609024)), c.denom());
a /= b;
EXPECT_EQ (GncInt128(UINT64_C(669260),
- UINT64_C(11059994577585475840)), a.m_num);
+ UINT64_C(11059994577585475840)), a.num());
EXPECT_EQ (GncInt128(UINT64_C(3547086),
- UINT64_C(11115994079396609024)), a.m_den);
+ UINT64_C(11115994079396609024)), a.denom());
});
}
@@ -148,12 +148,12 @@ TEST(gncrational_functions, test_round_to_numeric)
expected = expected.convert<RoundType::bankers>(100);
auto rounded = c.round_to_numeric();
rounded = rounded.convert<RoundType::bankers>(100);
- EXPECT_EQ(0, expected.m_num - rounded.m_num);
- EXPECT_FALSE(rounded.m_num.isBig());
- EXPECT_FALSE(rounded.m_den.isBig());
- EXPECT_FALSE(rounded.m_num.isNan());
- EXPECT_FALSE(rounded.m_den.isNan());
- EXPECT_FALSE(rounded.m_num.isOverflow());
- EXPECT_FALSE(rounded.m_den.isOverflow());
+ EXPECT_EQ(0, expected.num() - rounded.num());
+ EXPECT_FALSE(rounded.num().isBig());
+ EXPECT_FALSE(rounded.denom().isBig());
+ EXPECT_FALSE(rounded.num().isNan());
+ EXPECT_FALSE(rounded.denom().isNan());
+ EXPECT_FALSE(rounded.num().isOverflow());
+ EXPECT_FALSE(rounded.denom().isOverflow());
}
}
commit 739c91a4ccb4e5d05d0fd842ec66ecde2a7dffc5
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Feb 2 13:15:52 2017 -0800
Change GncRational::round to GncRational::convert.
With the same signature as the GncNumeric version.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 63a8941..3312b84 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -44,7 +44,7 @@ extern "C"
#include "gnc-rational.hpp"
static QofLogModule log_module = "qof";
-static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
+static const int64_t pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000,
INT64_C(10000000000), INT64_C(100000000000),
INT64_C(1000000000000), INT64_C(10000000000000),
@@ -55,11 +55,11 @@ static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
#define POWTEN_OVERFLOW -5
int64_t
-powten (int64_t exp)
+powten (unsigned int exp)
{
- if (exp > 18 || exp < -18)
- return POWTEN_OVERFLOW;
- return exp < 0 ? -pten[-exp] : pten[exp];
+ if (exp > 17)
+ exp = 17;
+ return pten[exp];
}
GncNumeric::GncNumeric(GncRational rr)
@@ -268,7 +268,7 @@ GncNumeric::prepare_conversion(int64_t new_denom) const
{
GncRational rr(new_num, new_denom);
GncNumeric nn(rr);
- rr.round(new_denom, RoundType::truncate);
+ rr = rr.convert<RoundType::truncate>(new_denom);
return {static_cast<int64_t>(rr.m_num), new_denom, 0};
}
return {static_cast<int64_t>(new_num), static_cast<int64_t>(red_conv.m_den),
@@ -328,13 +328,14 @@ GncNumeric::to_decimal(unsigned int max_places) const
return GncNumeric(m_num / excess, powten(max_places));
}
GncRational rr(*this);
- rr.round(powten(max_places), RoundType::never); //May throw
+ rr = rr.convert<RoundType::never>(powten(max_places)); //May throw
/* rr might have gotten reduced a bit too much; if so, put it back: */
unsigned int pwr{1};
for (; pwr <= max_places && !(rr.m_den % powten(pwr)); ++pwr);
- if (rr.m_den % powten(pwr))
+ auto reduce_to = powten(pwr);
+ if (rr.m_den % reduce_to)
{
- auto factor(powten(pwr) / rr.m_den);
+ auto factor(reduce_to / rr.m_den);
rr.m_num *= factor;
rr.m_den *= factor;
}
@@ -495,8 +496,9 @@ operator!=(GncNumeric a, GncNumeric b)
return a.cmp(b) != 0;
}
-static gnc_numeric
-convert(GncNumeric num, int64_t new_denom, int how)
+
+template <typename T, typename I> T
+convert(T num, I new_denom, int how)
{
// std::cout << "Converting " << num << ".\n";
auto rtype = static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK);
@@ -506,77 +508,60 @@ convert(GncNumeric num, int64_t new_denom, int how)
bool sigfigs = dtype == DenomType::sigfigs;
if (dtype == DenomType::reduce)
num = num.reduce();
- try
- {
- switch (rtype)
- {
- case RoundType::floor:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::floor>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::floor>(new_denom));
- case RoundType::ceiling:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::ceiling>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::ceiling>(new_denom));
- case RoundType::truncate:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::truncate>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::truncate>(new_denom));
- case RoundType::promote:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::promote>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::promote>(new_denom));
- case RoundType::half_down:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::half_down>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::half_down>(new_denom));
- case RoundType::half_up:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::half_up>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::half_up>(new_denom));
- case RoundType::bankers:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::bankers>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::bankers>(new_denom));
- case RoundType::never:
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::never>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::never>(new_denom));
- default:
-/* round-truncate just returns the numerator unchanged. The old gnc-numeric
- * convert had no "default" behavior at rounding that had the same result, but
- * we need to make it explicit here to run the rest of the conversion code.
- */
- if (sigfigs)
- return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::truncate>(figs));
- else
- return static_cast<gnc_numeric>(num.convert<RoundType::truncate>(new_denom));
-// return static_cast<gnc_numeric>(num);
- }
- }
- catch (const std::domain_error& err)
- {
- PWARN("%s", err.what());
- return gnc_numeric_error(GNC_ERROR_REMAINDER);
- }
- catch (const std::overflow_error& err)
- {
- PWARN("%s", err.what());
- return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- }
- catch (const std::exception& err)
- {
- PWARN("%s", err.what());
- return gnc_numeric_error(GNC_ERROR_ARG);
+ switch (rtype)
+ {
+ case RoundType::floor:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::floor>(figs);
+ else
+ return num.template convert<RoundType::floor>(new_denom);
+ case RoundType::ceiling:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::ceiling>(figs);
+ else
+ return num.template convert<RoundType::ceiling>(new_denom);
+ case RoundType::truncate:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::truncate>(figs);
+ else
+ return num.template convert<RoundType::truncate>(new_denom);
+ case RoundType::promote:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::promote>(figs);
+ else
+ return num.template convert<RoundType::promote>(new_denom);
+ case RoundType::half_down:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::half_down>(figs);
+ else
+ return num.template convert<RoundType::half_down>(new_denom);
+ case RoundType::half_up:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::half_up>(figs);
+ else
+ return num.template convert<RoundType::half_up>(new_denom);
+ case RoundType::bankers:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::bankers>(figs);
+ else
+ return num.template convert<RoundType::bankers>(new_denom);
+ case RoundType::never:
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::never>(figs);
+ else
+ return num.template convert<RoundType::never>(new_denom);
+ default:
+ /* round-truncate just returns the numerator unchanged. The old
+ * gnc-numeric convert had no "default" behavior at rounding that
+ * had the same result, but we need to make it explicit here to
+ * run the rest of the conversion code.
+ */
+ if (sigfigs)
+ return num.template convert_sigfigs<RoundType::truncate>(figs);
+ else
+ return num.template convert<RoundType::truncate>(new_denom);
+
}
}
@@ -791,16 +776,18 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
{
GncNumeric an (a), bn (b);
- auto sum = an + bn;
- return convert(sum, denom, how);
+ GncNumeric sum = an + bn;
+ return static_cast<gnc_numeric>(convert(sum, denom, how));
}
GncRational ar(a), br(b);
auto sum = ar + br;
- sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
-
+ if (denom == GNC_DENOM_AUTO &&
+ (how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
+ return static_cast<gnc_numeric>(sum.round_to_numeric());
+ sum = convert(sum, denom, how);
if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- return GncNumeric(sum);
+ return static_cast<gnc_numeric>(sum);
}
catch (const std::overflow_error& err)
{
@@ -844,14 +831,17 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
{
GncNumeric an (a), bn (b);
auto sum = an - bn;
- return convert(sum, denom, how);
+ return static_cast<gnc_numeric>(convert(sum, denom, how));
}
GncRational ar(a), br(b);
auto sum = ar - br;
- sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (denom == GNC_DENOM_AUTO &&
+ (how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
+ return static_cast<gnc_numeric>(sum.round_to_numeric());
+ sum = convert(sum, denom, how);
if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- return GncNumeric(sum);
+ return static_cast<gnc_numeric>(sum);
}
catch (const std::overflow_error& err)
{
@@ -894,14 +884,17 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
{
GncNumeric an (a), bn (b);
auto prod = an * bn;
- return convert(prod, denom, how);
+ return static_cast<gnc_numeric>(convert(prod, denom, how));
}
GncRational ar(a), br(b);
auto prod = ar * br;
- prod.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (denom == GNC_DENOM_AUTO &&
+ (how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
+ return static_cast<gnc_numeric>(prod.round_to_numeric());
+ prod = convert(prod, denom, how);
if (prod.is_big() || !prod.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- return GncNumeric(prod);
+ return static_cast<gnc_numeric>(prod);
}
catch (const std::overflow_error& err)
{
@@ -945,14 +938,17 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
{
GncNumeric an (a), bn (b);
auto quot = an / bn;
- return convert(quot, denom, how);
+ return static_cast<gnc_numeric>(convert(quot, denom, how));
}
GncRational ar(a), br(b);
auto quot = ar / br;
- quot.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (denom == GNC_DENOM_AUTO &&
+ (how & GNC_NUMERIC_RND_MASK) != GNC_HOW_RND_NEVER)
+ return static_cast<gnc_numeric>(quot.round_to_numeric());
+ quot = static_cast<gnc_numeric>(convert(quot, denom, how));
if (quot.is_big() || !quot.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- return GncNumeric(quot);
+ return static_cast<gnc_numeric>(quot);
}
catch (const std::overflow_error& err)
{
@@ -1014,7 +1010,14 @@ gnc_numeric_abs(gnc_numeric a)
gnc_numeric
gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
- return convert(GncNumeric(in), denom, how);
+ try
+ {
+ return convert(GncNumeric(in), denom, how);
+ }
+ catch (const std::overflow_error& err)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
}
diff --git a/src/libqof/qof/gnc-numeric.hpp b/src/libqof/qof/gnc-numeric.hpp
index 8353403..2fce5eb 100644
--- a/src/libqof/qof/gnc-numeric.hpp
+++ b/src/libqof/qof/gnc-numeric.hpp
@@ -135,7 +135,11 @@ public:
* std::invalid_argument.
*/
GncNumeric(const std::string& str, bool autoround=false);
-
+ GncNumeric(const GncNumeric& rhs) = default;
+ GncNumeric(GncNumeric&& rhs) = default;
+ GncNumeric& operator=(const GncNumeric& rhs) = default;
+ GncNumeric& operator=(GncNumeric&& rhs) = default;
+ ~GncNumeric() = default;
/**
* gnc_numeric conversion. Use static_cast<gnc_numeric>(foo)
*/
@@ -206,7 +210,7 @@ public:
* and the appropriate power-of-ten denominator.
*/
template <RoundType RT>
- GncNumeric convert_sigfigs(int figs) const
+ GncNumeric convert_sigfigs(unsigned int figs) const
{
auto new_denom(sigfigs_denom(figs));
auto params = prepare_conversion(new_denom);
@@ -360,6 +364,6 @@ bool operator!=(GncNumeric a, GncNumeric b);
* \param digits The desired exponent. Maximum value is 17.
* \return 10**digits
*/
-int64_t powten(int64_t digits);
+int64_t powten(unsigned int digits);
#endif // __GNC_NUMERIC_HPP__
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index d66122f..635b4ca 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -191,127 +191,33 @@ GncRational::operator/=(GncRational b)
*this = std::move(new_val);
}
-void
-GncRational::round (GncInt128 new_den, RoundType rtype)
+GncRational::round_param
+GncRational::prepare_conversion (GncInt128 new_denom) const
{
- if (new_den == 0) new_den = m_den;
- if (!(m_num.isBig() || new_den.isBig() ))
- {
- if (m_den == new_den)
- return;
-
- if (m_num.isZero())
- {
- m_den = new_den;
- return;
- }
- }
- GncInt128 new_num {}, remainder {};
- if (new_den.isNeg())
- m_num.div(-new_den * m_den, new_num, remainder);
- else if (new_den != m_den)
- (m_num * new_den).div(m_den, new_num, remainder);
- else
- {
- new_num = m_num;
- new_den = m_den;
- remainder = 0;
- }
- if (new_num.isOverflow() || new_den.isOverflow() || remainder.isOverflow())
- throw std::overflow_error("Overflow during rounding.");
- if (new_num.isNan() || new_den.isNan() || remainder.isNan())
- {
- throw std::underflow_error("Underflow during rounding.");
- }
- if (remainder.isZero() && !(new_num.isBig() || new_den.isBig()))
- {
- m_num = new_num;
- m_den = new_den;
- return;
- }
-
- if (new_num.isBig() || new_den.isBig())
- {
- /* First, try to reduce it */
- GncInt128 gcd = new_num.gcd(new_den);
- if (!(gcd.isNan() || gcd.isOverflow()))
- {
- new_num /= gcd;
- new_den /= gcd;
- remainder /= gcd;
- }
+ if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
+ return {m_num, m_den, 0};
+ GncRational conversion(new_denom, m_den);
+ auto red_conv = conversion.reduce();
+ GncInt128 old_num(m_num);
+ auto new_num = old_num * red_conv.m_num;
+ auto rem = new_num % red_conv.m_den;
+ new_num /= red_conv.m_den;
+ return {new_num, red_conv.m_den, rem};
+}
-/* if that didn't work, shift both num and den down until neither is "big", then
- * fall through to rounding.
- */
- while (rtype != RoundType::never && new_num && new_num.isBig() &&
- new_den && new_den.isBig())
- {
- new_num >>= 1;
- new_den >>= 1;
- remainder >>= 1;
- }
- }
- if (remainder == 0)
- {
- m_num = new_num;
- m_den = new_den;
- return;
- }
-/* If we got here, then we can't exactly represent the rational with
- * new_denom. We must either round or punt.
- */
- switch (rtype)
+GncInt128
+GncRational::sigfigs_denom(unsigned figs) const noexcept
+{
+ auto num_abs = m_num.abs();
+ bool not_frac = num_abs > m_den;
+ int64_t val{ not_frac ? num_abs / m_den : m_den / num_abs };
+ unsigned digits{};
+ while (val >= 10)
{
- case RoundType::never:
- throw std::domain_error("Rounding required when 'never round' specified.");
- case RoundType::floor:
- if (new_num.isNeg()) ++new_num;
- break;
- case RoundType::ceiling:
- if (! new_num.isNeg()) ++new_num;
- break;
- case RoundType::truncate:
- break;
- case RoundType::promote:
- new_num += new_num.isNeg() ? -1 : 1;
- break;
- case RoundType::half_down:
- if (new_den.isNeg())
- {
- if (remainder * 2 > m_den * new_den)
- new_num += new_num.isNeg() ? -1 : 1;
- }
- else if (remainder * 2 > m_den)
- new_num += new_num.isNeg() ? -1 : 1;
- break;
- case RoundType::half_up:
- if (new_den.isNeg())
- {
- if (remainder * 2 >= m_den * new_den)
- new_num += new_num.isNeg() ? -1 : 1;
- }
- else if (remainder * 2 >= m_den)
- new_num += new_num.isNeg() ? -1 : 1;
- break;
- case RoundType::bankers:
- if (new_den.isNeg())
- {
- if (remainder * 2 > m_den * -new_den ||
- (remainder * 2 == m_den * -new_den && new_num % 2))
- new_num += new_num.isNeg() ? -1 : 1;
- }
- else
- {
- if (remainder * 2 > m_den ||
- (remainder * 2 == m_den && new_num % 2))
- new_num += new_num.isNeg() ? -1 : 1;
- }
- break;
+ ++digits;
+ val /= 10;
}
- m_num = new_num;
- m_den = new_den;
- return;
+ return not_frac ? powten(figs - digits - 1) : powten(figs + digits);
}
GncRational
@@ -340,10 +246,9 @@ GncRational::round_to_numeric() const
<< "GncNumeric. Its integer value is too large.\n";
throw std::overflow_error(msg.str());
}
- GncRational new_rational(*this);
- GncRational scratch(1, 1);
- new_rational.round(m_den / (m_num.abs() >> 62), RoundType::half_down);
- return new_rational;
+ GncRational new_v(*this);
+ new_v = new_v.convert<RoundType::half_down>(m_den / (m_num.abs() >> 62));
+ return new_v;
}
auto quot(m_den / m_num);
if (quot.isBig())
@@ -358,8 +263,7 @@ GncRational::round_to_numeric() const
GncRational new_rational(num, den);
return new_rational;
}
- GncRational new_rational(*this);
- GncRational scratch(1, 1);
- new_rational.round(m_den / divisor, RoundType::half_down);
- return new_rational;
+ GncRational new_v(*this);
+ new_v = new_v.convert<RoundType::half_down>(m_den / divisor);
+ return new_v;
}
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 9bfa6d4..013730f 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -25,13 +25,15 @@
#include "gnc-numeric.h"
#include "gnc-int128.hpp"
+#include "gnc-rational-rounding.hpp"
class GncNumeric;
enum class RoundType;
enum class DenomType;
/** @ingroup QOF
- * @brief Rational number class using GncInt128 for the numerator and denominator.
+ * @brief Rational number class using GncInt128 for the numerator
+ * and denominator.
*/
class GncRational
@@ -46,45 +48,110 @@ public:
GncRational(GncRational&& rhs) = default;
GncRational& operator=(const GncRational& rhs) = default;
GncRational& operator=(GncRational&& rhs) = default;
-/** Report if both members are valid numbers.
- * \return true if neither numerator nor denominator are Nan or Overflowed.
- */
+ ~GncRational() = default;
+ /** Report if both members are valid numbers.
+ * \return true if neither numerator nor denominator are Nan or Overflowed.
+ */
bool valid() const noexcept;
-/** Report if either numerator or denominator are too big to fit in an int64_t.
- * \return true if either is too big.
- */
+ /** Report if either numerator or denominator are too big to fit in an
+ * int64_t.
+ * \return true if either is too big.
+ */
bool is_big() const noexcept;
-/** Conversion operator; use static_cast<gnc_numeric>(foo). */
+ /** Conversion operator; use static_cast<gnc_numeric>(foo). */
operator gnc_numeric() const noexcept;
-/** Make a new GncRational with the opposite sign. */
+ /** Make a new GncRational with the opposite sign. */
GncRational operator-() const noexcept;
-/**
- * Reduce this to an equivalent fraction with the least common multiple as the
- * denominator.
- *
- * @return reduced GncRational
- */
+ /**
+ * Reduce this to an equivalent fraction with the least common multiple as
+ * the denominator.
+ *
+ * @return reduced GncRational
+ */
GncRational reduce() const;
-/**
- * Round to fit an int64_t, finding the closest possible approximation.
- *
- * Throws std::overflow_error if m_den is 1 and m_num is big.
- * @return rounded GncRational
- */
+ /**
+ * Round to fit an int64_t, finding the closest possible approximation.
+ *
+ * Throws std::overflow_error if m_den is 1 and m_num is big.
+ * @return rounded GncRational
+ */
GncRational round_to_numeric() const;
-/** Round/convert this to the denominator provided by d, according to d's
- * m_round value.
- */
- void round (GncInt128 new_den, RoundType rtype);
+ /**
+ * Convert a GncRational to use a new denominator. If rounding is necessary
+ * use the indicated template specification. For example, to use half-up
+ * rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you
+ * specify RoundType::never this will throw std::domain_error if rounding is
+ * required.
+ *
+ * \param new_denom The new denominator to convert the fraction to.
+ * \return A new GncRational having the requested denominator.
+ */
+ template <RoundType RT>
+ GncRational convert (GncInt128 new_denom) const
+ {
+ auto params = prepare_conversion(new_denom);
+ if (new_denom == GNC_DENOM_AUTO)
+ new_denom = m_den;
+ if (params.rem == 0)
+ return GncRational(params.num, new_denom);
+ return GncRational(round(params.num, params.den,
+ params.rem, RT2T<RT>()), new_denom);
+ }
+
+ /**
+ * Convert with the specified sigfigs. The resulting denominator depends on
+ * the value of the GncRational, such that the specified significant digits
+ * are retained in the numerator and the denominator is always a power of
+ * 10. This is of rather dubious benefit in an accounting program, but it's
+ * used in several places so it needs to be implemented.
+ *
+ * @param figs The number of digits to use for the numerator.
+ * @return A GncRational with the specified number of digits in the
+ * numerator and the appropriate power-of-ten denominator.
+ */
+ template <RoundType RT>
+ GncRational convert_sigfigs(unsigned int figs) const
+ {
+ auto new_denom(sigfigs_denom(figs));
+ auto params = prepare_conversion(new_denom);
+ if (new_denom == 0) //It had better not, but just in case...
+ new_denom = 1;
+ if (params.rem == 0)
+ return GncRational(params.num, new_denom);
+ return GncRational(round(params.num, params.den,
+ params.rem, RT2T<RT>()), new_denom);
+ }
+
+ /** @defgroup gnc_rational_mutators
+ * @{
+ * Standard mutating arithmetic operators.
+ */
void operator+=(GncRational b);
void operator-=(GncRational b);
void operator*=(GncRational b);
void operator/=(GncRational b);
-/** Inverts the number, equivalent of /= {1, 1} */
+ /** @} */
+ /** Inverts the number, equivalent of /= {1, 1} */
GncRational& inv() noexcept;
GncInt128 m_num;
GncInt128 m_den;
+private:
+ struct round_param
+ {
+ GncInt128 num;
+ GncInt128 den;
+ GncInt128 rem;
+ };
+ /* Calculates the denominator required to convert to figs sigfigs. Note that
+ * it uses the same powten function that the GncNumeric version does because
+ * 17 significant figures should be plenty.
+ */
+ GncInt128 sigfigs_denom(unsigned figs) const noexcept;
+ /* Calculates a round_param struct to pass to a rounding function that will
+ * finish computing a GncNumeric with the new denominator.
+ */
+ round_param prepare_conversion(GncInt128 new_denom) const;
};
GncRational operator+(GncRational a, GncRational b);
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index b1124e4..04f4c24 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -145,9 +145,9 @@ TEST(gncrational_functions, test_round_to_numeric)
GncRational b(di(dre), 100);
auto c = a * b;
auto expected = c;
- expected.round(100, RoundType::bankers);
+ expected = expected.convert<RoundType::bankers>(100);
auto rounded = c.round_to_numeric();
- rounded.round(100, RoundType::bankers);
+ rounded = rounded.convert<RoundType::bankers>(100);
EXPECT_EQ(0, expected.m_num - rounded.m_num);
EXPECT_FALSE(rounded.m_num.isBig());
EXPECT_FALSE(rounded.m_den.isBig());
commit e506f9a4a1b3433a2927fb5dd78acaedd4b034a2
Author: John Ralls <jralls at ceridwen.us>
Date: Tue Jan 31 14:34:51 2017 -0800
Make the rounding functions templates and move them to their own header.
diff --git a/src/libqof/CMakeLists.txt b/src/libqof/CMakeLists.txt
index 05a03da..adcaa6d 100644
--- a/src/libqof/CMakeLists.txt
+++ b/src/libqof/CMakeLists.txt
@@ -6,10 +6,12 @@ SET (gnc_qof_HEADERS
qof/gnc-aqbanking-templates.h
qof/gnc-date-p.h
qof/gnc-date.h
- qof/gnc-numeric.h
qof/gnc-datetime.hpp
- qof/gnc-rational.hpp
qof/gnc-timezone.hpp
+ qof/gnc-numeric.h
+ qof/gnc-numeric.hpp
+ qof/gnc-rational.hpp
+ qof/gnc-rational-rounding.hpp
qof/guid.h
qof/kvp_frame.hpp
qof/kvp-value.hpp
diff --git a/src/libqof/qof/Makefile.am b/src/libqof/qof/Makefile.am
index c2fec7f..6b77c4f 100644
--- a/src/libqof/qof/Makefile.am
+++ b/src/libqof/qof/Makefile.am
@@ -54,7 +54,9 @@ qofinclude_HEADERS = \
gnc-date-p.h \
gnc-date.h \
gnc-numeric.h \
+ gnc-numeric.hpp \
gnc-rational.hpp \
+ gnc-rational-rounding.hpp \
gnc-timezone.hpp \
gnc-datetime.hpp \
guid.h \
diff --git a/src/libqof/qof/gnc-numeric.hpp b/src/libqof/qof/gnc-numeric.hpp
index 44db4b2..8353403 100644
--- a/src/libqof/qof/gnc-numeric.hpp
+++ b/src/libqof/qof/gnc-numeric.hpp
@@ -25,115 +25,7 @@
#include <string>
#include <iostream>
-
-#include "gnc-numeric.h"
-
-
-enum class RoundType
-{
- floor = GNC_HOW_RND_FLOOR,
- ceiling = GNC_HOW_RND_CEIL,
- truncate = GNC_HOW_RND_TRUNC,
- promote = GNC_HOW_RND_PROMOTE,
- half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
- half_up = GNC_HOW_RND_ROUND_HALF_UP,
- bankers = GNC_HOW_RND_ROUND,
- never = GNC_HOW_RND_NEVER,
-};
-
-enum class DenomType
-{
- den_auto = GNC_DENOM_AUTO,
- exact = GNC_HOW_DENOM_EXACT,
- reduce = GNC_HOW_DENOM_REDUCE,
- lcd = GNC_HOW_DENOM_LCD,
- fixed = GNC_HOW_DENOM_FIXED,
- sigfigs = GNC_HOW_DENOM_SIGFIG,
-};
-
-
-template <RoundType rt>
-struct RT2T
-{
- RoundType value = rt;
-};
-
-/* The following templates implement the rounding policies for the convert and
- * convert_sigfigs template functions.
- */
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::never>)
-{
- if (rem == 0)
- return num;
- throw std::domain_error("Rounding required when 'never round' specified.");
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::floor>)
-{
-// std::cout << "Rounding to floor with num " << num << " den " << den
-// << ", and rem " << rem << ".\n";
- if (rem == 0)
- return num;
- if (num < 0)
- return num + 1;
- return num;
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::ceiling>)
-{
- if (rem == 0)
- return num;
- if (num > 0)
- return num + 1;
- return num;
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::truncate>)
-{
- return num;
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::promote>)
-{
- if (rem == 0)
- return num;
- return num + (num < 0 ? -1 : 1);
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::half_down>)
-{
- if (rem == 0)
- return num;
- if (rem * 2 > den)
- return num + (num < 0 ? -1 : 1);
- return num;
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::half_up>)
-{
- if (rem == 0)
- return num;
- if (rem * 2 >= den)
- return num + (num < 0 ? -1 : 1);
- return num;
-}
-
-inline int64_t
-round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::bankers>)
-{
- if (rem == 0)
- return num;
- if (rem * 2 > den || (rem * 2 == den && num % 2))
- return num += (num < 0 ? -1 : 1);
- return num;
-}
+#include "gnc-rational-rounding.hpp"
class GncRational;
diff --git a/src/libqof/qof/gnc-rational-rounding.hpp b/src/libqof/qof/gnc-rational-rounding.hpp
new file mode 100644
index 0000000..f5cdb50
--- /dev/null
+++ b/src/libqof/qof/gnc-rational-rounding.hpp
@@ -0,0 +1,133 @@
+/********************************************************************
+ * gnc-rational-rounding.hpp - Template functions for rounding *
+ * Copyright 2017 John Ralls <jralls at ceridwen.us> *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+#ifndef __GNC_RATIONAL_ROUNDING_HPP__
+#define __GNC_RATIONAL_ROUNDING_HPP__
+#include "gnc-numeric.h"
+
+enum class RoundType
+{
+ floor = GNC_HOW_RND_FLOOR,
+ ceiling = GNC_HOW_RND_CEIL,
+ truncate = GNC_HOW_RND_TRUNC,
+ promote = GNC_HOW_RND_PROMOTE,
+ half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
+ half_up = GNC_HOW_RND_ROUND_HALF_UP,
+ bankers = GNC_HOW_RND_ROUND,
+ never = GNC_HOW_RND_NEVER,
+};
+
+enum class DenomType
+{
+ den_auto = GNC_DENOM_AUTO,
+ exact = GNC_HOW_DENOM_EXACT,
+ reduce = GNC_HOW_DENOM_REDUCE,
+ lcd = GNC_HOW_DENOM_LCD,
+ fixed = GNC_HOW_DENOM_FIXED,
+ sigfigs = GNC_HOW_DENOM_SIGFIG,
+};
+
+
+template <RoundType rt>
+struct RT2T
+{
+ RoundType value = rt;
+};
+
+/* The following templates implement the rounding policies for the convert and
+ * convert_sigfigs template functions.
+ */
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::never>)
+{
+ if (rem == 0)
+ return num;
+ throw std::domain_error("Rounding required when 'never round' specified.");
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::floor>)
+{
+// std::cout << "Rounding to floor with num " << num << " den " << den
+// << ", and rem " << rem << ".\n";
+ if (rem == 0)
+ return num;
+ if (num < 0)
+ return num + 1;
+ return num;
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::ceiling>)
+{
+ if (rem == 0)
+ return num;
+ if (num > 0)
+ return num + 1;
+ return num;
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::truncate>)
+{
+ return num;
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::promote>)
+{
+ if (rem == 0)
+ return num;
+ return num + (num < 0 ? -1 : 1);
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::half_down>)
+{
+ if (rem == 0)
+ return num;
+ if (rem * 2 > den)
+ return num + (num < 0 ? -1 : 1);
+ return num;
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::half_up>)
+{
+ if (rem == 0)
+ return num;
+ if (rem * 2 >= den)
+ return num + (num < 0 ? -1 : 1);
+ return num;
+}
+
+template <typename T> inline T
+round(T num, T den, T rem, RT2T<RoundType::bankers>)
+{
+ if (rem == 0)
+ return num;
+ if (rem * 2 > den || (rem * 2 == den && num % 2))
+ return num += (num < 0 ? -1 : 1);
+ return num;
+}
+
+#endif //__GNC_RATIONAL_ROUNDING_HPP__
commit 0403a6667aaa2ff80563061786b9d51d6f42b2da
Author: John Ralls <jralls at ceridwen.us>
Date: Tue Jan 31 14:21:00 2017 -0800
Replace GncInt128âs flags variable with bit-stuffing in the high leg.
Loses three bits so GncInt128 becomes really a GncInt125, but we donât
really need the single order-of-magnitude: 10**38 is big enough. Saves
a full word of memory for each GncInt128, which means 2 words for GncRational.
Thatâs a 33% saving in memory for 64-bit and makes the object size the
same (32 bytes) for all architectures.
diff --git a/src/libqof/qof/gnc-int128.cpp b/src/libqof/qof/gnc-int128.cpp
index e846804..fdb87e2 100644
--- a/src/libqof/qof/gnc-int128.cpp
+++ b/src/libqof/qof/gnc-int128.cpp
@@ -32,6 +32,7 @@ extern "C"
#include <utility>
#include <cassert>
#include <cstdio>
+#include <sstream>
/* All algorithms from Donald E. Knuth, "The Art of Computer
* Programming, Volume 2: Seminumerical Algorithms", 3rd Ed.,
@@ -42,13 +43,29 @@ namespace {
static const unsigned int sublegs = GncInt128::numlegs * 2;
static const unsigned int sublegbits = GncInt128::legbits / 2;
static const uint64_t sublegmask = (UINT64_C(1) << sublegbits) - 1;
+ static const uint64_t flagmask = UINT64_C(0xe000000000000000);
+ static const uint64_t nummask = UINT64_C(0x1fffffffffffffff);
+/* Assumes that the high bits represent old flags to be replaced.
+ * Any overflow must be detected first!
+ */
+ static inline uint64_t set_flags(uint64_t leg, uint8_t flags)
+ {
+ auto flag_part = static_cast<uint64_t>(flags) << 61;
+ return flag_part + (leg & nummask);
+ }
+ static inline uint8_t get_flags(uint64_t leg)
+ {
+ return (leg & flagmask) >> 61;
+ }
+ static inline uint64_t get_num(uint64_t leg)
+ {
+ return leg & nummask;
+ }
}
-GncInt128::GncInt128 () : m_flags {}, m_hi {0}, m_lo {0}{}
+GncInt128::GncInt128 () : m_hi {0}, m_lo {0}{}
-GncInt128::GncInt128 (int64_t upper, int64_t lower, unsigned char flags) :
- m_flags {static_cast<unsigned char>(flags ^ (upper < 0 ? neg :
- upper == 0 && lower < 0 ? neg : pos))},
+GncInt128::GncInt128 (int64_t upper, int64_t lower, uint8_t flags) :
m_hi {static_cast<uint64_t>(upper < 0 ? -upper : upper)},
m_lo {static_cast<uint64_t>(lower < 0 ? -lower : lower)}
{
@@ -58,38 +75,75 @@ GncInt128::GncInt128 (int64_t upper, int64_t lower, unsigned char flags) :
m_lo += (m_hi << 63);
m_hi >>= 1;
+ if (m_hi & flagmask)
+ {
+ std::ostringstream ss;
+ ss << "Constructing GncInt128 with int64_t " << upper
+ << " which is too big.";
+ throw std::overflow_error(ss.str());
+ }
+ flags ^= (upper < 0 ? neg : upper == 0 && lower < 0 ? neg : pos);
+ m_hi = set_flags(m_hi, flags);
+}
+
+GncInt128::GncInt128 (int64_t upper, uint64_t lower, uint8_t flags) :
+ m_hi {static_cast<uint64_t>(upper < 0 ? -upper : upper)}, m_lo {lower}
+{
+ if (m_hi & flagmask)
+ {
+ std::ostringstream ss;
+ ss << "Constructing GncInt128 with int64_t " << upper
+ << " which is too big when lower is unsigned.";
+ throw std::overflow_error(ss.str());
+ }
+ flags ^= (upper < 0 ? neg : pos);
+ m_hi = set_flags(m_hi, flags);
}
-GncInt128::GncInt128 (int64_t upper, uint64_t lower, unsigned char flags) :
- m_flags {static_cast<unsigned char>(flags ^ (upper < 0 ? neg : pos))},
- m_hi {static_cast<uint64_t>(upper < 0 ? -upper : upper)}, m_lo {lower} {}
+GncInt128::GncInt128 (uint64_t upper, uint64_t lower, uint8_t flags) :
+ m_hi {upper}, m_lo {lower}
+ {
+ /* somewhere in the bowels of gnc_module.scm compilation this gets called
+ * with upper=INT64_MAX, which would otherwise throw. Make it our max value
+ * instead.
+ */
+ if (m_hi == UINT64_MAX)
+ m_hi = nummask;
+ if (m_hi & flagmask)
+ {
+ std::ostringstream ss;
+ ss << "Constructing GncInt128 with uint64_t " << upper
+ << " which is too big.";
+ throw std::overflow_error(ss.str());
+ }
-GncInt128::GncInt128 (uint64_t upper, uint64_t lower, unsigned char flags) :
- m_flags {flags}, m_hi {upper}, m_lo {lower} {}
+ m_hi = set_flags(m_hi, flags);
+}
GncInt128&
GncInt128::zero () noexcept
{
- m_flags = 0;
m_lo = m_hi = UINT64_C(0);
return *this;
}
GncInt128::operator int64_t() const
{
- if ((m_flags & neg) && isBig())
+ auto flags = get_flags(m_hi);
+ if ((flags & neg) && isBig())
throw std::underflow_error ("Negative value too large to represent as int64_t");
- if ((m_flags & (overflow | NaN)) || isBig())
+ if ((flags & (overflow | NaN)) || isBig())
throw std::overflow_error ("Value too large to represent as int64_t");
int64_t retval = static_cast<int64_t>(m_lo);
- return m_flags & neg ? -retval : retval;
+ return flags & neg ? -retval : retval;
}
GncInt128::operator uint64_t() const
{
- if (m_flags & neg)
+ auto flags = get_flags(m_hi);
+ if (flags & neg)
throw std::underflow_error ("Can't represent negative value as uint64_t");
- if ((m_flags & (overflow | NaN)) || (m_hi || m_lo > UINT64_MAX))
+ if ((flags & (overflow | NaN)) || (m_hi || m_lo > UINT64_MAX))
throw std::overflow_error ("Value to large to represent as uint64_t");
return m_lo;
}
@@ -98,22 +152,25 @@ GncInt128::operator uint64_t() const
int
GncInt128::cmp (const GncInt128& b) const noexcept
{
- if (m_flags & (overflow | NaN))
+ auto flags = get_flags(m_hi);
+ if (flags & (overflow | NaN))
return -1;
if (b.isOverflow () || b.isNan ())
return 1;
- if (m_flags & neg)
+ auto hi = get_num(m_hi);
+ auto bhi = get_num(b.m_hi);
+ if (flags & neg)
{
if (!b.isNeg()) return -1;
- if (m_hi > b.m_hi) return -1;
- if (m_hi < b.m_hi) return 1;
+ if (hi > bhi) return -1;
+ if (hi < bhi) return 1;
if (m_lo > b.m_lo) return -1;
if (m_lo < b.m_lo) return 1;
return 0;
}
if (b.isNeg()) return 1;
- if (m_hi < b.m_hi) return -1;
- if (m_hi > b.m_hi) return 1;
+ if (hi < bhi) return -1;
+ if (hi > bhi) return 1;
if (m_lo < b.m_lo) return -1;
if (m_lo > b.m_lo) return 1;
return 0;
@@ -190,37 +247,38 @@ GncInt128::pow(unsigned int b) const noexcept
bool
GncInt128::isNeg () const noexcept
{
- return (m_flags & neg);
+ return (get_flags(m_hi) & neg);
}
bool
GncInt128::isBig () const noexcept
{
- return (m_hi || m_lo > INT64_MAX);
+ return (get_num(m_hi) || m_lo > INT64_MAX);
}
bool
GncInt128::isOverflow () const noexcept
{
- return (m_flags & overflow);
+ return (get_flags(m_hi) & overflow);
}
bool
GncInt128::isNan () const noexcept
{
- return (m_flags & NaN);
+ return (get_flags(m_hi) & NaN);
}
bool
GncInt128::valid() const noexcept
{
- return !(m_flags & (overflow | NaN));
+ return !(get_flags(m_hi) & (overflow | NaN));
}
bool
GncInt128::isZero() const noexcept
{
- return ((m_flags & (overflow | NaN)) == 0 && m_hi == 0 && m_lo == 0);
+ return ((get_flags(m_hi) & (overflow | NaN)) == 0 &&
+ get_num(m_hi) == 0 && m_lo == 0);
}
GncInt128
@@ -235,8 +293,9 @@ GncInt128::abs() const noexcept
unsigned int
GncInt128::bits() const noexcept
{
- unsigned int bits {static_cast<unsigned int>(m_hi == 0 ? 0 : 64)};
- uint64_t temp {(m_hi == 0 ? m_lo : m_hi)};
+ auto hi = get_num(m_hi);
+ unsigned int bits {static_cast<unsigned int>(hi == 0 ? 0 : 64)};
+ uint64_t temp {(hi == 0 ? m_lo : hi)};
for (;temp > 0; temp >>= 1)
++bits;
return bits;
@@ -247,10 +306,12 @@ GncInt128
GncInt128::operator-() const noexcept
{
auto retval = *this;
+ auto flags = get_flags(retval.m_hi);
if (isNeg())
- retval.m_flags ^= neg;
+ flags ^= neg;
else
- retval.m_flags |= neg;
+ flags |= neg;
+ retval.m_hi = set_flags(retval.m_hi, flags);
return retval;
}
@@ -286,11 +347,12 @@ GncInt128::operator-- (int) noexcept
GncInt128&
GncInt128::operator+= (const GncInt128& b) noexcept
{
+ auto flags = get_flags(m_hi);
if (b.isOverflow())
- m_flags |= overflow;
+ flags |= overflow;
if (b.isNan())
- m_flags |= NaN;
-
+ flags |= NaN;
+ m_hi = set_flags(m_hi, flags);
if (isOverflow() || isNan())
return *this;
if ((isNeg () && !b.isNeg ()) || (!isNeg () && b.isNeg ()))
@@ -298,32 +360,38 @@ GncInt128::operator+= (const GncInt128& b) noexcept
uint64_t result = m_lo + b.m_lo;
uint64_t carry = static_cast<int64_t>(result < m_lo); //Wrapping
m_lo = result;
- result = m_hi + b.m_hi + carry;
- if (result < m_hi)
- m_flags |= overflow;
- m_hi = result;
+ auto hi = get_num(m_hi);
+ auto bhi = get_num(b.m_hi);
+ result = hi + bhi + carry;
+ if (result < hi || result & flagmask)
+ flags |= overflow;
+ m_hi = set_flags(result, flags);
return *this;
}
GncInt128&
GncInt128::operator<<= (unsigned int i) noexcept
{
+ auto flags = get_flags(m_hi);
if (i > maxbits)
{
- m_flags &= 0xfe;
- m_hi = 0;
+ flags &= 0xfe;
+ m_hi = set_flags(0, flags);
m_lo = 0;
return *this;
}
+ auto hi = get_num(m_hi);
if (i < legbits)
{
uint64_t carry {(m_lo & (((UINT64_C(1) << i) - 1) << (legbits - i)))};
m_lo <<= i;
- m_hi <<= i;
- m_hi += carry;
+ hi <<= i;
+ hi += carry;
+ m_hi = set_flags(hi, flags);
return *this;
}
- m_hi = m_lo << (i - legbits);
+ hi = m_lo << (i - legbits);
+ m_hi = set_flags(hi, flags);
m_lo = 0;
return *this;
}
@@ -331,33 +399,38 @@ GncInt128::operator<<= (unsigned int i) noexcept
GncInt128&
GncInt128::operator>>= (unsigned int i) noexcept
{
+ auto flags = get_flags(m_hi);
if (i > maxbits)
{
- m_flags &= 0xfe;
- m_hi = 0;
+ flags &= 0xfe;
+ m_hi = set_flags(0, flags);
m_lo = 0;
return *this;
}
+ auto hi = get_num(m_hi);
if (i < legbits)
{
- uint64_t carry {(m_hi & ((UINT64_C(1) << i) - 1))};
+ uint64_t carry {(hi & ((UINT64_C(1) << i) - 1))};
m_lo >>= i;
- m_hi >>= i;
+ hi >>= i;
m_lo += (carry << (legbits - i));
+ m_hi = set_flags(hi, flags);
return *this;
}
- m_lo = m_hi >> (i - legbits);
- m_hi = 0;
+ m_lo = hi >> (i - legbits);
+ m_hi = set_flags(0, flags);
return *this;
}
GncInt128&
GncInt128::operator-= (const GncInt128& b) noexcept
{
+ auto flags = get_flags(m_hi);
if (b.isOverflow())
- m_flags |= overflow;
+ flags |= overflow;
if (b.isNan())
- m_flags |= NaN;
+ flags |= NaN;
+ m_hi = set_flags(m_hi, flags);
if (isOverflow() || isNan())
return *this;
@@ -365,10 +438,11 @@ GncInt128::operator-= (const GncInt128& b) noexcept
if ((!isNeg() && b.isNeg()) || (isNeg() && !b.isNeg()))
return this->operator+= (-b);
bool operand_bigger {abs().cmp (b.abs()) < 0};
+ auto hi = get_num(m_hi);
+ auto far_hi = get_num(b.m_hi);
if (operand_bigger)
{
- m_flags ^= neg; // ^= flips the bit
- uint64_t far_hi {b.m_hi};
+ flags ^= neg; // ^= flips the bit
if (m_lo > b.m_lo)
{
/* The + 1 on the end is because we really want to use 2^64, or
@@ -379,20 +453,20 @@ GncInt128::operator-= (const GncInt128& b) noexcept
}
else
m_lo = b.m_lo - m_lo;
-
- m_hi = far_hi - m_hi;
+ hi = far_hi - hi;
+ m_hi = set_flags(hi, flags);
return *this;
}
if (m_lo < b.m_lo)
{
m_lo = UINT64_MAX - b.m_lo + m_lo + 1; //See UINT64_MAX comment above
- --m_hi; //borrow
+ --hi; //borrow
}
else
m_lo -= b.m_lo;
- m_hi -= b.m_hi;
-
+ hi -= far_hi;
+ m_hi = set_flags(hi, flags);
return *this;
}
@@ -400,38 +474,45 @@ GncInt128&
GncInt128::operator*= (const GncInt128& b) noexcept
{
/* Test for 0 first */
+ auto flags = get_flags(m_hi);
if (isZero() || b.isZero())
{
- m_hi = m_lo = 0;
+ m_lo = 0;
+ m_hi = set_flags(0, flags);
return *this;
}
if (b.isOverflow())
- m_flags |= overflow;
+ flags |= overflow;
if (b.isNan())
- m_flags |= NaN;
-
+ flags |= NaN;
+ m_hi = set_flags(m_hi, flags);
if (isOverflow() || isNan())
return *this;
/* Test for overflow before spending time on the calculation */
- if (m_hi && b.m_hi)
+ auto hi = get_num(m_hi);
+ auto bhi = get_num(b.m_hi);
+ if (hi && bhi)
{
- m_flags |= overflow;
+ flags |= overflow;
+ m_hi = set_flags(hi, flags);
return *this;
}
unsigned int abits {bits()}, bbits {b.bits()};
if (abits + bbits > maxbits)
{
- m_flags |= overflow;
+ flags |= overflow;
+ m_hi = set_flags(m_hi, flags);
return *this;
}
/* Handle the sign; ^ flips if b is negative */
- m_flags ^= (b.m_flags & neg);
+ flags ^= (get_flags(b.m_hi) & neg);
/* The trivial case */
if (abits + bbits <= legbits)
{
m_lo *= b.m_lo;
+ m_hi = set_flags(m_hi, flags);
return *this;
}
@@ -448,9 +529,9 @@ GncInt128::operator*= (const GncInt128& b) noexcept
*/
uint64_t av[sublegs] {(m_lo & sublegmask), (m_lo >> sublegbits),
- (m_hi & sublegmask), (m_hi >> sublegbits)};
+ (hi & sublegmask), (hi >> sublegbits)};
uint64_t bv[sublegs] {(b.m_lo & sublegmask), (b.m_lo >> sublegbits),
- (b.m_hi & sublegmask), (b.m_hi >> sublegbits)};
+ (bhi & sublegmask), (bhi >> sublegbits)};
uint64_t rv[sublegs] {};
uint64_t carry {}, scratch {};
@@ -478,19 +559,23 @@ GncInt128::operator*= (const GncInt128& b) noexcept
if (carry) //Shouldn't happen because of the checks above
{
- m_flags |= overflow;
+ flags |= overflow;
+ m_hi = set_flags(m_hi, flags);
return *this;
}
m_lo = rv[0] + (rv[1] << sublegbits);
carry = rv[1] >> sublegbits;
carry += (rv[1] << sublegbits) > m_lo || rv[0] > m_lo ? 1 : 0;
- m_hi = rv[2] + (rv[3] << sublegbits) + carry;
- if ((rv[3] << sublegbits) > m_hi || rv[2] > m_hi || (rv[3] >> sublegbits))
+ hi = rv[2] + (rv[3] << sublegbits) + carry;
+ if ((rv[3] << sublegbits) > hi || rv[2] > hi || (rv[3] >> sublegbits) ||
+ hi & flagmask)
{
- m_flags |= overflow;
+ flags |= overflow;
+ m_hi = set_flags(hi, flags);
return *this;
}
+ m_hi = set_flags(hi, flags);
return *this;
}
@@ -501,7 +586,8 @@ namespace {
*/
/* We're using arrays here instead of vectors to avoid an alloc. */
void
-div_multi_leg (uint64_t* u, size_t m, uint64_t* v, size_t n, GncInt128& q, GncInt128& r) noexcept
+div_multi_leg (uint64_t* u, size_t m, uint64_t* v, size_t n,
+ GncInt128& q, GncInt128& r) noexcept
{
/* D1, Normalization */
uint64_t qv[sublegs] {};
@@ -598,7 +684,8 @@ div_multi_leg (uint64_t* u, size_t m, uint64_t* v, size_t n, GncInt128& q, GncIn
}
void
-div_single_leg (uint64_t* u, size_t m, uint64_t v, GncInt128& q, GncInt128& r) noexcept
+div_single_leg (uint64_t* u, size_t m, uint64_t v,
+ GncInt128& q, GncInt128& r) noexcept
{
uint64_t qv[sublegs] {};
uint64_t carry {};
@@ -625,17 +712,23 @@ div_single_leg (uint64_t* u, size_t m, uint64_t v, GncInt128& q, GncInt128& r) n
void
GncInt128::div (const GncInt128& b, GncInt128& q, GncInt128& r) const noexcept
{
+ auto qflags = get_flags(q.m_hi);
+ auto rflags = get_flags(r.m_hi);
if (isOverflow() || b.isOverflow())
{
- q.m_flags |= overflow;
- r.m_flags |= overflow;
+ qflags |= overflow;
+ rflags |= overflow;
+ q.m_hi = set_flags(q.m_hi, qflags);
+ r.m_hi = set_flags(r.m_hi, rflags);
return;
}
if (isNan() || b.isNan())
{
- q.m_flags |= NaN;
- r.m_flags |= NaN;
+ qflags |= NaN;
+ rflags |= NaN;
+ q.m_hi = set_flags(q.m_hi, qflags);
+ r.m_hi = set_flags(r.m_hi, rflags);
return;
}
assert (&q != this);
@@ -646,23 +739,28 @@ GncInt128::div (const GncInt128& b, GncInt128& q, GncInt128& r) const noexcept
q.zero(), r.zero();
if (b.isZero())
{
- q.m_flags |= NaN;
- r.m_flags |= NaN;
+ qflags |= NaN;
+ rflags |= NaN;
+ q.m_hi = set_flags(q.m_hi, qflags);
+ r.m_hi = set_flags(r.m_hi, rflags);
return;
}
if (isNeg())
- q.m_flags |= neg;
+ qflags |= neg;
if (b.isNeg())
- q.m_flags ^= neg;
+ qflags ^= neg;
if (abs() < b.abs())
{
r = *this;
return;
}
- if (m_hi == 0 && b.m_hi == 0) //let the hardware do it
+ auto hi = get_num(m_hi);
+ auto bhi = get_num(b.m_hi);
+ q.m_hi = set_flags(hi, qflags);
+ if (hi == 0 && bhi == 0) //let the hardware do it
{
q.m_lo = m_lo / b.m_lo;
r.m_lo = m_lo % b.m_lo;
@@ -670,9 +768,9 @@ GncInt128::div (const GncInt128& b, GncInt128& q, GncInt128& r) const noexcept
}
uint64_t u[sublegs + 2] {(m_lo & sublegmask), (m_lo >> sublegbits),
- (m_hi & sublegmask), (m_hi >> sublegbits), 0, 0};
+ (hi & sublegmask), (hi >> sublegbits), 0, 0};
uint64_t v[sublegs] {(b.m_lo & sublegmask), (b.m_lo >> sublegbits),
- (b.m_hi & sublegmask), (b.m_hi >> sublegbits)};
+ (bhi & sublegmask), (bhi >> sublegbits)};
auto m = u[3] ? 4 : u[2] ? 3 : u[1] ? 2 : u[0] ? 1 : 0;
auto n = v[3] ? 4 : v[2] ? 3 : v[1] ? 2 : v[0] ? 1 : 0;
if (m == 0 || n == 0) //Shouldn't happen
@@ -699,30 +797,35 @@ GncInt128::operator%= (const GncInt128& b) noexcept
div(b, q, r);
std::swap (*this, r);
if (q.isNan())
- m_flags |= NaN;
+ m_hi = set_flags(m_hi, (get_flags(m_hi) | NaN));
return *this;
}
GncInt128&
GncInt128::operator&= (const GncInt128& b) noexcept
{
+ auto flags = get_flags(m_hi);
if (b.isOverflow())
- m_flags |= overflow;
+ flags |= overflow;
if (b.isNan())
- m_flags |= NaN;
-
+ flags |= NaN;
+ m_hi = set_flags(m_hi, flags);
if (isOverflow() || isNan())
return *this;
-
- m_hi &= b.m_hi;
+ auto hi = get_num(m_hi);
+ hi &= get_num(b.m_hi);
m_lo &= b.m_lo;
+ m_hi = set_flags(hi, flags);
return *this;
}
GncInt128&
GncInt128::operator|= (const GncInt128& b) noexcept
{
- m_hi ^= b.m_hi;
+ auto flags = get_flags(m_hi);
+ auto hi = get_num(m_hi);
+ hi ^= get_num(b.m_hi);
+ m_hi = set_flags(hi, flags);
m_lo ^= b.m_lo;
return *this;
}
@@ -730,15 +833,17 @@ GncInt128::operator|= (const GncInt128& b) noexcept
GncInt128&
GncInt128::operator^= (const GncInt128& b) noexcept
{
+ auto flags = get_flags(m_hi);
if (b.isOverflow())
- m_flags |= overflow;
+ flags |= overflow;
if (b.isNan())
- m_flags |= NaN;
-
+ flags |= NaN;
+ m_hi = set_flags(m_hi, flags);
if (isOverflow() || isNan())
return *this;
-
- m_hi ^= b.m_hi;
+ auto hi = get_num(m_hi);
+ hi ^= get_num(b.m_hi);
+ m_hi = set_flags(hi, flags);
m_lo ^= b.m_lo;
return *this;
}
@@ -801,7 +906,7 @@ GncInt128::asCharBufR(char* buf) const noexcept
return buf;
}
uint64_t d[dec_array_size] {};
- decimal_from_binary(d, m_hi, m_lo);
+ decimal_from_binary(d, get_num(m_hi), m_lo);
char* next = buf;
char neg {'-'};
diff --git a/src/libqof/qof/gnc-int128.hpp b/src/libqof/qof/gnc-int128.hpp
index f8b3926..bb99336 100644
--- a/src/libqof/qof/gnc-int128.hpp
+++ b/src/libqof/qof/gnc-int128.hpp
@@ -48,19 +48,21 @@ extern "C"
/** @addtogroup GncInt128
* @ingroup QOF
* @{
- * @brief provides a 128-bit int as a base class for GncNumeric.
+ * @brief provides a 125-bit int as a base class for GncNumeric.
*
- * All the usual operators are provided. Only the explicit integer
- * conversions throw; all other errors are indicated by the overflow
- * and NaN ("Not a Number") flags. Note that performing any operation
- * on an overflowed or NaN Gncint128 will yield an overflowed or NaN
- * result, so calling routines need not check until the end of a
- * chained calculation.
+ * In order to make space for the status flags the upper leg is limited to
+ * 0x1fffffffffffffff. Attempting to construct a GncInt128 with a larger upper
+ * leg will throw a std::overflow_error.
+ *
+ * All the usual operators are provided. Only the constructors and explicit
+ * integer conversions throw; all other errors are indicated by the overflow and
+ * NaN ("Not a Number") flags. Note that performing any operation on an
+ * overflowed or NaN Gncint128 will yield an overflowed or NaN result, so
+ * calling routines need not check until the end of a chained calculation.
* GncInt128 uses implicit copy and move constructors and implicit destructor.
*/
class GncInt128
{
- unsigned char m_flags;
uint64_t m_hi;
uint64_t m_lo;
diff --git a/src/libqof/qof/test/gtest-gnc-int128.cpp b/src/libqof/qof/test/gtest-gnc-int128.cpp
index 30d6191..c6402da 100644
--- a/src/libqof/qof/test/gtest-gnc-int128.cpp
+++ b/src/libqof/qof/test/gtest-gnc-int128.cpp
@@ -24,6 +24,8 @@
#include <gtest/gtest.h>
#include "../gnc-int128.hpp"
+static constexpr uint64_t UPPER_MAX{2305843009213693951};
+
TEST(qofint128_constructors, test_default_constructor)
{
GncInt128 value {};
@@ -36,112 +38,146 @@ TEST(qofint128_constructors, test_default_constructor)
TEST(qofint128_constructors, test_single_arg_constructor)
{
- GncInt128 value1 (INT64_C(0));
- EXPECT_TRUE (value1.isZero());
- EXPECT_FALSE (value1.isNeg());
- EXPECT_FALSE (value1.isBig());
- EXPECT_FALSE (value1.isOverflow());
- EXPECT_FALSE (value1.isNan());
-
- GncInt128 value2 (INT64_C(567894392130486208));
- EXPECT_FALSE (value2.isZero());
- EXPECT_FALSE (value2.isNeg());
- EXPECT_FALSE (value2.isBig());
- EXPECT_FALSE (value2.isOverflow());
- EXPECT_FALSE (value2.isNan());
-
- GncInt128 value3 (INT64_C(-567894392130486208));
- EXPECT_FALSE (value3.isZero());
- EXPECT_TRUE (value3.isNeg());
- EXPECT_FALSE (value3.isBig());
- EXPECT_FALSE (value3.isOverflow());
- EXPECT_FALSE (value3.isNan());
-
- GncInt128 value4 (UINT64_C(13567894392130486208));
- EXPECT_FALSE (value4.isZero());
- EXPECT_FALSE (value4.isNeg());
- EXPECT_TRUE (value4.isBig());
- EXPECT_FALSE (value4.isOverflow());
- EXPECT_FALSE (value4.isNan());
+ EXPECT_NO_THROW({
+ GncInt128 value1 (INT64_C(0));
+ EXPECT_TRUE (value1.isZero());
+ EXPECT_FALSE (value1.isNeg());
+ EXPECT_FALSE (value1.isBig());
+ EXPECT_FALSE (value1.isOverflow());
+ EXPECT_FALSE (value1.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value2 (INT64_C(567894392130486208));
+ EXPECT_FALSE (value2.isZero());
+ EXPECT_FALSE (value2.isNeg());
+ EXPECT_FALSE (value2.isBig());
+ EXPECT_FALSE (value2.isOverflow());
+ EXPECT_FALSE (value2.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value3 (INT64_C(-567894392130486208));
+ EXPECT_FALSE (value3.isZero());
+ EXPECT_TRUE (value3.isNeg());
+ EXPECT_FALSE (value3.isBig());
+ EXPECT_FALSE (value3.isOverflow());
+ EXPECT_FALSE (value3.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value4 (UINT64_C(13567894392130486208));
+ EXPECT_FALSE (value4.isZero());
+ EXPECT_FALSE (value4.isNeg());
+ EXPECT_TRUE (value4.isBig());
+ EXPECT_FALSE (value4.isOverflow());
+ EXPECT_FALSE (value4.isNan());
+ });
}
TEST(qofint128_constructors, test_double_arg_constructor)
{
- GncInt128 value1 (INT64_C(0), INT64_C(0));
- EXPECT_TRUE (value1.isZero());
- EXPECT_FALSE (value1.isNeg());
- EXPECT_FALSE (value1.isBig());
- EXPECT_FALSE (value1.isOverflow());
- EXPECT_FALSE (value1.isNan());
-
- GncInt128 value2 (INT64_C(0), INT64_C(567894392130486208));
- EXPECT_FALSE (value2.isZero());
- EXPECT_FALSE (value2.isNeg());
- EXPECT_FALSE (value2.isBig());
- EXPECT_FALSE (value2.isOverflow());
- EXPECT_FALSE (value2.isNan());
-
- GncInt128 value3 (INT64_C(567894392130486208), INT64_C(0));
- EXPECT_FALSE (value3.isZero());
- EXPECT_FALSE (value3.isNeg());
- EXPECT_TRUE (value3.isBig());
- EXPECT_FALSE (value3.isOverflow());
- EXPECT_FALSE (value3.isNan());
-
- GncInt128 value4 (INT64_C(567894392130486208), INT64_C(567894392130486208));
- EXPECT_FALSE (value4.isZero());
- EXPECT_FALSE (value4.isNeg());
- EXPECT_TRUE (value4.isBig());
- EXPECT_FALSE (value4.isOverflow());
- EXPECT_FALSE (value4.isNan());
-
- GncInt128 value5 (INT64_C(567894392130486208),
- INT64_C(-567894392130486208));
- EXPECT_FALSE (value5.isZero());
- EXPECT_FALSE (value5.isNeg());
- EXPECT_TRUE (value5.isBig());
- EXPECT_FALSE (value5.isOverflow());
- EXPECT_FALSE (value5.isNan());
-
- GncInt128 value6 (INT64_C(-567894392130486208),
- INT64_C(567894392130486208));
- EXPECT_FALSE (value6.isZero());
- EXPECT_TRUE (value6.isNeg());
- EXPECT_TRUE (value6.isBig());
- EXPECT_FALSE (value6.isOverflow());
- EXPECT_FALSE (value6.isNan());
-
- GncInt128 value7 (UINT64_C(13567894392130486208),
- UINT64_C(13567894392130486208), GncInt128::pos);
- EXPECT_FALSE (value7.isZero());
- EXPECT_FALSE (value7.isNeg());
- EXPECT_TRUE (value7.isBig());
- EXPECT_FALSE (value7.isOverflow());
- EXPECT_FALSE (value7.isNan());
-
- GncInt128 value8 (UINT64_C(13567894392130486208),
- UINT64_C(13567894392130486208), GncInt128::neg);
- EXPECT_FALSE (value8.isZero());
- EXPECT_TRUE (value8.isNeg());
- EXPECT_TRUE (value8.isBig());
- EXPECT_FALSE (value8.isOverflow());
- EXPECT_FALSE (value8.isNan());
-
- GncInt128 value9 (UINT64_C(13567894392130486208),
- UINT64_C(13567894392130486208), GncInt128::overflow);
- EXPECT_FALSE (value9.isZero());
- EXPECT_FALSE (value9.isNeg());
- EXPECT_TRUE (value9.isBig());
- EXPECT_TRUE (value9.isOverflow());
- EXPECT_FALSE (value9.isNan());
-
- GncInt128 value10 (UINT64_C(13567894392130486208),
- UINT64_C(13567894392130486208), GncInt128::NaN);
- EXPECT_FALSE (value10.isZero());
- EXPECT_FALSE (value10.isNeg());
- EXPECT_TRUE (value10.isBig());
- EXPECT_FALSE (value10.isOverflow());
- EXPECT_TRUE (value10.isNan());
+ EXPECT_NO_THROW({
+ GncInt128 value1 (INT64_C(0), INT64_C(0));
+ EXPECT_TRUE (value1.isZero());
+ EXPECT_FALSE (value1.isNeg());
+ EXPECT_FALSE (value1.isBig());
+ EXPECT_FALSE (value1.isOverflow());
+ EXPECT_FALSE (value1.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value2 (INT64_C(0), INT64_C(567894392130486208));
+ EXPECT_FALSE (value2.isZero());
+ EXPECT_FALSE (value2.isNeg());
+ EXPECT_FALSE (value2.isBig());
+ EXPECT_FALSE (value2.isOverflow());
+ EXPECT_FALSE (value2.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value3 (INT64_C(567894392130486208), INT64_C(0));
+ EXPECT_FALSE (value3.isZero());
+ EXPECT_FALSE (value3.isNeg());
+ EXPECT_TRUE (value3.isBig());
+ EXPECT_FALSE (value3.isOverflow());
+ EXPECT_FALSE (value3.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value4 (INT64_C(567894392130486208),
+ INT64_C(567894392130486208));
+ EXPECT_FALSE (value4.isZero());
+ EXPECT_FALSE (value4.isNeg());
+ EXPECT_TRUE (value4.isBig());
+ EXPECT_FALSE (value4.isOverflow());
+ EXPECT_FALSE (value4.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value5 (INT64_C(567894392130486208),
+ INT64_C(-567894392130486208));
+ EXPECT_FALSE (value5.isZero());
+ EXPECT_FALSE (value5.isNeg());
+ EXPECT_TRUE (value5.isBig());
+ EXPECT_FALSE (value5.isOverflow());
+ EXPECT_FALSE (value5.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value6 (INT64_C(-567894392130486208),
+ INT64_C(567894392130486208));
+ EXPECT_FALSE (value6.isZero());
+ EXPECT_TRUE (value6.isNeg());
+ EXPECT_TRUE (value6.isBig());
+ EXPECT_FALSE (value6.isOverflow());
+ EXPECT_FALSE (value6.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value7 (UINT64_C(1695986799016310843),
+ UINT64_C(13567894392130486208), GncInt128::pos);
+ EXPECT_FALSE (value7.isZero());
+ EXPECT_FALSE (value7.isNeg());
+ EXPECT_TRUE (value7.isBig());
+ EXPECT_FALSE (value7.isOverflow());
+ EXPECT_FALSE (value7.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value8 (UINT64_C(1695986799016310843),
+ UINT64_C(13567894392130486208), GncInt128::neg);
+ EXPECT_FALSE (value8.isZero());
+ EXPECT_TRUE (value8.isNeg());
+ EXPECT_TRUE (value8.isBig());
+ EXPECT_FALSE (value8.isOverflow());
+ EXPECT_FALSE (value8.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value9 (UINT64_C(1695986799016310843),
+ UINT64_C(13567894392130486208),
+ GncInt128::overflow);
+ EXPECT_FALSE (value9.isZero());
+ EXPECT_FALSE (value9.isNeg());
+ EXPECT_TRUE (value9.isBig());
+ EXPECT_TRUE (value9.isOverflow());
+ EXPECT_FALSE (value9.isNan());
+ });
+
+ EXPECT_NO_THROW({
+ GncInt128 value10 (UINT64_C(1695986799016310843),
+ UINT64_C(13567894392130486208), GncInt128::NaN);
+ EXPECT_FALSE (value10.isZero());
+ EXPECT_FALSE (value10.isNeg());
+ EXPECT_TRUE (value10.isBig());
+ EXPECT_FALSE (value10.isOverflow());
+ EXPECT_TRUE (value10.isNan());
+ });
+
+ EXPECT_THROW(GncInt128 value10 (UINT64_C(13567894392130486208),
+ UINT64_C(13567894392130486208)),
+ std::overflow_error);
}
TEST(qofint128_functions, test_int_functions)
@@ -149,9 +185,13 @@ TEST(qofint128_functions, test_int_functions)
int64_t arg {INT64_C(567894392130486208)};
int64_t narg {INT64_C(-567894392130486208)};
uint64_t uarg {UINT64_C(13567894392130486208)};
- GncInt128 value1 (INT64_C(0), arg);
- EXPECT_EQ (arg, static_cast<int64_t>(value1));
- EXPECT_EQ (static_cast<uint64_t>(arg), static_cast<uint64_t>(value1));
+
+ EXPECT_NO_THROW({
+ GncInt128 value1 (INT64_C(0), arg);
+ EXPECT_EQ (arg, static_cast<int64_t>(value1));
+ EXPECT_EQ (static_cast<uint64_t>(arg),
+ static_cast<uint64_t>(value1));
+ });
GncInt128 value2 (UINT64_C(0), uarg);
EXPECT_THROW (auto v = static_cast<int64_t>(value2), std::overflow_error);
@@ -291,19 +331,22 @@ TEST(qofint128_functions, add_and_subtract)
{
/* UINT64_MAX = 18,446,744,073,709,551,615
* INT64_MAX = 9,223,372,036,854,775,807
- * barg + sarg = INT64_MAX
+ * upper leg max = 2,305,843,009,213,693,951
+ * barg + marg = INT64_MAX
* barg + uarg = UINT64_MAX
+ * marg + sarg = upper leg max
*/
int64_t barg {INT64_C(4878849681579065407)};
- int64_t sarg {INT64_C(4344522355275710400)};
+ int64_t marg {INT64_C(4344522355275710400)};
+ int64_t sarg {INT64_C(267163663151677502)};
uint64_t uarg {UINT64_C(13567894392130486208)};
- GncInt128 smallest (sarg + 100);
+ GncInt128 smallest (marg + 100);
GncInt128 smaller (barg + 500);
GncInt128 small (uarg);
GncInt128 big (sarg, barg);
- GncInt128 bigger (static_cast<uint64_t>(barg), uarg);
- GncInt128 biggest (uarg, static_cast<uint64_t>(barg));
+ GncInt128 bigger (marg, barg);
+ GncInt128 biggest (sarg, static_cast<uint64_t>(barg) + uarg);
GncInt128 nsmall (UINT64_C(0), uarg, GncInt128::neg);
EXPECT_EQ (GncInt128(INT64_C(2), INT64_C(499)), small += smaller);
@@ -311,12 +354,11 @@ TEST(qofint128_functions, add_and_subtract)
nsmall -= smaller);
EXPECT_EQ (GncInt128(uarg), small -= smaller);
- EXPECT_EQ (GncInt128(static_cast<uint64_t>(barg + sarg/2), UINT64_MAX),
+ EXPECT_EQ (GncInt128(UINT64_C(2305843009213693951),
+ UINT64_C(9757699363158130814)),
bigger += big);
- EXPECT_EQ (GncInt128(static_cast<uint64_t>(barg), uarg), bigger -= big);
+ EXPECT_EQ (GncInt128(marg, barg), bigger -= big);
bigger += biggest;
- EXPECT_EQ (GncInt128(UINT64_MAX, UINT64_MAX), bigger);
- bigger += smallest;
EXPECT_TRUE (bigger.isOverflow());
bigger -= biggest;
EXPECT_TRUE (bigger.isOverflow());
@@ -325,38 +367,70 @@ TEST(qofint128_functions, add_and_subtract)
TEST(qofint128_functions, multiply)
{
int64_t barg {INT64_C(4878849681579065407)};
- int64_t sarg {INT64_C(4344522355275710400)};
+ int64_t marg {INT64_C(4344522355275710400)};
+ int64_t sarg {INT64_C(267163663151677502)};
uint64_t uarg {UINT64_C(13567894392130486208)};
- GncInt128 smallest (sarg);
+ GncInt128 smallest (marg);
GncInt128 smaller (barg);
GncInt128 small (uarg);
GncInt128 big (sarg, barg);
- GncInt128 bigger (static_cast<uint64_t>(barg), uarg);
-
- small *= big;
- EXPECT_TRUE (small.isOverflow());
- big *= bigger;
- EXPECT_TRUE (big.isOverflow());
- EXPECT_EQ (GncInt128(UINT64_C(1149052180967758316), UINT64_C(6323251814974894144)), smallest *= smaller);
- EXPECT_FALSE (smallest.isOverflow());
+ GncInt128 bigger (sarg, uarg);
+ GncInt128 nsmaller (-barg);
+ GncInt128 nsmallest (-marg);
+ GncInt128 tiny (53);
+ GncInt128 teensy (37);
+
+ EXPECT_NO_THROW({
+ small *= big;
+ EXPECT_TRUE (small.isOverflow());
+ big *= bigger;
+ EXPECT_TRUE (big.isOverflow());
+ EXPECT_EQ(1961, tiny * teensy);
+ EXPECT_EQ(-1961, -tiny * teensy);
+ EXPECT_EQ(-1961, tiny * -teensy);
+ EXPECT_EQ(1961, -tiny * -teensy);
+ EXPECT_EQ (GncInt128(INT64_C(1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ smallest * smaller);
+ EXPECT_EQ (GncInt128(INT64_C(-1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ -smallest * smaller);
+ EXPECT_EQ (GncInt128(INT64_C(-1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ smallest * -smaller);
+ EXPECT_EQ (GncInt128(INT64_C(1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ -smallest * -smaller);
+ EXPECT_EQ (GncInt128(INT64_C(-1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ nsmallest * smaller);
+ EXPECT_EQ (GncInt128(INT64_C(-1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ smallest * nsmaller);
+ EXPECT_EQ (GncInt128(INT64_C(1149052180967758316),
+ UINT64_C(6323251814974894144)),
+ nsmallest * nsmaller);
+ EXPECT_FALSE (smallest.isOverflow());
+ });
}
TEST(qofint128_functions, divide)
{
int64_t barg {INT64_C(4878849681579065407)};
- int64_t sarg {INT64_C(4344522355275710400)};
+ int64_t marg {INT64_C(4344522355275710400)};
+ int64_t sarg {INT64_C(267163663151677502)};
uint64_t uarg {UINT64_C(13567894392130486208)};
GncInt128 zero (INT64_C(0));
GncInt128 one (INT64_C(1));
GncInt128 two (INT64_C(2));
- GncInt128 smallest (sarg);
+ GncInt128 smallest (marg);
GncInt128 smaller (barg);
GncInt128 small (uarg);
GncInt128 big (sarg, barg);
- GncInt128 bigger (static_cast<uint64_t>(barg), uarg);
+ GncInt128 bigger (static_cast<uint64_t>(sarg), uarg);
GncInt128 nsmall = -small;
GncInt128 nbig = -bigger;
@@ -392,28 +466,24 @@ TEST(qofint128_functions, divide)
EXPECT_EQ (zero, r);
big.div (smaller, q, r);
- EXPECT_EQ (GncInt128(INT64_C(8213236443097627766)), q);
- EXPECT_EQ (GncInt128(INT64_C(3162679692459777845)), r);
+ EXPECT_EQ (GncInt128(INT64_C(505067796878574019)), q);
+ EXPECT_EQ (GncInt128(INT64_C(1659575984014676290)), r);
bigger.div (big, q, r);
EXPECT_EQ (two, q);
- EXPECT_EQ (GncInt128(UINT64_C(534327326303355007),
- UINT64_C(3810195028972355394)), r);
+ EXPECT_EQ (GncInt128(UINT64_C(3810195028972355394)), r);
bigger.div (-big, q, r);
EXPECT_EQ (-two, q);
- EXPECT_EQ (GncInt128(UINT64_C(534327326303355007),
- UINT64_C(3810195028972355394)), r);
+ EXPECT_EQ (GncInt128(UINT64_C(3810195028972355394)), r);
nbig.div (-big, q, r);
EXPECT_EQ (two, q);
- EXPECT_EQ (GncInt128(UINT64_C(534327326303355007),
- UINT64_C(3810195028972355394)), r);
+ EXPECT_EQ (GncInt128(UINT64_C(3810195028972355394)), r);
nbig.div (-big, q, r);
EXPECT_EQ (two, q);
- EXPECT_EQ (GncInt128(UINT64_C(534327326303355007),
- UINT64_C(3810195028972355394)), r);
+ EXPECT_EQ (GncInt128(UINT64_C(3810195028972355394)), r);
big.div (bigger, q, r);
EXPECT_EQ (zero, q);
@@ -422,7 +492,7 @@ TEST(qofint128_functions, divide)
big.div (big - 1, q, r);
EXPECT_EQ(one, q);
EXPECT_EQ(one, r);
-
+
EXPECT_EQ (big, big %= bigger);
EXPECT_EQ (two, bigger /= big);
}
@@ -430,24 +500,27 @@ TEST(qofint128_functions, divide)
TEST(qofint128_functions, GCD)
{
int64_t barg {INT64_C(4878849681579065407)};
- int64_t sarg {INT64_C(4344522355275710401)};
+ int64_t marg {INT64_C(4344522355275710400)};
+ int64_t sarg {INT64_C(267163663151677502)};
uint64_t uarg {UINT64_C(13567894392130486208)};
GncInt128 one (INT64_C(1));
GncInt128 smallest (sarg);
- GncInt128 smaller (barg);
- GncInt128 small (uarg);
-
- GncInt128 big = smaller * smallest;
- GncInt128 bigger = small * smaller;
-
- EXPECT_EQ (smaller, big.gcd(smaller));
- EXPECT_EQ (smallest, big.gcd(smallest));
- EXPECT_EQ (small, bigger.gcd(small));
- EXPECT_EQ (smaller, bigger.gcd(smaller));
- EXPECT_EQ (one, big.gcd (small));
- EXPECT_EQ (one, bigger.gcd (smallest));
- EXPECT_EQ (big, smaller.lcm (smallest));
+ GncInt128 smaller (marg);
+ GncInt128 small (barg);
+
+ EXPECT_NO_THROW({
+ GncInt128 big = smaller * smallest;
+ GncInt128 bigger = small * smaller;
+
+ EXPECT_EQ (smaller, big.gcd(smaller));
+ EXPECT_EQ (smallest, big.gcd(smallest));
+ EXPECT_EQ (small, bigger.gcd(small));
+ EXPECT_EQ (smaller, bigger.gcd(smaller));
+ EXPECT_EQ (one, big.gcd (small));
+ EXPECT_EQ (2, bigger.gcd (smallest));
+ EXPECT_EQ (big >> 1, smaller.lcm (smallest));
+ });
}
TEST(qofint128_functions, pow)
commit c633e80a2480725f5a7ca85778270b139693cb0a
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 15:01:27 2017 -0800
Convert GncRational to throw instead of using a status byte.
More consistent with GncNumeric and saves a word of memory per instance.
Still bleeping huge because the two GncInt128s each need 128 bits (2 or 4 words)
plus a word for status (for 3 bits!).
Also provide a couple of convenience functions, is_big() and valid() to
test if the either numerator and denominator is big or overflowed or NaN.
diff --git a/src/libqof/qof/gnc-int128.cpp b/src/libqof/qof/gnc-int128.cpp
index f12ec91..e846804 100644
--- a/src/libqof/qof/gnc-int128.cpp
+++ b/src/libqof/qof/gnc-int128.cpp
@@ -212,6 +212,12 @@ GncInt128::isNan () const noexcept
}
bool
+GncInt128::valid() const noexcept
+{
+ return !(m_flags & (overflow | NaN));
+}
+
+bool
GncInt128::isZero() const noexcept
{
return ((m_flags & (overflow | NaN)) == 0 && m_hi == 0 && m_lo == 0);
diff --git a/src/libqof/qof/gnc-int128.hpp b/src/libqof/qof/gnc-int128.hpp
index 700c69d..f8b3926 100644
--- a/src/libqof/qof/gnc-int128.hpp
+++ b/src/libqof/qof/gnc-int128.hpp
@@ -210,6 +210,10 @@ enum // Values for m_flags
* @return true if the object represents 0.
*/
bool isZero() const noexcept;
+/**
+ * @return true if neither the overflow nor nan flags are set.
+ */
+ bool valid() const noexcept;
/**
* @return the number of bits used to represent the value
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 34345d7..63a8941 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -328,14 +328,7 @@ GncNumeric::to_decimal(unsigned int max_places) const
return GncNumeric(m_num / excess, powten(max_places));
}
GncRational rr(*this);
- rr.round(powten(max_places), RoundType::never);
- if (rr.m_error)
- {
- std::ostringstream msg;
- msg << "GncNumeric " << *this
- << " could not be represented as a decimal without rounding.\n";
- throw std::range_error(msg.str());
- }
+ rr.round(powten(max_places), RoundType::never); //May throw
/* rr might have gotten reduced a bit too much; if so, put it back: */
unsigned int pwr{1};
for (; pwr <= max_places && !(rr.m_den % powten(pwr)); ++pwr);
@@ -804,11 +797,8 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto sum = ar + br;
sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
- if (sum.m_error)
- return gnc_numeric_error(sum.m_error);
- if (sum.m_num.isBig() || sum.m_den.isBig() ||
- sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
- sum.m_num.isNan() || sum.m_den.isNan())
+
+ if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(sum);
}
@@ -827,6 +817,11 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
/* *******************************************************************
@@ -854,11 +849,7 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto sum = ar - br;
sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
- if (sum.m_error)
- return gnc_numeric_error(sum.m_error);
- if (sum.m_num.isBig() || sum.m_den.isBig() ||
- sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
- sum.m_num.isNan() || sum.m_den.isNan())
+ if (sum.is_big() || !sum.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(sum);
}
@@ -877,6 +868,11 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
/* *******************************************************************
@@ -903,11 +899,7 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto prod = ar * br;
prod.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
- if (prod.m_error)
- return gnc_numeric_error(prod.m_error);
- if (prod.m_num.isBig() || prod.m_den.isBig() ||
- prod.m_num.isOverflow() || prod.m_den.isOverflow() ||
- prod.m_num.isNan() || prod.m_den.isNan())
+ if (prod.is_big() || !prod.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(prod);
}
@@ -926,6 +918,11 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
@@ -953,11 +950,7 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
GncRational ar(a), br(b);
auto quot = ar / br;
quot.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
- if (quot.m_error)
- return gnc_numeric_error(quot.m_error);
- if (quot.m_num.isBig() || quot.m_den.isBig() ||
- quot.m_num.isOverflow() || quot.m_den.isOverflow() ||
- quot.m_num.isNan() || quot.m_den.isNan())
+ if (quot.is_big() || !quot.valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
return GncNumeric(quot);
}
@@ -976,6 +969,11 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
/* *******************************************************************
@@ -1056,7 +1054,11 @@ gnc_numeric_reduce(gnc_numeric in)
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
-
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
@@ -1112,6 +1114,11 @@ gnc_numeric_invert(gnc_numeric num)
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
/* *******************************************************************
@@ -1144,6 +1151,11 @@ double_to_gnc_numeric(double in, gint64 denom, gint how)
PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_ARG);
}
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
}
/* *******************************************************************
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 95fc33d..d66122f 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -26,7 +26,7 @@
GncRational::GncRational(GncNumeric n) noexcept :
- m_num(n.num()), m_den(n.denom()), m_error(GNC_ERROR_OK)
+ m_num(n.num()), m_den(n.denom())
{
if (m_den.isNeg())
{
@@ -36,7 +36,7 @@ GncRational::GncRational(GncNumeric n) noexcept :
}
GncRational::GncRational (gnc_numeric n) noexcept :
- m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK}
+ m_num (n.num), m_den (n.denom)
{
if (m_den.isNeg())
{
@@ -45,13 +45,26 @@ GncRational::GncRational (gnc_numeric n) noexcept :
}
}
+bool
+GncRational::valid() const noexcept
+{
+ if (m_num.valid() && m_den.valid())
+ return true;
+ return false;
+}
+
+bool
+GncRational::is_big() const noexcept
+{
+ if (m_num.isBig() || m_den.isBig())
+ return true;
+ return false;
+}
+
GncRational::operator gnc_numeric () const noexcept
{
- if (m_num.isOverflow() || m_num.isNan() ||
- m_den.isOverflow() || m_den.isNan())
+ if (!valid())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- if (m_error != GNC_ERROR_OK)
- return gnc_numeric_error (m_error);
try
{
return {static_cast<int64_t>(m_num), static_cast<int64_t>(m_den)};
@@ -87,16 +100,12 @@ GncRational::inv () noexcept
GncRational
operator+(GncRational a, GncRational b)
{
- if (a.m_error || b.m_error)
- {
- if (b.m_error)
- return GncRational(0, 1, b.m_error);
- return GncRational(0, 1, a.m_error);
- }
+ if (!(a.valid() && b.valid()))
+ throw std::range_error("Operator+ called with out-of-range operand.");
GncInt128 lcm = a.m_den.lcm(b.m_den);
GncInt128 num(a.m_num * lcm / a.m_den + b.m_num * lcm / b.m_den);
- if (lcm.isOverflow() || lcm.isNan() || num.isOverflow() || num.isNan())
- return GncRational(0, 1, GNC_ERROR_OVERFLOW);
+ if (!(lcm.valid() && num.valid()))
+ throw std::overflow_error("Operator+ overflowed.");
GncRational retval(num, lcm);
return retval;
}
@@ -111,15 +120,11 @@ operator-(GncRational a, GncRational b)
GncRational
operator*(GncRational a, GncRational b)
{
- if (a.m_error || b.m_error)
- {
- if (b.m_error)
- return GncRational(0, 1, b.m_error);
- return GncRational(0, 1, a.m_error);
- }
+ if (!(a.valid() && b.valid()))
+ throw std::range_error("Operator* called with out-of-range operand.");
GncInt128 num (a.m_num * b.m_num), den(a.m_den * b.m_den);
- if (num.isOverflow() || num.isNan() || den.isOverflow() || den.isNan())
- return GncRational(0, 1, GNC_ERROR_OVERFLOW);
+ if (!(num.valid() && den.valid()))
+ throw std::overflow_error("Operator* overflowed.");
GncRational retval(num, den);
return retval;
}
@@ -127,12 +132,10 @@ operator*(GncRational a, GncRational b)
GncRational
operator/(GncRational a, GncRational b)
{
- if (a.m_error || b.m_error)
- {
- if (b.m_error)
- return GncRational(0, 1, b.m_error);
- return GncRational(0, 1, a.m_error);
- }
+ if (!(a.valid() && b.valid()))
+ throw std::range_error("Operator/ called with out-of-range operand.");
+ if (b.m_num == 0)
+ throw std::underflow_error("Divide by 0.");
if (b.m_num.isNeg())
{
a.m_num = -a.m_num;
@@ -155,8 +158,8 @@ operator/(GncRational a, GncRational b)
}
GncInt128 num(a.m_num * b.m_den), den(a.m_den * b.m_num);
- if (num.isOverflow() || num.isNan() || den.isOverflow() || den.isNan())
- return GncRational(0, 1, GNC_ERROR_OVERFLOW);
+ if (!(num.valid() && den.valid()))
+ throw std::overflow_error("Operator/ overflowed.");
return GncRational(num, den);
}
@@ -261,8 +264,7 @@ GncRational::round (GncInt128 new_den, RoundType rtype)
switch (rtype)
{
case RoundType::never:
- m_error = GNC_ERROR_REMAINDER;
- return;
+ throw std::domain_error("Rounding required when 'never round' specified.");
case RoundType::floor:
if (new_num.isNeg()) ++new_num;
break;
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 415035f..9bfa6d4 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -37,16 +37,23 @@ enum class DenomType;
class GncRational
{
public:
- GncRational() : m_num(0), m_den(1), m_error(GNC_ERROR_OK) {}
+ GncRational() : m_num(0), m_den(1) {}
GncRational (gnc_numeric n) noexcept;
GncRational(GncNumeric n) noexcept;
- GncRational (GncInt128 num, GncInt128 den,
- GNCNumericErrorCode err=GNC_ERROR_OK) noexcept
- : m_num(num), m_den(den), m_error(err) {}
+ GncRational (GncInt128 num, GncInt128 den) noexcept
+ : m_num(num), m_den(den) {}
GncRational(const GncRational& rhs) = default;
GncRational(GncRational&& rhs) = default;
GncRational& operator=(const GncRational& rhs) = default;
GncRational& operator=(GncRational&& rhs) = default;
+/** Report if both members are valid numbers.
+ * \return true if neither numerator nor denominator are Nan or Overflowed.
+ */
+ bool valid() const noexcept;
+/** Report if either numerator or denominator are too big to fit in an int64_t.
+ * \return true if either is too big.
+ */
+ bool is_big() const noexcept;
/** Conversion operator; use static_cast<gnc_numeric>(foo). */
operator gnc_numeric() const noexcept;
/** Make a new GncRational with the opposite sign. */
@@ -78,7 +85,6 @@ public:
GncInt128 m_num;
GncInt128 m_den;
- GNCNumericErrorCode m_error;
};
GncRational operator+(GncRational a, GncRational b);
diff --git a/src/libqof/qof/test/gtest-gnc-numeric.cpp b/src/libqof/qof/test/gtest-gnc-numeric.cpp
index e825ecf..9ba7a68 100644
--- a/src/libqof/qof/test/gtest-gnc-numeric.cpp
+++ b/src/libqof/qof/test/gtest-gnc-numeric.cpp
@@ -499,7 +499,7 @@ TEST(gnc_numeric_functions, test_conversion_to_decimal)
EXPECT_EQ(1000, r.denom());
EXPECT_THROW(r = a.to_decimal(2), std::range_error);
GncNumeric b(123456789, 456);
- EXPECT_THROW(r = b.to_decimal(), std::range_error);
+ EXPECT_THROW(r = b.to_decimal(), std::domain_error);
GncNumeric c(123456789, 450);
EXPECT_NO_THROW(r = c.to_decimal());
EXPECT_EQ(27434842, r.num());
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 78a0edf..b1124e4 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -28,118 +28,109 @@
TEST(gncrational_constructors, test_default_constructor)
{
- GncRational value;
- EXPECT_EQ(value.m_num, 0);
- EXPECT_EQ(value.m_den, 1);
- EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+ EXPECT_NO_THROW({
+ GncRational value;
+ EXPECT_EQ(value.m_num, 0);
+ EXPECT_EQ(value.m_den, 1);
+ });
}
TEST(gncrational_constructors, test_gnc_numeric_constructor)
{
gnc_numeric input = gnc_numeric_create(123, 456);
- GncRational value(input);
- EXPECT_EQ(input.num, value.m_num);
- EXPECT_EQ(input.denom, value.m_den);
- EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+ EXPECT_NO_THROW({
+ GncRational value(input);
+ EXPECT_EQ(input.num, value.m_num);
+ EXPECT_EQ(input.denom, value.m_den);
+ });
}
TEST(gncrational_constructors, test_gnc_int128_constructor)
{
GncInt128 num(123), denom(456);
- GncRational value(num, denom);
- EXPECT_EQ(123, value.m_num);
- EXPECT_EQ(456, value.m_den);
- EXPECT_EQ(GNC_ERROR_OK, value.m_error);
+ EXPECT_NO_THROW({
+ GncRational value(num, denom);
+ EXPECT_EQ(123, value.m_num);
+ EXPECT_EQ(456, value.m_den);
+ });
}
TEST(gncrational_constructors, test_implicit_int_constructor)
{
int num(123), denom(456);
- GncRational value(num, denom);
- EXPECT_EQ(123, value.m_num);
- EXPECT_EQ(456, value.m_den);
- EXPECT_EQ(GNC_ERROR_OK, value.m_error);
-}
-
-TEST(gncrational_constructors, test_with_error_code)
-{
- int num(123), denom(456);
- GncRational value(num, denom, GNC_ERROR_OVERFLOW);
- EXPECT_EQ(123, value.m_num);
- EXPECT_EQ(456, value.m_den);
- EXPECT_EQ(GNC_ERROR_OVERFLOW, value.m_error);
+ EXPECT_NO_THROW({
+ GncRational value(num, denom);
+ EXPECT_EQ(123, value.m_num);
+ EXPECT_EQ(456, value.m_den);
+ });
}
TEST(gncrational_operators, test_addition)
{
- GncRational a(123456789987654321, 1000000000);
- GncRational b(65432198765432198, 100000000);
- GncRational c = a + b;
- EXPECT_EQ (777778777641976301, c.m_num);
- EXPECT_EQ (1000000000, c.m_den);
- EXPECT_EQ (GNC_ERROR_OK, c.m_error);
- a += b;
- EXPECT_EQ (777778777641976301, a.m_num);
- EXPECT_EQ (1000000000, a.m_den);
- EXPECT_EQ (GNC_ERROR_OK, a.m_error);
+ EXPECT_NO_THROW({
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a + b;
+ EXPECT_EQ (777778777641976301, c.m_num);
+ EXPECT_EQ (1000000000, c.m_den);
+ a += b;
+ EXPECT_EQ (777778777641976301, a.m_num);
+ EXPECT_EQ (1000000000, a.m_den);
+ });
}
TEST(gncrational_operators, test_subtraction)
{
- GncRational a(123456789987654321, 1000000000);
- GncRational b(65432198765432198, 100000000);
- GncRational c = a - b;
- EXPECT_EQ (-530865197666667659, c.m_num);
- EXPECT_TRUE(c.m_num.isNeg());
- EXPECT_EQ (1000000000, c.m_den);
- EXPECT_EQ (GNC_ERROR_OK, c.m_error);
- c = b - a;
- EXPECT_EQ (530865197666667659, c.m_num);
- EXPECT_FALSE(c.m_num.isNeg());
- EXPECT_EQ (1000000000, c.m_den);
- EXPECT_EQ (GNC_ERROR_OK, c.m_error);
- a -= b;
- EXPECT_EQ (-530865197666667659, a.m_num);
- EXPECT_TRUE(a.m_num.isNeg());
- EXPECT_EQ (1000000000, a.m_den);
- EXPECT_EQ (GNC_ERROR_OK, a.m_error);
+ EXPECT_NO_THROW({
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a - b;
+ EXPECT_EQ (-530865197666667659, c.m_num);
+ EXPECT_TRUE(c.m_num.isNeg());
+ EXPECT_EQ (1000000000, c.m_den);
+ c = b - a;
+ EXPECT_EQ (530865197666667659, c.m_num);
+ EXPECT_FALSE(c.m_num.isNeg());
+ EXPECT_EQ (1000000000, c.m_den);
+ a -= b;
+ EXPECT_EQ (-530865197666667659, a.m_num);
+ EXPECT_TRUE(a.m_num.isNeg());
+ EXPECT_EQ (1000000000, a.m_den);
+ });
}
TEST(gncrational_operators, test_multiplication)
{
- GncRational a(123456789987654321, 1000000000);
- GncRational b(65432198765432198, 100000000);
- GncRational c = a * b;
- EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
- UINT64_C(8081008345983448486)), c.m_num);
- EXPECT_EQ (100000000000000000, c.m_den);
- EXPECT_EQ (GNC_ERROR_OK, c.m_error);
- a *= b;
- EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
- UINT64_C(8081008345983448486)), a.m_num);
- EXPECT_EQ (100000000000000000, a.m_den);
- EXPECT_EQ (GNC_ERROR_OK, a.m_error);
-
+ EXPECT_NO_THROW({
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a * b;
+ EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
+ UINT64_C(8081008345983448486)), c.m_num);
+ EXPECT_EQ (100000000000000000, c.m_den);
+ a *= b;
+ EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
+ UINT64_C(8081008345983448486)), a.m_num);
+ EXPECT_EQ (100000000000000000, a.m_den);
+ });
}
TEST(gncrational_operators, test_division)
{
- GncRational a(123456789987654321, 1000000000);
- GncRational b(65432198765432198, 100000000);
- GncRational c = a / b;
- EXPECT_EQ (GncInt128(UINT64_C(669260), UINT64_C(11059994577585475840)),
- c.m_num);
- EXPECT_EQ (GncInt128(UINT64_C(3547086), UINT64_C(11115994079396609024)),
- c.m_den);
- EXPECT_EQ (GNC_ERROR_OK, c.m_error);
-
- a /= b;
- EXPECT_EQ (GncInt128(UINT64_C(669260), UINT64_C(11059994577585475840)),
- a.m_num);
- EXPECT_EQ (GncInt128(UINT64_C(3547086), UINT64_C(11115994079396609024)),
- a.m_den);
- EXPECT_EQ (GNC_ERROR_OK, c.m_error);
-
+ EXPECT_NO_THROW({
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a / b;
+ EXPECT_EQ (GncInt128(UINT64_C(669260),
+ UINT64_C(11059994577585475840)), c.m_num);
+ EXPECT_EQ (GncInt128(UINT64_C(3547086),
+ UINT64_C(11115994079396609024)), c.m_den);
+ a /= b;
+ EXPECT_EQ (GncInt128(UINT64_C(669260),
+ UINT64_C(11059994577585475840)), a.m_num);
+ EXPECT_EQ (GncInt128(UINT64_C(3547086),
+ UINT64_C(11115994079396609024)), a.m_den);
+ });
}
TEST(gncrational_functions, test_round_to_numeric)
commit ff7e6a37d531f05716e1e1214b84ea7cbe49de60
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 13:29:48 2017 -0800
Reimplement gnc_numeric in terms of GncNumeric instead of GncRational.
Except when how has DenomType::exact; that triggers direct use of GncRational
and direct rounding to the specified denominator.
diff --git a/src/app-utils/gnc-ui-util.c b/src/app-utils/gnc-ui-util.c
index eb0db3d..e09bdb7 100644
--- a/src/app-utils/gnc-ui-util.c
+++ b/src/app-utils/gnc-ui-util.c
@@ -1378,7 +1378,8 @@ PrintAmountInternal(char *buf, gnc_numeric val, const GNCPrintAmountInfo *info)
{
rounding.num = 5; /* Limit the denom to 10^13 ~= 2^44, leaving max at ~524288 */
rounding.denom = pow(10, max_dp + 1);
- val = gnc_numeric_add(val, rounding, val.denom, GNC_HOW_RND_TRUNC);
+ val = gnc_numeric_add(val, rounding, val.denom,
+ GNC_HOW_DENOM_EXACT | GNC_HOW_RND_TRUNC);
if (gnc_numeric_check(val))
{
diff --git a/src/app-utils/test/test-print-parse-amount.cpp b/src/app-utils/test/test-print-parse-amount.cpp
index 728cbcf..6d4e7e1 100644
--- a/src/app-utils/test/test-print-parse-amount.cpp
+++ b/src/app-utils/test/test-print-parse-amount.cpp
@@ -39,22 +39,12 @@ test_num_print_info (gnc_numeric n, GNCPrintAmountInfo print_info, int line)
const char *s;
gboolean ok, print_ok;
- auto msg = "[PrintAmountInternal()] Bad numeric from rounding: GNC_ERROR_OVERFLOW.";
- auto log_domain = "gnc.gui";
- auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING);
- auto check = test_error_struct_new (log_domain, loglevel, msg);
-
- /* Throws overflows during rounding step in xaccPrintAmount when the "fraction" is high. See bug 665707. */
- auto hdlr = g_log_set_handler (log_domain, loglevel,
- (GLogFunc)test_checked_handler, &check);
s = xaccPrintAmount (n, print_info);
print_ok = (s && s[0] != '\0');
if (!print_ok)
return;
ok = xaccParseAmount (s, print_info.monetary, &n_parsed, NULL);
- g_log_remove_handler (log_domain, hdlr);
-
do_test_args (ok, "parsing failure", __FILE__, __LINE__,
"num: %s, string %s (line %d)", gnc_numeric_to_string (n), s, line);
@@ -64,8 +54,6 @@ test_num_print_info (gnc_numeric n, GNCPrintAmountInfo print_info, int line)
"start: %s, string %s, finish: %s (line %d)",
gnc_numeric_to_string (n), s,
gnc_numeric_to_string (n_parsed), line);
- test_error_struct_free (check);
-
}
static void
@@ -90,7 +78,8 @@ test_num (gnc_numeric n)
print_info.force_fit = 0;
print_info.round = 0;
- n1 = gnc_numeric_convert (n, fraction, GNC_HOW_RND_ROUND_HALF_UP);
+ n1 = gnc_numeric_convert (n, fraction, GNC_HOW_DENOM_EXACT |
+ GNC_HOW_RND_ROUND_HALF_UP);
if (gnc_numeric_check(n1))
{
do_test_args((gnc_numeric_check(n1) == GNC_ERROR_OVERFLOW),
@@ -133,6 +122,20 @@ static void
run_tests (void)
{
int i;
+ auto msg1 = "[gnc_numeric_mul()] Cannot be represented as a GncNumeric. Its integer value is too large.\n";
+ auto msg2 = "[gnc_numeric_mul()] Overflow during rounding.";
+ auto msg3 = "[convert()] Value too large to represent as int64_t";
+ const char* log_domain = "qof";
+ auto loglevel = static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING);
+ auto check1 = test_error_struct_new (log_domain, loglevel, msg1);
+ auto check2 = test_error_struct_new (log_domain, loglevel, msg2);
+ auto check3 = test_error_struct_new (log_domain, loglevel, msg3);
+ test_add_error(check1);
+ test_add_error(check2);
+ test_add_error(check3);
+ /* Throws overflows during rounding step in xaccPrintAmount when the "fraction" is high. See bug 665707. */
+ auto hdlr = g_log_set_handler (log_domain, loglevel,
+ (GLogFunc)test_list_handler, nullptr);
for (i = 0; i < 50; i++)
{
@@ -147,10 +150,13 @@ run_tests (void)
IS_VALID_NUM(n1, n);
test_num (n);
- n1 = gnc_numeric_mul (n, n, n.denom, GNC_HOW_RND_ROUND_HALF_UP);
+ n1 = gnc_numeric_mul (n, n, n.denom,
+ GNC_HOW_DENOM_EXACT | GNC_HOW_RND_ROUND_HALF_UP);
IS_VALID_NUM(n1, n);
test_num (n);
}
+ g_log_remove_handler (log_domain, hdlr);
+ test_clear_error_list();
}
int
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 15c1e8b..34345d7 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -708,9 +708,9 @@ gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
return -1;
}
- GncRational an (a), bn (b);
+ GncNumeric an (a), bn (b);
- return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
+ return an.cmp(bn);
}
@@ -768,6 +768,17 @@ gnc_numeric_same(gnc_numeric a, gnc_numeric b, gint64 denom,
return(gnc_numeric_equal(aconv, bconv));
}
+static int64_t
+denom_lcd(gnc_numeric a, gnc_numeric b, int64_t denom, int how)
+{
+ if (denom == GNC_DENOM_AUTO &&
+ (how & GNC_NUMERIC_DENOM_MASK) == GNC_HOW_DENOM_LCD)
+ {
+ GncInt128 ad(a.denom), bd(b.denom);
+ denom = static_cast<int64_t>(ad.lcm(bd));
+ }
+ return denom;
+}
/* *******************************************************************
* gnc_numeric_add
@@ -781,18 +792,39 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
-
- GncRational an (a), bn (b);
- GncDenom new_denom (an, bn, denom, how);
- if (new_denom.m_error)
- return gnc_numeric_error (new_denom.m_error);
-
+ denom = denom_lcd(a, b, denom, how);
try
{
- return static_cast<gnc_numeric>(an.add(bn, new_denom));
+ if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
+ {
+ GncNumeric an (a), bn (b);
+ auto sum = an + bn;
+ return convert(sum, denom, how);
+ }
+ GncRational ar(a), br(b);
+ auto sum = ar + br;
+ sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (sum.m_error)
+ return gnc_numeric_error(sum.m_error);
+ if (sum.m_num.isBig() || sum.m_den.isBig() ||
+ sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
+ sum.m_num.isNan() || sum.m_den.isNan())
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ return GncNumeric(sum);
}
catch (const std::overflow_error& err)
{
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err)
+ {
+ PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
}
@@ -810,10 +842,41 @@ gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
-
- nb = b;
- nb.num = -nb.num;
- return gnc_numeric_add (a, nb, denom, how);
+ denom = denom_lcd(a, b, denom, how);
+ try
+ {
+ if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
+ {
+ GncNumeric an (a), bn (b);
+ auto sum = an - bn;
+ return convert(sum, denom, how);
+ }
+ GncRational ar(a), br(b);
+ auto sum = ar - br;
+ sum.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (sum.m_error)
+ return gnc_numeric_error(sum.m_error);
+ if (sum.m_num.isBig() || sum.m_den.isBig() ||
+ sum.m_num.isOverflow() || sum.m_den.isOverflow() ||
+ sum.m_num.isNan() || sum.m_den.isNan())
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ return GncNumeric(sum);
+ }
+ catch (const std::overflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
}
/* *******************************************************************
@@ -828,20 +891,41 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
-
- GncRational an (a), bn (b);
- GncDenom new_denom (an, bn, denom, how);
- if (new_denom.m_error)
- return gnc_numeric_error (new_denom.m_error);
+ denom = denom_lcd(a, b, denom, how);
try
{
- return static_cast<gnc_numeric>(an.mul(bn, new_denom));
- }
+ if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
+ {
+ GncNumeric an (a), bn (b);
+ auto prod = an * bn;
+ return convert(prod, denom, how);
+ }
+ GncRational ar(a), br(b);
+ auto prod = ar * br;
+ prod.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (prod.m_error)
+ return gnc_numeric_error(prod.m_error);
+ if (prod.m_num.isBig() || prod.m_den.isBig() ||
+ prod.m_num.isOverflow() || prod.m_den.isOverflow() ||
+ prod.m_num.isNan() || prod.m_den.isNan())
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ return GncNumeric(prod);
+ }
catch (const std::overflow_error& err)
{
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err)
+ {
+ PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
-
}
@@ -857,17 +941,39 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
{
return gnc_numeric_error(GNC_ERROR_ARG);
}
-
- GncRational an (a), bn (b);
- GncDenom new_denom (an, bn, denom, how);
- if (new_denom.m_error)
- return gnc_numeric_error (new_denom.m_error);
+ denom = denom_lcd(a, b, denom, how);
try
{
- return static_cast<gnc_numeric>(an.div(bn, new_denom));
+ if ((how & GNC_NUMERIC_DENOM_MASK) != GNC_HOW_DENOM_EXACT)
+ {
+ GncNumeric an (a), bn (b);
+ auto quot = an / bn;
+ return convert(quot, denom, how);
+ }
+ GncRational ar(a), br(b);
+ auto quot = ar / br;
+ quot.round(denom, static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK));
+ if (quot.m_error)
+ return gnc_numeric_error(quot.m_error);
+ if (quot.m_num.isBig() || quot.m_den.isBig() ||
+ quot.m_num.isOverflow() || quot.m_den.isOverflow() ||
+ quot.m_num.isNan() || quot.m_den.isNan())
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ return GncNumeric(quot);
}
catch (const std::overflow_error& err)
{
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err) //Divide by zero
+ {
+ PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
}
@@ -910,18 +1016,7 @@ gnc_numeric_abs(gnc_numeric a)
gnc_numeric
gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
- GncRational a (in), b (gnc_numeric_zero());
- GncDenom d (a, b, denom, how);
- try
- {
- d.reduce(a);
- a.round (d.get(), d.m_round);
- return static_cast<gnc_numeric>(a);
- }
- catch (const std::overflow_error& err)
- {
- return gnc_numeric_error(GNC_ERROR_OVERFLOW);
- }
+ return convert(GncNumeric(in), denom, how);
}
@@ -941,18 +1036,27 @@ gnc_numeric_reduce(gnc_numeric in)
if (in.denom < 0) /* Negative denoms multiply num, can't be reduced. */
return in;
- GncRational a (in), b (gnc_numeric_zero());
- GncDenom d (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
try
{
- d.reduce(a);
- a.round (d.get(), d.m_round);
- return static_cast<gnc_numeric>(a);
+ GncNumeric an (in);
+ return static_cast<gnc_numeric>(an.reduce());
}
catch (const std::overflow_error& err)
{
+ PWARN("%s", err.what());
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
}
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+
}
@@ -968,87 +1072,48 @@ gnc_numeric_reduce(gnc_numeric in)
gboolean
gnc_numeric_to_decimal(gnc_numeric *a, guint8 *max_decimal_places)
{
- guint8 decimal_places = 0;
- gnc_numeric converted_val;
- gint64 fraction;
-
- g_return_val_if_fail(a, FALSE);
-
- if (gnc_numeric_check(*a) != GNC_ERROR_OK)
- return FALSE;
-
- converted_val = *a;
- if (converted_val.denom <= 0)
+ int max_places = max_decimal_places == NULL ? 17 : *max_decimal_places;
+ try
{
- converted_val = gnc_numeric_convert(converted_val, 1, GNC_HOW_DENOM_EXACT);
- if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
- return FALSE;
- *a = converted_val;
- if (max_decimal_places)
- *max_decimal_places = decimal_places;
+ GncNumeric an (*a);
+ auto bn = an.to_decimal(max_places);
+ *a = static_cast<gnc_numeric>(bn);
return TRUE;
}
-
- /* Zero is easily converted. */
- if (converted_val.num == 0)
- converted_val.denom = 1;
-
- fraction = converted_val.denom;
- while (fraction != 1)
+ catch (const std::exception& err)
{
- switch (fraction % 10)
- {
- case 0:
- fraction = fraction / 10;
- break;
-
- case 5:
- converted_val = gnc_numeric_mul(converted_val,
- gnc_numeric_create(2, 2),
- GNC_DENOM_AUTO,
- GNC_HOW_DENOM_EXACT |
- GNC_HOW_RND_NEVER);
- if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
- return FALSE;
- fraction = fraction / 5;
- break;
-
- case 2:
- case 4:
- case 6:
- case 8:
- converted_val = gnc_numeric_mul(converted_val,
- gnc_numeric_create(5, 5),
- GNC_DENOM_AUTO,
- GNC_HOW_DENOM_EXACT |
- GNC_HOW_RND_NEVER);
- if (gnc_numeric_check(converted_val) != GNC_ERROR_OK)
- return FALSE;
- fraction = fraction / 2;
- break;
-
- default:
- return FALSE;
- }
-
- decimal_places += 1;
+ PWARN("%s", err.what());
+ return FALSE;
}
-
- if (max_decimal_places)
- *max_decimal_places = decimal_places;
-
- *a = converted_val;
-
- return TRUE;
}
+
gnc_numeric
gnc_numeric_invert(gnc_numeric num)
{
if (num.num == 0)
return gnc_numeric_zero();
- return static_cast<gnc_numeric>(GncRational(num).inv());
+ try
+ {
+ return static_cast<gnc_numeric>(GncNumeric(num).inv());
+ }
+ catch (const std::overflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
}
+
/* *******************************************************************
* double_to_gnc_numeric
********************************************************************/
@@ -1059,72 +1124,26 @@ gnc_numeric_invert(gnc_numeric num)
gnc_numeric
double_to_gnc_numeric(double in, gint64 denom, gint how)
{
- gnc_numeric out;
- gint64 int_part = 0;
- double frac_part;
- gint64 frac_int = 0;
- double logval;
- double sigfigs;
-
- if (isnan (in) || fabs (in) > 1e18)
- return gnc_numeric_error (GNC_ERROR_OVERFLOW);
-
- if ((denom == GNC_DENOM_AUTO) && (how & GNC_HOW_DENOM_SIGFIG))
+ try
{
- if (fabs(in) < 10e-20)
- {
- logval = 0;
- }
- else
- {
- logval = log10(fabs(in));
- logval = ((logval > 0.0) ?
- (floor(logval) + 1.0) : (ceil(logval)));
- }
- sigfigs = GNC_HOW_GET_SIGFIGS(how);
- if ((denom = powten (sigfigs - logval)) == POWTEN_OVERFLOW)
- return gnc_numeric_error(GNC_ERROR_OVERFLOW);
-
- how = how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
+ GncNumeric an(in);
+ return convert(an, denom, how);
}
-
- int_part = (gint64)(floor(fabs(in)));
- frac_part = in - (double)int_part;
-
- int_part = int_part * denom;
- frac_part = frac_part * (double)denom;
-
- switch (how & GNC_NUMERIC_RND_MASK)
+ catch (const std::overflow_error& err)
{
- case GNC_HOW_RND_FLOOR:
- frac_int = (gint64)floor(frac_part);
- break;
-
- case GNC_HOW_RND_CEIL:
- frac_int = (gint64)ceil(frac_part);
- break;
-
- case GNC_HOW_RND_TRUNC:
- frac_int = (gint64)frac_part;
- break;
-
- case GNC_HOW_RND_ROUND:
- case GNC_HOW_RND_ROUND_HALF_UP:
- frac_int = (gint64)rint(frac_part);
- break;
-
- case GNC_HOW_RND_NEVER:
- frac_int = (gint64)floor(frac_part);
- if (frac_part != (double) frac_int)
- {
- /* signal an error */
- }
- break;
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::invalid_argument& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+ catch (const std::underflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
}
-
- out.num = int_part + frac_int;
- out.denom = denom;
- return out;
}
/* *******************************************************************
@@ -1191,20 +1210,17 @@ gnc_num_dbg_to_string(gnc_numeric n)
gboolean
string_to_gnc_numeric(const gchar* str, gnc_numeric *n)
{
- gint64 tmpnum;
- gint64 tmpdenom;
-
- if (!str) return FALSE;
-
- tmpnum = g_ascii_strtoll (str, NULL, 0);
- str = strchr (str, '/');
- if (!str) return FALSE;
- str ++;
- tmpdenom = g_ascii_strtoll (str, NULL, 0);
-
- n->num = tmpnum;
- n->denom = tmpdenom;
- return TRUE;
+ try
+ {
+ GncNumeric an(str);
+ *n = static_cast<gnc_numeric>(an);
+ return TRUE;
+ }
+ catch (const std::exception& err)
+ {
+ PWARN("%s", err.what());
+ return FALSE;
+ }
}
/* *******************************************************************
diff --git a/src/libqof/qof/gnc-numeric.h b/src/libqof/qof/gnc-numeric.h
index c67cba7..7840736 100644
--- a/src/libqof/qof/gnc-numeric.h
+++ b/src/libqof/qof/gnc-numeric.h
@@ -245,9 +245,6 @@ typedef enum
*/
#define GNC_DENOM_AUTO 0
-/** Use the value 1/n as the denominator of the output value. */
-#define GNC_DENOM_RECIPROCAL( a ) (- ( a ))
-
/** @} */
/** @name Constructors
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index c00cfab..95fc33d 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -47,7 +47,7 @@ GncRational::GncRational (gnc_numeric n) noexcept :
GncRational::operator gnc_numeric () const noexcept
{
- if (m_num.isOverflow() || m_num.isNan() ||
+ if (m_num.isOverflow() || m_num.isNan() ||
m_den.isOverflow() || m_den.isNan())
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
if (m_error != GNC_ERROR_OK)
@@ -73,11 +73,14 @@ GncRational::operator-() const noexcept
GncRational&
GncRational::inv () noexcept
{
+ if (m_den < 0)
+ {
+ m_num *= -m_den;
+ m_den = 1;
+ }
std::swap(m_num, m_den);
- GncRational b {1, 1};
- GncDenom d {*this, b, INT64_C(0), GNC_HOW_RND_NEVER };
- d.reduce(*this);
+ reduce();
return *this;
}
@@ -185,39 +188,6 @@ GncRational::operator/=(GncRational b)
*this = std::move(new_val);
}
-GncRational&
-GncRational::mul (const GncRational& b, GncDenom& d)
-{
- *this *= b;
- d.reduce(*this);
- round (d.get(), d.m_round);
- return *this;
-}
-
-GncRational&
-GncRational::div (GncRational b, GncDenom& d)
-{
- *this /= b;
- d.reduce(*this);
- round (d.get(), d.m_round);
- return *this;
-}
-
-GncRational&
-GncRational::add (const GncRational& b, GncDenom& d)
-{
- *this += b;
- d.reduce(*this);
- round (d.get(), d.m_round);
- return *this;
-}
-
-GncRational&
-GncRational::sub (const GncRational& b, GncDenom& d)
-{
- return add(-b, d);
-}
-
void
GncRational::round (GncInt128 new_den, RoundType rtype)
{
@@ -263,9 +233,9 @@ GncRational::round (GncInt128 new_den, RoundType rtype)
GncInt128 gcd = new_num.gcd(new_den);
if (!(gcd.isNan() || gcd.isOverflow()))
{
- new_num /= gcd;
- new_den /= gcd;
- remainder /= gcd;
+ new_num /= gcd;
+ new_den /= gcd;
+ remainder /= gcd;
}
/* if that didn't work, shift both num and den down until neither is "big", then
@@ -370,10 +340,7 @@ GncRational::round_to_numeric() const
}
GncRational new_rational(*this);
GncRational scratch(1, 1);
- auto divisor = static_cast<int64_t>(m_den / (m_num.abs() >> 62));
- GncDenom gnc_denom(new_rational, scratch, divisor,
- GNC_HOW_RND_ROUND_HALF_DOWN);
- new_rational.round(gnc_denom.get(), gnc_denom.m_round);
+ new_rational.round(m_den / (m_num.abs() >> 62), RoundType::half_down);
return new_rational;
}
auto quot(m_den / m_num);
@@ -391,88 +358,6 @@ GncRational::round_to_numeric() const
}
GncRational new_rational(*this);
GncRational scratch(1, 1);
- auto int_div = static_cast<int64_t>(m_den / divisor);
- GncDenom gnc_denom(new_rational, scratch, int_div,
- GNC_HOW_RND_ROUND_HALF_DOWN);
- new_rational.round(gnc_denom.get(), gnc_denom.m_round);
+ new_rational.round(m_den / divisor, RoundType::half_down);
return new_rational;
}
-
-GncDenom::GncDenom (GncRational& a, GncRational& b,
- int64_t spec, unsigned int how) noexcept :
- m_value (spec),
- m_round (static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK)),
- m_type (static_cast<DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
- m_auto (spec == GNC_DENOM_AUTO),
- m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8),
- m_error (GNC_ERROR_OK)
-
-{
-
- if (!m_auto)
- return;
- switch (m_type)
- {
- case DenomType::fixed:
- if (a.m_den == b.m_den)
- {
- m_value = a.m_den;
- }
- else if (b.m_num == 0)
- {
- m_value = a.m_den;
- b.m_den = a.m_den;
- }
- else if (a.m_num == 0)
- {
- m_value = b.m_den;
- a.m_den = b.m_den;
- }
- else
- {
- m_error = GNC_ERROR_DENOM_DIFF;
- }
- m_auto = false;
- break;
-
- case DenomType::lcd:
- m_value = a.m_den.lcm(b.m_den);
- m_auto = false;
- break;
- default:
- break;
-
- }
-}
-
-void
-GncDenom::reduce (const GncRational& a) noexcept
-{
- if (!m_auto)
- return;
- switch (m_type)
- {
- default:
- break;
- case DenomType::reduce:
- m_value = a.m_den / a.m_num.gcd(a.m_den);
- break;
-
- case DenomType::sigfigs:
- GncInt128 val {};
- if (a.m_num.abs() > a.m_den)
- val = a.m_num.abs() / a.m_den;
- else
- val = a.m_den / a.m_num.abs();
- unsigned int digits {};
- while (val >= 10)
- {
- ++digits;
- val /= 10;
- }
- m_value = (a.m_num.abs() > a.m_den ? powten (m_sigfigs - digits - 1) :
- powten (m_sigfigs + digits));
- m_auto = false;
- break;
- }
-}
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 0ae3fee..415035f 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -26,7 +26,7 @@
#include "gnc-numeric.h"
#include "gnc-int128.hpp"
-struct GncDenom;
+class GncNumeric;
enum class RoundType;
enum class DenomType;
@@ -68,13 +68,6 @@ public:
/** Round/convert this to the denominator provided by d, according to d's
* m_round value.
*/
-/* These are mutators; in other words, they implement the equivalent of
- * operators *=, /=, +=, and -=. They return a reference to this for chaining.
- */
- GncRational& mul(const GncRational& b, GncDenom& d);
- GncRational& div(GncRational b, GncDenom& d);
- GncRational& add(const GncRational& b, GncDenom& d);
- GncRational& sub(const GncRational& b, GncDenom& d);
void round (GncInt128 new_den, RoundType rtype);
void operator+=(GncRational b);
void operator-=(GncRational b);
@@ -93,19 +86,4 @@ GncRational operator-(GncRational a, GncRational b);
GncRational operator*(GncRational a, GncRational b);
GncRational operator/(GncRational a, GncRational b);
-
-/** Encapsulates the rounding specifications computations. */
-struct GncDenom
-{
- GncDenom (GncRational& a, GncRational& b, int64_t spec, unsigned int how) noexcept;
- void reduce (const GncRational& a) noexcept;
- GncInt128 get () const noexcept { return m_value; }
-
- GncInt128 m_value;
- RoundType m_round;
- DenomType m_type;
- bool m_auto;
- unsigned int m_sigfigs;
- GNCNumericErrorCode m_error;
-};
#endif //__GNC_RATIONAL_HPP__
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index ef4d350..596157b 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -134,9 +134,15 @@ test_gnc_rational_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
+ $(top_srcdir)/${MODULEPATH}/qoflog.cpp \
gtest-gnc-rational.cpp
test_gnc_rational_CPPFLAGS = \
+ -I${top_srcdir}/src \
+ -I${top_srcdir}/src/libqof/qof \
-I${GTEST_HEADERS} \
${GLIB_CFLAGS}
@@ -155,8 +161,14 @@ test_gnc_numeric_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
$(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-datetime.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-timezone.cpp \
+ $(top_srcdir)/$(MODULEPATH)/gnc-date.cpp \
+ $(top_srcdir)/${MODULEPATH}/qoflog.cpp \
gtest-gnc-numeric.cpp
test_gnc_numeric_CPPFLAGS = \
+ -I${top_srcdir}/src \
+ -I${top_srcdir}/src/libqof/qof \
-I${GTEST_HEADERS} \
${GLIB_CFLAGS}
diff --git a/src/libqof/qof/test/gtest-gnc-int128.cpp b/src/libqof/qof/test/gtest-gnc-int128.cpp
index edd659d..30d6191 100644
--- a/src/libqof/qof/test/gtest-gnc-int128.cpp
+++ b/src/libqof/qof/test/gtest-gnc-int128.cpp
@@ -419,6 +419,10 @@ TEST(qofint128_functions, divide)
EXPECT_EQ (zero, q);
EXPECT_EQ (big, r);
+ big.div (big - 1, q, r);
+ EXPECT_EQ(one, q);
+ EXPECT_EQ(one, r);
+
EXPECT_EQ (big, big %= bigger);
EXPECT_EQ (two, bigger /= big);
}
commit c3d22c429f1febdd1892f0b45d26606b1ea10ea9
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 13:26:41 2017 -0800
Add GncRational test for GncRational::round_to_numeric().
Needed GncNumeric to be defined.
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 9179d72..78a0edf 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -22,7 +22,9 @@
*******************************************************************/
#include <gtest/gtest.h>
+#include <random>
#include "../gnc-rational.hpp"
+#include "../gnc-numeric.hpp" //for RoundType
TEST(gncrational_constructors, test_default_constructor)
{
@@ -117,6 +119,7 @@ TEST(gncrational_operators, test_multiplication)
UINT64_C(8081008345983448486)), a.m_num);
EXPECT_EQ (100000000000000000, a.m_den);
EXPECT_EQ (GNC_ERROR_OK, a.m_error);
+
}
TEST(gncrational_operators, test_division)
@@ -138,3 +141,28 @@ TEST(gncrational_operators, test_division)
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
}
+
+TEST(gncrational_functions, test_round_to_numeric)
+{
+ std::default_random_engine dre;
+ std::uniform_int_distribution<int64_t> di{INT64_C(0x10000000000000),
+ INT64_C(0x7fffffffffffff)};
+ static const int reps{25};
+ for (auto i = 0; i < reps; ++i)
+ {
+ GncRational a(di(dre), di(dre));
+ GncRational b(di(dre), 100);
+ auto c = a * b;
+ auto expected = c;
+ expected.round(100, RoundType::bankers);
+ auto rounded = c.round_to_numeric();
+ rounded.round(100, RoundType::bankers);
+ EXPECT_EQ(0, expected.m_num - rounded.m_num);
+ EXPECT_FALSE(rounded.m_num.isBig());
+ EXPECT_FALSE(rounded.m_den.isBig());
+ EXPECT_FALSE(rounded.m_num.isNan());
+ EXPECT_FALSE(rounded.m_den.isNan());
+ EXPECT_FALSE(rounded.m_num.isOverflow());
+ EXPECT_FALSE(rounded.m_den.isOverflow());
+ }
+}
commit 4a46ae3ddffd0e70a800aa925d6d3eaccbdb1837
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 13:25:23 2017 -0800
Fix GncInt128 tests for older compilers.
diff --git a/src/libqof/qof/test/gtest-gnc-int128.cpp b/src/libqof/qof/test/gtest-gnc-int128.cpp
index 02b7476..edd659d 100644
--- a/src/libqof/qof/test/gtest-gnc-int128.cpp
+++ b/src/libqof/qof/test/gtest-gnc-int128.cpp
@@ -154,33 +154,33 @@ TEST(qofint128_functions, test_int_functions)
EXPECT_EQ (static_cast<uint64_t>(arg), static_cast<uint64_t>(value1));
GncInt128 value2 (UINT64_C(0), uarg);
- EXPECT_THROW (static_cast<int64_t>(value2), std::overflow_error);
+ EXPECT_THROW (auto v = static_cast<int64_t>(value2), std::overflow_error);
EXPECT_EQ (uarg, static_cast<uint64_t>(value2));
GncInt128 value3 (UINT64_C(0), uarg, GncInt128::neg);
- EXPECT_THROW (static_cast<int64_t>(value3), std::underflow_error);
- EXPECT_THROW (static_cast<uint64_t>(value3), std::underflow_error);
+ EXPECT_THROW (auto v = static_cast<int64_t>(value3), std::underflow_error);
+ EXPECT_THROW (auto v = static_cast<uint64_t>(value3), std::underflow_error);
GncInt128 value4 (UINT64_C(0), uarg, GncInt128::overflow);
- EXPECT_THROW (static_cast<int64_t>(value4), std::overflow_error);
- EXPECT_THROW (static_cast<uint64_t>(value4), std::overflow_error);
+ EXPECT_THROW (auto v = static_cast<int64_t>(value4), std::overflow_error);
+ EXPECT_THROW (auto v = static_cast<uint64_t>(value4), std::overflow_error);
GncInt128 value5 (UINT64_C(0), uarg, GncInt128::NaN);
- EXPECT_THROW (static_cast<int64_t>(value5), std::overflow_error);
- EXPECT_THROW (static_cast<uint64_t>(value5), std::overflow_error);
+ EXPECT_THROW (auto v = static_cast<int64_t>(value5), std::overflow_error);
+ EXPECT_THROW (auto v = static_cast<uint64_t>(value5), std::overflow_error);
GncInt128 value6 (INT64_C(1), arg);
- EXPECT_THROW (static_cast<int64_t>(value6), std::overflow_error);
+ EXPECT_THROW (auto v = static_cast<int64_t>(value6), std::overflow_error);
EXPECT_EQ (arg + (UINT64_C(0x1) << 63), static_cast<uint64_t>(value6));
GncInt128 value7 (INT64_C(-1), arg);
EXPECT_EQ (-static_cast<int64_t>((UINT64_C(0x1) << 63) - arg),
static_cast<int64_t>(value7));
- EXPECT_THROW (static_cast<uint64_t>(value7), std::underflow_error);
+ EXPECT_THROW (auto v = static_cast<uint64_t>(value7), std::underflow_error);
GncInt128 value8 (INT64_C(0), narg);
EXPECT_EQ (narg, static_cast<int64_t>(value8));
- EXPECT_THROW (static_cast<uint64_t>(value8), std::underflow_error);
+ EXPECT_THROW (auto v = static_cast<uint64_t>(value8), std::underflow_error);
GncInt128 value9 (INT64_C(1), narg);
EXPECT_EQ (static_cast<int64_t>((UINT64_C(0x1) << 63) + narg),
@@ -188,8 +188,9 @@ TEST(qofint128_functions, test_int_functions)
EXPECT_EQ ((UINT64_C(0x1) << 63) + narg, static_cast<uint64_t>(value9));
GncInt128 value10 (INT64_C(-2), arg);
- EXPECT_THROW (static_cast<int64_t>(value10), std::underflow_error);
- EXPECT_THROW (static_cast<uint64_t>(value10), std::underflow_error);
+ EXPECT_THROW (auto v = static_cast<int64_t>(value10), std::underflow_error);
+ EXPECT_THROW (auto v = static_cast<uint64_t>(value10),
+ std::underflow_error);
}
commit a54edf1a5e04e7d1cc871e04da5c767020b3144a
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 13:23:00 2017 -0800
Add GncRational(GncNumeric) constructor.
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index e78ed6e..c00cfab 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -24,6 +24,17 @@
#include "gnc-rational.hpp"
#include "gnc-numeric.hpp"
+
+GncRational::GncRational(GncNumeric n) noexcept :
+ m_num(n.num()), m_den(n.denom()), m_error(GNC_ERROR_OK)
+{
+ if (m_den.isNeg())
+ {
+ m_num *= -m_den;
+ m_den = 1;
+ }
+}
+
GncRational::GncRational (gnc_numeric n) noexcept :
m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK}
{
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index b6667e6..0ae3fee 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -39,10 +39,14 @@ class GncRational
public:
GncRational() : m_num(0), m_den(1), m_error(GNC_ERROR_OK) {}
GncRational (gnc_numeric n) noexcept;
+ GncRational(GncNumeric n) noexcept;
GncRational (GncInt128 num, GncInt128 den,
GNCNumericErrorCode err=GNC_ERROR_OK) noexcept
: m_num(num), m_den(den), m_error(err) {}
-
+ GncRational(const GncRational& rhs) = default;
+ GncRational(GncRational&& rhs) = default;
+ GncRational& operator=(const GncRational& rhs) = default;
+ GncRational& operator=(GncRational&& rhs) = default;
/** Conversion operator; use static_cast<gnc_numeric>(foo). */
operator gnc_numeric() const noexcept;
/** Make a new GncRational with the opposite sign. */
commit 82fe06e390ee5aa16ff2d46e5fed4865548780cc
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 13:17:05 2017 -0800
Extract new class GncNumeric.
Similar to GncRational, except that itâs based on int64_t instead of
GncInt128 and throws instead of using a status byte.
Most calculations are performed using GncRational, the result is then
rounded (RoundType::half_down) to fit. GncRational should be used in
circumstances where the automatic rounding is undesirable.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 05eb90c..15c1e8b 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -36,12 +36,14 @@ extern "C"
}
#include <stdint.h>
+#include <regex>
+#include <sstream>
+#include <cstdlib>
-#include "gnc-numeric.h"
+#include "gnc-numeric.hpp"
#include "gnc-rational.hpp"
-using GncNumeric = GncRational;
-
+static QofLogModule log_module = "qof";
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
10000000, 100000000, 1000000000,
INT64_C(10000000000), INT64_C(100000000000),
@@ -52,14 +54,538 @@ static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
INT64_C(1000000000000000000)};
#define POWTEN_OVERFLOW -5
-static inline gint64
-powten (int exp)
+int64_t
+powten (int64_t exp)
{
if (exp > 18 || exp < -18)
return POWTEN_OVERFLOW;
return exp < 0 ? -pten[-exp] : pten[exp];
}
+GncNumeric::GncNumeric(GncRational rr)
+{
+ if (rr.m_num.isNan() || rr.m_den.isNan())
+ throw std::underflow_error("Operation resulted in NaN.");
+ if (rr.m_num.isOverflow() || rr.m_den.isOverflow())
+ throw std::overflow_error("Operation overflowed a 128-bit int.");
+ if (rr.m_num.isBig() || rr.m_den.isBig())
+ {
+ GncRational reduced(rr.reduce());
+ rr = reduced.round_to_numeric(); // A no-op if it's already small.
+ }
+ m_num = static_cast<int64_t>(rr.m_num);
+ m_den = static_cast<int64_t>(rr.m_den);
+}
+
+GncNumeric::GncNumeric(double d) : m_num(0), m_den(1)
+{
+ if (isnan(d) || fabs(d) > 1e18)
+ {
+ std::ostringstream msg;
+ msg << "Unable to construct a GncNumeric from " << d << ".\n";
+ throw std::invalid_argument(msg.str());
+ }
+ constexpr auto max_denom = INT64_MAX / 10;
+ auto logval = log10(fabs(d));
+ int64_t den;
+ if (logval > 0.0)
+ den = powten(18 - static_cast<int>(floor(logval) + 1.0));
+ else
+ den = powten(17);
+ auto num = static_cast<int64_t>(floor(static_cast<double>(den) * d));
+
+ if (num == 0)
+ return;
+ GncNumeric q(num, den);
+ auto r = q.reduce();
+ m_num = r.num();
+ m_den = r.denom();
+}
+
+GncNumeric::GncNumeric(const std::string& str, bool autoround)
+{
+ static const std::string numer_frag("(-?[0-9]+)");
+ static const std::string denom_frag("([0-9]+)");
+ static const std::string hex_frag("(0x[a-f0-9]+)");
+ static const std::string slash( "[ \\t]*/[ \\t]*");
+ /* The llvm standard C++ library refused to recognize the - in the
+ * numer_frag patter with the default ECMAScript syntax so we use the awk
+ * syntax.
+ */
+ static const std::regex numeral(numer_frag, std::regex::awk);
+ static const std::regex hex(hex_frag, std::regex::awk);
+ static const std::regex numeral_rational(numer_frag + slash + denom_frag,
+ std::regex::awk);
+ static const std::regex hex_rational(hex_frag + slash + hex_frag,
+ std::regex::awk);
+ static const std::regex hex_over_num(hex_frag + slash + denom_frag,
+ std::regex::awk);
+ static const std::regex num_over_hex(numer_frag + slash + hex_frag,
+ std::regex::awk);
+ static const std::regex decimal(numer_frag + "[.,]" + denom_frag,
+ std::regex::awk);
+ std::smatch m;
+/* The order of testing the regexes is from the more restrictve to the less
+ * restrictive, as less-restrictive ones will match patterns that would also
+ * match the more-restrictive and so invoke the wrong construction.
+ */
+ if (str.empty())
+ throw std::invalid_argument("Can't construct a GncNumeric from an empty string.");
+ if (std::regex_search(str, m, hex_rational))
+ {
+ GncNumeric n(stoll(m[1].str(), nullptr, 16),
+ stoll(m[2].str(), nullptr, 16));
+ m_num = n.num();
+ m_den = n.denom();
+ return;
+ }
+ if (std::regex_search(str, m, hex_over_num))
+ {
+ GncNumeric n(stoll(m[1].str(), nullptr, 16),
+ stoll(m[2].str()));
+ m_num = n.num();
+ m_den = n.denom();
+ return;
+ }
+ if (std::regex_search(str, m, num_over_hex))
+ {
+ GncNumeric n(stoll(m[1].str()),
+ stoll(m[2].str(), nullptr, 16));
+ m_num = n.num();
+ m_den = n.denom();
+ return;
+ }
+ if (std::regex_search(str, m, numeral_rational))
+ {
+ GncNumeric n(stoll(m[1].str()), stoll(m[2].str()));
+ m_num = n.num();
+ m_den = n.denom();
+ return;
+ }
+ if (std::regex_search(str, m, decimal))
+ {
+ GncInt128 high(stoll(m[1].str()));
+ GncInt128 low(stoll(m[2].str()));
+ int64_t d = powten(m[2].str().length());
+ GncInt128 n = high * d + (high > 0 ? low : -low);
+ if (!autoround && n.isBig())
+ {
+ std::ostringstream errmsg;
+ errmsg << "Decimal string " << m[1].str() << "." << m[2].str()
+ << "can't be represented in a GncNumeric without rounding.";
+ throw std::overflow_error(errmsg.str());
+ }
+ while (n.isBig() && d > 0)
+ {
+ n >>= 1;
+ d >>= 1;
+ }
+ if (n.isBig()) //Shouldn't happen, of course
+ {
+ std::ostringstream errmsg;
+ errmsg << "Decimal string " << m[1].str() << "." << m[2].str()
+ << " can't be represented in a GncNumeric, even after reducing denom to " << d;
+ throw std::overflow_error(errmsg.str());
+ }
+ GncNumeric gncn(static_cast<int64_t>(n), d);
+ m_num = gncn.num();
+ m_den = gncn.denom();
+ return;
+ }
+ if (std::regex_search(str, m, hex))
+ {
+ GncNumeric n(stoll(m[1].str(), nullptr, 16),INT64_C(1));
+ m_num = n.num();
+ m_den = n.denom();
+ return;
+ }
+ if (std::regex_search(str, m, numeral))
+ {
+ GncNumeric n(stoll(m[1].str()), INT64_C(1));
+ m_num = n.num();
+ m_den = n.denom();
+ return;
+ }
+ std::ostringstream errmsg;
+ errmsg << "String " << str << " contains no recognizable numeric value.";
+ throw std::invalid_argument(errmsg.str());
+}
+
+GncNumeric::operator gnc_numeric() const noexcept
+{
+ return {m_num, m_den};
+}
+
+GncNumeric::operator double() const noexcept
+{
+ return static_cast<double>(m_num) / static_cast<double>(m_den);
+}
+
+GncNumeric
+GncNumeric::operator-() const noexcept
+{
+ GncNumeric b(*this);
+ b.m_num = - b.m_num;
+ return b;
+}
+
+GncNumeric
+GncNumeric::inv() const noexcept
+{
+ if (m_num == 0)
+ return *this;
+ if (m_num < 0)
+ return GncNumeric(-m_den, -m_num);
+ return GncNumeric(m_den, m_num);
+}
+
+GncNumeric
+GncNumeric::abs() const noexcept
+{
+ if (m_num < 0)
+ return -*this;
+ return *this;
+}
+
+GncNumeric
+GncNumeric::reduce() const noexcept
+{
+ return static_cast<GncNumeric>(GncRational(*this).reduce());
+}
+
+GncNumeric::round_param
+GncNumeric::prepare_conversion(int64_t new_denom) const
+{
+ if (new_denom == m_den || new_denom == GNC_DENOM_AUTO)
+ return {m_num, m_den, 0};
+ GncRational conversion(new_denom, m_den);
+ auto red_conv = conversion.reduce();
+ GncInt128 old_num(m_num);
+ auto new_num = old_num * red_conv.m_num;
+ auto rem = new_num % red_conv.m_den;
+ new_num /= red_conv.m_den;
+ if (new_num.isBig())
+ {
+ GncRational rr(new_num, new_denom);
+ GncNumeric nn(rr);
+ rr.round(new_denom, RoundType::truncate);
+ return {static_cast<int64_t>(rr.m_num), new_denom, 0};
+ }
+ return {static_cast<int64_t>(new_num), static_cast<int64_t>(red_conv.m_den),
+ static_cast<int64_t>(rem)};
+}
+
+int64_t
+GncNumeric::sigfigs_denom(unsigned figs) const noexcept
+{
+ int64_t num_abs{std::abs(m_num)};
+ bool not_frac = num_abs > m_den;
+ int64_t val{ not_frac ? num_abs / m_den : m_den / num_abs };
+ unsigned digits{};
+ while (val >= 10)
+ {
+ ++digits;
+ val /= 10;
+ }
+ return not_frac ? powten(figs - digits - 1) : powten(figs + digits);
+}
+
+std::string
+GncNumeric::to_string() const noexcept
+{
+ std::ostringstream out;
+ out << *this;
+ return out.str();
+}
+
+GncNumeric
+GncNumeric::to_decimal(unsigned int max_places) const
+{
+ if (max_places > 17)
+ max_places = 17;
+ bool is_pwr_ten = true;
+ for (int pwr = 1; pwr <= 17 && m_den > powten(pwr); ++pwr)
+ if (m_den % powten(pwr))
+ {
+ is_pwr_ten = false;
+ break;
+ }
+
+ if (m_num == 0 || (is_pwr_ten && m_den < powten(max_places)))
+ return *this; // Nothing to do.
+ if (is_pwr_ten)
+ {
+ /* See if we can reduce m_num to fit in max_places */
+ auto excess = m_den / powten(max_places);
+ if (m_num % excess)
+ {
+ std::ostringstream msg;
+ msg << "GncNumeric " << *this
+ << " could not be represented in " << max_places
+ << " decimal places without rounding.\n";
+ throw std::range_error(msg.str());
+ }
+ return GncNumeric(m_num / excess, powten(max_places));
+ }
+ GncRational rr(*this);
+ rr.round(powten(max_places), RoundType::never);
+ if (rr.m_error)
+ {
+ std::ostringstream msg;
+ msg << "GncNumeric " << *this
+ << " could not be represented as a decimal without rounding.\n";
+ throw std::range_error(msg.str());
+ }
+ /* rr might have gotten reduced a bit too much; if so, put it back: */
+ unsigned int pwr{1};
+ for (; pwr <= max_places && !(rr.m_den % powten(pwr)); ++pwr);
+ if (rr.m_den % powten(pwr))
+ {
+ auto factor(powten(pwr) / rr.m_den);
+ rr.m_num *= factor;
+ rr.m_den *= factor;
+ }
+ while (rr.m_num % 10 == 0)
+ {
+ rr.m_num /= 10;
+ rr.m_den /= 10;
+ }
+ try
+ {
+ /* Construct from the parts to avoid the GncRational constructor's
+ * automatic rounding.
+ */
+ return {static_cast<int64_t>(rr.m_num), static_cast<int64_t>(rr.m_den)};
+ }
+ catch (const std::invalid_argument& err)
+ {
+ std::ostringstream msg;
+ msg << "GncNumeric " << *this
+ << " could not be represented as a decimal without rounding.\n";
+ throw std::range_error(msg.str());
+ }
+ catch (const std::overflow_error& err)
+ {
+ std::ostringstream msg;
+ msg << "GncNumeric " << *this
+ << " overflows when attempting to convert it to decimal.\n";
+ throw std::range_error(msg.str());
+ }
+}
+
+void
+GncNumeric::operator+=(GncNumeric b)
+{
+ *this = *this + b;
+}
+
+void
+GncNumeric::operator-=(GncNumeric b)
+{
+ *this = *this - b;
+}
+
+void
+GncNumeric::operator*=(GncNumeric b)
+{
+ *this = *this * b;
+}
+
+void
+GncNumeric::operator/=(GncNumeric b)
+{
+ *this = *this / b;
+}
+
+int
+GncNumeric::cmp(GncNumeric b)
+{
+ if (m_den == b.denom())
+ {
+ auto b_num = b.num();
+ return m_num < b_num ? -1 : b_num < m_num ? 1 : 0;
+ }
+// GncInt128 a_den(m_den), b_den(b.denom());
+// auto lcm = a_den.gcd(b_den);
+// GncInt128 a_num(m_num * gcd / a_den), b_num(b.num() * gcd / b_den);
+// return a_num < b_num ? -1 : b_num < a_num ? 1 : 0;
+ GncRational an(*this), bn(b);
+ return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
+}
+
+GncNumeric
+operator+(GncNumeric a, GncNumeric b)
+{
+ if (a.num() == 0)
+ return b;
+ if (b.num() == 0)
+ return a;
+ GncRational ar(a), br(b);
+ auto rr = ar + br;
+ return static_cast<GncNumeric>(rr);
+}
+
+GncNumeric
+operator-(GncNumeric a, GncNumeric b)
+{
+ return a + (-b);
+}
+
+GncNumeric
+operator*(GncNumeric a, GncNumeric b)
+{
+ if (a.num() == 0 || b.num() == 0)
+ {
+ GncNumeric retval;
+ return retval;
+ }
+ GncRational ar(a), br(b);
+ auto rr = ar * br;
+ return static_cast<GncNumeric>(rr);
+}
+
+GncNumeric
+operator/(GncNumeric a, GncNumeric b)
+{
+ if (a.num() == 0)
+ {
+ GncNumeric retval;
+ return retval;
+ }
+ if (b.num() == 0)
+ throw std::underflow_error("Attempt to divide by zero.");
+
+ GncRational ar(a), br(b);
+ auto rr = ar / br;
+ return static_cast<GncNumeric>(rr);
+}
+
+int
+cmp(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b);
+}
+
+bool
+operator<(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b) < 0;
+}
+
+bool
+operator>(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b) > 0;
+}
+
+bool
+operator==(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b) == 0;
+}
+
+bool
+operator<=(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b) <= 0;
+}
+
+bool
+operator>=(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b) >= 0;
+}
+
+bool
+operator!=(GncNumeric a, GncNumeric b)
+{
+ return a.cmp(b) != 0;
+}
+
+static gnc_numeric
+convert(GncNumeric num, int64_t new_denom, int how)
+{
+// std::cout << "Converting " << num << ".\n";
+ auto rtype = static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK);
+ unsigned int figs = GNC_HOW_GET_SIGFIGS(how);
+
+ auto dtype = static_cast<DenomType>(how & GNC_NUMERIC_DENOM_MASK);
+ bool sigfigs = dtype == DenomType::sigfigs;
+ if (dtype == DenomType::reduce)
+ num = num.reduce();
+ try
+ {
+ switch (rtype)
+ {
+ case RoundType::floor:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::floor>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::floor>(new_denom));
+ case RoundType::ceiling:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::ceiling>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::ceiling>(new_denom));
+ case RoundType::truncate:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::truncate>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::truncate>(new_denom));
+ case RoundType::promote:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::promote>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::promote>(new_denom));
+ case RoundType::half_down:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::half_down>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::half_down>(new_denom));
+ case RoundType::half_up:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::half_up>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::half_up>(new_denom));
+ case RoundType::bankers:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::bankers>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::bankers>(new_denom));
+ case RoundType::never:
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::never>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::never>(new_denom));
+ default:
+/* round-truncate just returns the numerator unchanged. The old gnc-numeric
+ * convert had no "default" behavior at rounding that had the same result, but
+ * we need to make it explicit here to run the rest of the conversion code.
+ */
+ if (sigfigs)
+ return static_cast<gnc_numeric>(num.convert_sigfigs<RoundType::truncate>(figs));
+ else
+ return static_cast<gnc_numeric>(num.convert<RoundType::truncate>(new_denom));
+
+// return static_cast<gnc_numeric>(num);
+ }
+ }
+ catch (const std::domain_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_REMAINDER);
+ }
+ catch (const std::overflow_error& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
+ catch (const std::exception& err)
+ {
+ PWARN("%s", err.what());
+ return gnc_numeric_error(GNC_ERROR_ARG);
+ }
+}
/* =============================================================== */
/* This function is small, simple, and used everywhere below,
@@ -182,7 +708,7 @@ gnc_numeric_compare(gnc_numeric a, gnc_numeric b)
return -1;
}
- GncNumeric an (a), bn (b);
+ GncRational an (a), bn (b);
return (an.m_num * bn.m_den).cmp(bn.m_num * an.m_den);
}
@@ -256,7 +782,7 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
return gnc_numeric_error(GNC_ERROR_ARG);
}
- GncNumeric an (a), bn (b);
+ GncRational an (a), bn (b);
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
@@ -303,7 +829,7 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
return gnc_numeric_error(GNC_ERROR_ARG);
}
- GncNumeric an (a), bn (b);
+ GncRational an (a), bn (b);
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
@@ -332,7 +858,7 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
return gnc_numeric_error(GNC_ERROR_ARG);
}
- GncNumeric an (a), bn (b);
+ GncRational an (a), bn (b);
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
@@ -384,7 +910,7 @@ gnc_numeric_abs(gnc_numeric a)
gnc_numeric
gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
- GncNumeric a (in), b (gnc_numeric_zero());
+ GncRational a (in), b (gnc_numeric_zero());
GncDenom d (a, b, denom, how);
try
{
@@ -415,7 +941,7 @@ gnc_numeric_reduce(gnc_numeric in)
if (in.denom < 0) /* Negative denoms multiply num, can't be reduced. */
return in;
- GncNumeric a (in), b (gnc_numeric_zero());
+ GncRational a (in), b (gnc_numeric_zero());
GncDenom d (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
try
{
@@ -521,7 +1047,7 @@ gnc_numeric_invert(gnc_numeric num)
{
if (num.num == 0)
return gnc_numeric_zero();
- return static_cast<gnc_numeric>(GncNumeric(num).inv());
+ return static_cast<gnc_numeric>(GncRational(num).inv());
}
/* *******************************************************************
* double_to_gnc_numeric
diff --git a/src/libqof/qof/gnc-numeric.hpp b/src/libqof/qof/gnc-numeric.hpp
new file mode 100644
index 0000000..44db4b2
--- /dev/null
+++ b/src/libqof/qof/gnc-numeric.hpp
@@ -0,0 +1,473 @@
+/********************************************************************
+ * gnc-numeric.hpp - A rational number library for int64 *
+ * Copyright 2017 John Ralls <jralls at ceridwen.us> *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+#ifndef __GNC_NUMERIC_HPP__
+#define __GNC_NUMERIC_HPP__
+
+#include <string>
+#include <iostream>
+
+#include "gnc-numeric.h"
+
+
+enum class RoundType
+{
+ floor = GNC_HOW_RND_FLOOR,
+ ceiling = GNC_HOW_RND_CEIL,
+ truncate = GNC_HOW_RND_TRUNC,
+ promote = GNC_HOW_RND_PROMOTE,
+ half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
+ half_up = GNC_HOW_RND_ROUND_HALF_UP,
+ bankers = GNC_HOW_RND_ROUND,
+ never = GNC_HOW_RND_NEVER,
+};
+
+enum class DenomType
+{
+ den_auto = GNC_DENOM_AUTO,
+ exact = GNC_HOW_DENOM_EXACT,
+ reduce = GNC_HOW_DENOM_REDUCE,
+ lcd = GNC_HOW_DENOM_LCD,
+ fixed = GNC_HOW_DENOM_FIXED,
+ sigfigs = GNC_HOW_DENOM_SIGFIG,
+};
+
+
+template <RoundType rt>
+struct RT2T
+{
+ RoundType value = rt;
+};
+
+/* The following templates implement the rounding policies for the convert and
+ * convert_sigfigs template functions.
+ */
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::never>)
+{
+ if (rem == 0)
+ return num;
+ throw std::domain_error("Rounding required when 'never round' specified.");
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::floor>)
+{
+// std::cout << "Rounding to floor with num " << num << " den " << den
+// << ", and rem " << rem << ".\n";
+ if (rem == 0)
+ return num;
+ if (num < 0)
+ return num + 1;
+ return num;
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::ceiling>)
+{
+ if (rem == 0)
+ return num;
+ if (num > 0)
+ return num + 1;
+ return num;
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::truncate>)
+{
+ return num;
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::promote>)
+{
+ if (rem == 0)
+ return num;
+ return num + (num < 0 ? -1 : 1);
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::half_down>)
+{
+ if (rem == 0)
+ return num;
+ if (rem * 2 > den)
+ return num + (num < 0 ? -1 : 1);
+ return num;
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::half_up>)
+{
+ if (rem == 0)
+ return num;
+ if (rem * 2 >= den)
+ return num + (num < 0 ? -1 : 1);
+ return num;
+}
+
+inline int64_t
+round(int64_t num, int64_t den, int64_t rem, RT2T<RoundType::bankers>)
+{
+ if (rem == 0)
+ return num;
+ if (rem * 2 > den || (rem * 2 == den && num % 2))
+ return num += (num < 0 ? -1 : 1);
+ return num;
+}
+
+class GncRational;
+
+/**
+ * The primary numeric class for representing amounts and values.
+ *
+ * Calculations are generally performed in 128-bit (by converting to
+ * GncRational) and reducing the result. If the result would overflow a 64-bit
+ * representation then the GncNumeric(GncRational&) constructor will call
+ * GncRational::round_to_numeric() to get the value to fit. It will not raise an
+ * exeception, so in the unlikely event that you need an error instead of
+ * rounding, use GncRational directly.
+ *
+ * Errors: Errors are signalled by exceptions as follows:
+ * * A zero denominator will raise a std::invalid_argument.
+ * * Division by zero will raise a std::underflow_error.
+ * * Overflowing 128 bits will raise a std::overflow_error.
+ * * Failure to convert a number as specified by the arguments to convert() will
+ * raise a std::domain_error.
+ *
+ * Rounding Policy: GncNumeric provides a convert() member function that object
+ * amount and value setters (and *only* those functions!) should call to set a
+ * number which is represented in the commodity's SCU. Since SCUs are seldom 18
+ * digits the convert may result in rounding, which will be performed in the
+ * method specified by the arguments passed to convert(). Overflows may result
+ * in internal rounding as described earlier.
+ */
+
+class GncNumeric
+{
+public:
+ /**
+ * Default constructor provides the zero value.
+ */
+ GncNumeric() : m_num (0), m_den(1) {}
+ /**
+ * Integer constructor.
+ *
+ * Unfortunately specifying a default for denom causes ambiguity errors with
+ * the other single-argument constructors on older versions of gcc, so one
+ * must always specify both argumets.
+ *
+ * \param num The Numerator
+ * \param denom The Denominator
+ */
+ GncNumeric(int64_t num, int64_t denom) :
+ m_num(num), m_den(denom) {
+ if (denom == 0)
+ throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
+ }
+ /**
+ * GncRational constructor.
+ *
+ * This constructor will round rr's GncInt128s to fit into the int64_t
+ * members using RoundType::half-down.
+ *
+ * \param rr A GncRational.
+ */
+ GncNumeric(GncRational rr);
+ /**
+ * gnc_numeric constructor, used for interfacing old code. This function
+ * should not be used outside of gnc-numeric.cpp.
+ *
+ * \param in A gnc_numeric.
+ */
+ GncNumeric(gnc_numeric in) : m_num(in.num), m_den(in.denom)
+ {
+ if (in.denom == 0)
+ throw std::invalid_argument("Attempt to construct a GncNumeric with a 0 denominator.");
+ /* gnc_numeric has a dumb convention that a negative denominator means
+ * to multiply the numerator by the denominator instead of dividing.
+ */
+ if (in.denom < 0)
+ {
+ m_num *= -in.denom;
+ m_den = 1;
+ }
+ }
+ /**
+ * Double constructor.
+ *
+ * @param d The double to be converted. In order to fit in an int64_t, its
+ * absolute value must be < 1e18; if its absolute value is < 1e-18 it will
+ * be represented as 0, though for practical purposes nearly all commodities
+ * will round to zero at 1e-9 or larger.
+ */
+ GncNumeric(double d);
+
+ /**
+ * String constructor.
+ *
+ * Accepts integer values in decimal and hexadecimal. Does not accept
+ * thousands separators. If the string contains a '/' it is taken to
+ * separate the numerator and denominator; if it conains either a '.' or a
+ * ',' it is taken as a decimal point and the integers on either side will
+ * be combined and a denominator will be the appropriate power of 10. If
+ * neither is present the number will be treated as an integer and m_den
+ * will be set to 1.
+ *
+ * Whitespace around a '/' is ignored. A correctly-formatted number will be
+ * extracted from a larger string.
+ *
+ * Numbers that cannot be represented with int64_ts will throw
+ * std::out_of_range unless a decimal point is found (see above). A 0
+ * denominator will cause the constructor to throw std::underflow_error. An
+ * empty string or one which contains no recognizable number will result in
+ * std::invalid_argument.
+ */
+ GncNumeric(const std::string& str, bool autoround=false);
+
+ /**
+ * gnc_numeric conversion. Use static_cast<gnc_numeric>(foo)
+ */
+ operator gnc_numeric() const noexcept;
+ /**
+ * gnc_numeric conversion. Use static_cast<double>(foo)
+ */
+ operator double() const noexcept;
+
+ /**
+ * Accessor for numerator value.
+ */
+ int64_t num() const noexcept { return m_num; }
+ /**
+ * Accessor for denominator value.
+ */
+ int64_t denom() const noexcept { return m_den; }
+ /**
+ * @return A GncNumeric with the opposite sign.
+ */
+ GncNumeric operator-() const noexcept;
+ /**
+ * @return 0 if this == 0 else 1/this.
+ */
+ GncNumeric inv() const noexcept;
+ /**
+ * @return -this if this < 0 else this.
+ */
+ GncNumeric abs() const noexcept;
+ /**
+ * Reduce this to an equivalent fraction with the least common multiple as
+ * the denominator.
+ *
+ * @return reduced GncNumeric
+ */
+ GncNumeric reduce() const noexcept;
+ /**
+ * Convert a GncNumeric to use a new denominator. If rounding is necessary
+ * use the indicated template specification. For example, to use half-up
+ * rounding you'd call bar = foo.convert<RoundType::half_up>(1000). If you
+ * specify RoundType::never this will throw std::domain_error if rounding is
+ * required.
+ *
+ * \param new_denom The new denominator to convert the fraction to.
+ * \return A new GncNumeric having the requested denominator.
+ */
+ template <RoundType RT>
+ GncNumeric convert(int64_t new_denom) const
+ {
+ auto params = prepare_conversion(new_denom);
+ if (new_denom == GNC_DENOM_AUTO)
+ new_denom = m_den;
+ if (params.rem == 0)
+ return GncNumeric(params.num, new_denom);
+ return GncNumeric(round(params.num, params.den,
+ params.rem, RT2T<RT>()), new_denom);
+ }
+
+ /**
+ * Convert with the specified sigfigs. The resulting denominator depends on
+ * the value of the GncNumeric, such that the specified significant digits
+ * are retained in the numerator and the denominator is always a power of
+ * 10. This is of rather dubious benefit in an accounting program, but it's
+ * used in several places so it needs to be implemented.
+ *
+ * @param figs The number of digits to use for the numerator.
+ * @return A GncNumeric with the specified number of digits in the numerator
+ * and the appropriate power-of-ten denominator.
+ */
+ template <RoundType RT>
+ GncNumeric convert_sigfigs(int figs) const
+ {
+ auto new_denom(sigfigs_denom(figs));
+ auto params = prepare_conversion(new_denom);
+ if (new_denom == 0) //It had better not, but just in case...
+ new_denom = 1;
+ if (params.rem == 0)
+ return GncNumeric(params.num, new_denom);
+ return GncNumeric(round(params.num, params.den,
+ params.rem, RT2T<RT>()), new_denom);
+ }
+ /**
+ * Return a string representation of the GncNumeric. See operator<< for
+ * details.
+ */
+ std::string to_string() const noexcept;
+ /**
+ * Convert the numeric to have a power-of-10 denominator if possible without
+ * rounding. Throws a std::rane_error on failure; the message will explain
+ * the details.
+ *
+ * @param max_places exponent of the largest permissible denominator.
+ * @return A GncNumeric value with the new denominator.
+ */
+ GncNumeric to_decimal(unsigned int max_places=17) const;
+ /**
+ * \defgroup gnc_numeric_mutators
+ *
+ * These are the standard mutating operators. They use GncRational's
+ * operators and then call the GncRational constructor, which will silently
+ * round half-down.
+ *
+ * @{
+ */
+ void operator+=(GncNumeric b);
+ void operator-=(GncNumeric b);
+ void operator*=(GncNumeric b);
+ void operator/=(GncNumeric b);
+ /* @} */
+ /** Compare function
+ *
+ * @param b GncNumeric or int to compare to.
+ * @return -1 if this < b, 0 if ==, 1 if this > b.
+ */
+ int cmp(GncNumeric b);
+private:
+ struct round_param
+ {
+ int64_t num;
+ int64_t den;
+ int64_t rem;
+ };
+ /* Calculates the denominator required to convert to figs sigfigs. */
+ int64_t sigfigs_denom(unsigned figs) const noexcept;
+ /* Calculates a round_param struct to pass to a rounding function that will
+ * finish computing a GncNumeric with the new denominator.
+ */
+ round_param prepare_conversion(int64_t new_denom) const;
+ int64_t m_num;
+ int64_t m_den;
+};
+
+/**
+ * \defgroup gnc_numeric_arithmetic_operators
+ *
+ * Normal arithmetic operators. The class arithmetic operators are implemented
+ * in terms of these operators. They use GncRational operators internally then
+ * call the GncNumeric(GncRational&) constructor which will silently round
+ * half-down to obtain int64_t members.
+ *
+ * These operators can throw std::overflow_error, std::underflow_error, or
+ * std::invalid argument as indicated in the class GncNumeric documentation.
+ *
+ * \param a The right-side operand
+ * \param b The left-side operand
+ * \return A new GncNumeric computed from the sum.
+ */
+GncNumeric operator+(GncNumeric a, GncNumeric b);
+GncNumeric operator-(GncNumeric a, GncNumeric b);
+GncNumeric operator*(GncNumeric a, GncNumeric b);
+GncNumeric operator/(GncNumeric a, GncNumeric b);
+/**
+ * std::stream output operator. Uses standard integer operator<< so should obey
+ * locale rules. Numbers are presented as integers if the denominator is 1, as a
+ * decimal if the denominator is a multiple of 10, otherwise as
+ * "numerator/denominator".
+ */
+
+/* Implementation adapted from "The C++ Standard Library, Second Edition" by
+ * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
+ */
+template <typename charT, typename traits>
+std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& s, GncNumeric n)
+{
+ std::basic_ostringstream<charT, traits> ss;
+ ss.copyfmt(s);
+ ss.width(0);
+ if (n.denom() == 1)
+ ss << n.num();
+ else if (n.denom() % 10 == 0)
+ ss << n.num() / n.denom() << "."
+ << (n.num() > 0 ? n.num() : -n.num()) % n.denom();
+ else
+ ss << n.num() << "/" << n.denom();
+ s << ss.str();
+ return s;
+}
+
+
+/**
+ * std::stream input operator.
+ *
+ * Doesn't do any special space handling, spaces in the input stream will
+ * delimit elements. The result will be that if a number is presented as "123 /
+ * 456", the resulting GncNumeric will be 123/1 and the rest will go to the next
+ * parameter in the stream call. The GncNumeric will be constructed with the
+ * string constructor with autorounding. It will throw in the event of any
+ * errors noted in the string constructor documentation.
+ */
+/* Implementation adapted from "The C++ Standard Library, Second Edition" by
+ * Nicolai M. Josuttis, Addison-Wesley, 2012, ISBN 978-0-321-62321-8.
+ */
+template <typename charT, typename traits>
+std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& s, GncNumeric& n)
+{
+
+ std::string instr;
+ s >> instr;
+ if (s)
+ n = GncNumeric(instr, true);
+ return s;
+}
+
+/**
+ * @return -1 if a < b, 0 if a == b, 1 if a > b.
+ */
+int cmp(GncNumeric a, GncNumeric b);
+/**
+ * \defgroup gnc_numeric_comparison_operators
+ * @{
+ * Standard comparison operators, which do what one would expect.
+ */
+bool operator<(GncNumeric a, GncNumeric b);
+bool operator>(GncNumeric a, GncNumeric b);
+bool operator==(GncNumeric a, GncNumeric b);
+bool operator<=(GncNumeric a, GncNumeric b);
+bool operator>=(GncNumeric a, GncNumeric b);
+bool operator!=(GncNumeric a, GncNumeric b);
+/** @} */
+/**
+ * Convenience function to quickly return 10**digits.
+ * \param digits The desired exponent. Maximum value is 17.
+ * \return 10**digits
+ */
+int64_t powten(int64_t digits);
+
+#endif // __GNC_NUMERIC_HPP__
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 98bbe73..e78ed6e 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -22,21 +22,7 @@
#include <sstream>
#include "gnc-rational.hpp"
-
-static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
- 10000000, 100000000, 1000000000, 10000000000,
- 100000000000, 1000000000000, 10000000000000,
- 100000000000000, 10000000000000000,
- 100000000000000000, 1000000000000000000};
-static const int POWTEN_OVERFLOW {-5};
-
-static inline gint64
-powten (int exp)
-{
- if (exp > 18 || exp < -18)
- return POWTEN_OVERFLOW;
- return exp < 0 ? -pten[-exp] : pten[exp];
-}
+#include "gnc-numeric.hpp"
GncRational::GncRational (gnc_numeric n) noexcept :
m_num (n.num), m_den (n.denom), m_error {GNC_ERROR_OK}
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index fe363f5..b6667e6 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -27,28 +27,8 @@
#include "gnc-int128.hpp"
struct GncDenom;
-
-enum class RoundType
-{
- floor = GNC_HOW_RND_FLOOR,
- ceiling = GNC_HOW_RND_CEIL,
- truncate = GNC_HOW_RND_TRUNC,
- promote = GNC_HOW_RND_PROMOTE,
- half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
- half_up = GNC_HOW_RND_ROUND_HALF_UP,
- bankers = GNC_HOW_RND_ROUND,
- never = GNC_HOW_RND_NEVER,
-};
-
-enum class DenomType
-{
- den_auto = GNC_DENOM_AUTO,
- exact = GNC_HOW_DENOM_EXACT,
- reduce = GNC_HOW_DENOM_REDUCE,
- lcd = GNC_HOW_DENOM_LCD,
- fixed = GNC_HOW_DENOM_FIXED,
- sigfigs = GNC_HOW_DENOM_SIGFIG,
-};
+enum class RoundType;
+enum class DenomType;
/** @ingroup QOF
* @brief Rational number class using GncInt128 for the numerator and denominator.
diff --git a/src/libqof/qof/test/CMakeLists.txt b/src/libqof/qof/test/CMakeLists.txt
index de62f16..1e9ab7d 100644
--- a/src/libqof/qof/test/CMakeLists.txt
+++ b/src/libqof/qof/test/CMakeLists.txt
@@ -92,6 +92,13 @@ IF (NOT WIN32)
GNC_ADD_TEST(test-gnc-rational "${test_gnc_rational_SOURCES}"
gtest_qof_INCLUDES gtest_qof_LIBS)
+ SET(test_gnc_numeric_SOURCES
+ ${MODULEPATH}/gnc-numeric.cpp
+ gtest-gnc-numeric.cpp
+ ${GTEST_SRC})
+ GNC_ADD_TEST(test-gnc-numeric "${test_gnc_numeric_SOURCES}"
+ gtest_qof_INCLUDES gtest_qof_LIBS)
+
SET(test_gnc_timezone_SOURCES
${MODULEPATH}/gnc-timezone.cpp
gtest-gnc-timezone.cpp
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index ee82db8..ef4d350 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -132,15 +132,42 @@ check_PROGRAMS += test-gnc-int128
test_gnc_rational_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
+ $(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
+ $(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
gtest-gnc-rational.cpp
-test_gnc_rational_CPPFLAGS = -I${GTEST_HEADERS}
-test_gnc_rational_LDADD = ${GTEST_LIBS}
+test_gnc_rational_CPPFLAGS = \
+ -I${GTEST_HEADERS} \
+ ${GLIB_CFLAGS}
+
+test_gnc_rational_LDADD = \
+ ${GTEST_LIBS} \
+ ${GLIB_LIBS}
+
if !GOOGLE_TEST_LIBS
nodist_test_gnc_rational_SOURCES = \
${GTEST_SRC}/src/gtest_main.cc
endif
check_PROGRAMS += test-gnc-rational
+check_PROGRAMS += test-gnc-int128
+
+test_gnc_numeric_SOURCES = \
+ $(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
+ $(top_srcdir)/${MODULEPATH}/gnc-int128.cpp \
+ $(top_srcdir)/${MODULEPATH}/gnc-numeric.cpp \
+ gtest-gnc-numeric.cpp
+test_gnc_numeric_CPPFLAGS = \
+ -I${GTEST_HEADERS} \
+ ${GLIB_CFLAGS}
+
+test_gnc_numeric_LDADD = \
+ ${GTEST_LIBS} \
+ ${GLIB_LIBS}
+if !GOOGLE_TEST_LIBS
+nodist_test_gnc_numeric_SOURCES = \
+ ${GTEST_SRC}/src/gtest_main.cc
+endif
+check_PROGRAMS += test-gnc-numeric
test_gnc_timezone_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-timezone.cpp \
diff --git a/src/libqof/qof/test/gtest-gnc-numeric.cpp b/src/libqof/qof/test/gtest-gnc-numeric.cpp
new file mode 100644
index 0000000..e825ecf
--- /dev/null
+++ b/src/libqof/qof/test/gtest-gnc-numeric.cpp
@@ -0,0 +1,507 @@
+/********************************************************************
+ * gtest-gnc-numeric.cpp -- unit tests for the GncNumeric class *
+ * Copyright 2017 John Ralls <jralls at ceridwen.us> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+\********************************************************************/
+
+#include <gtest/gtest.h>
+#include "../gnc-numeric.hpp"
+#include "../gnc-rational.hpp"
+
+TEST(gncnumeric_constructors, test_default_constructor)
+{
+ GncNumeric value;
+ EXPECT_EQ(value.num(), 0);
+ EXPECT_EQ(value.denom(), 1);
+}
+
+TEST(gncnumeric_constructors, test_gnc_rational_constructor)
+{
+ GncInt128 num128(INT64_C(123456789), INT64_C(567894392130486208));
+ GncInt128 den128(INT64_C(456789123), INT64_C(543210987651234567));
+ GncRational in (num128, den128);
+ GncNumeric out;
+ ASSERT_NO_THROW(out = GncNumeric(in));
+ EXPECT_EQ(INT64_C(1246404345742679464), out.num());
+ EXPECT_EQ(INT64_C(4611686019021985017), out.denom());
+}
+
+TEST(gncnumeric_constructors, test_gnc_numeric_constructor)
+{
+ gnc_numeric input = gnc_numeric_create(123, 456);
+ GncNumeric value(input);
+ EXPECT_EQ(input.num, value.num());
+ EXPECT_EQ(input.denom, value.denom());
+
+ input = gnc_numeric_create(123, 0);
+ ASSERT_THROW(GncNumeric throw_val(input), std::invalid_argument);
+}
+
+TEST(gncnumeric_constructors, test_int64_constructor)
+{
+ int64_t num(123), denom(456);
+ GncNumeric value(num, denom);
+ EXPECT_EQ(123, value.num());
+ EXPECT_EQ(456, value.denom());
+ denom = INT64_C(0);
+ ASSERT_THROW(GncNumeric throw_val(num, denom), std::invalid_argument);
+}
+
+TEST(gncnumeric_constructors, test_implicit_int_constructor)
+{
+ int num(123), denom(456);
+ GncNumeric value(num, denom);
+ EXPECT_EQ(123, value.num());
+ EXPECT_EQ(456, value.denom());
+ EXPECT_THROW(GncNumeric throw_val(123, 0), std::invalid_argument);
+
+}
+
+TEST(gncnumeric_constructors, test_double_constructor)
+{
+ GncNumeric a(123456.789000);
+ EXPECT_EQ(123456789, a.num());
+ EXPECT_EQ(1000, a.denom());
+ GncNumeric b(-123456.789000);
+ EXPECT_EQ(-123456789, b.num());
+ EXPECT_EQ(1000, b.denom());
+ GncNumeric c(static_cast<double>(0.0000000123456789000));
+ EXPECT_EQ(123456789, c.num());
+ EXPECT_EQ(INT64_C(10000000000000000), c.denom());
+ GncNumeric d(1.23456789e-19);
+ EXPECT_EQ(0, d.num());
+ EXPECT_EQ(1, d.denom());
+ EXPECT_THROW(GncNumeric e(123456789e18), std::invalid_argument);
+ auto f = GncNumeric(1.1234567890123).convert_sigfigs<RoundType::bankers>(6);
+ EXPECT_EQ(112346, f.num());
+ EXPECT_EQ(100000, f.denom());
+ auto g = GncNumeric(0.011234567890123).convert_sigfigs<RoundType::bankers>(6);
+ EXPECT_EQ(112346, g.num());
+ EXPECT_EQ(10000000, g.denom());
+ auto h = GncNumeric(1123.4567890123).convert_sigfigs<RoundType::bankers>(6);
+ EXPECT_EQ(112346, h.num());
+ EXPECT_EQ(100, h.denom());
+ auto i = GncNumeric(1.1234567890123e-5).convert_sigfigs<RoundType::bankers>(6);
+ EXPECT_EQ(112346, i.num());
+ EXPECT_EQ(10000000000, i.denom());
+}
+
+TEST(gncnumeric_constructors, test_string_constructor)
+{
+ EXPECT_NO_THROW(GncNumeric simple_num("123/456"));
+ GncNumeric simple_num("123/456");
+ EXPECT_EQ(123, simple_num.num());
+ EXPECT_EQ(456, simple_num.denom());
+ EXPECT_NO_THROW(GncNumeric neg_simple_num("-123/456"));
+ GncNumeric neg_simple_num("-123/456");
+ EXPECT_EQ(-123, neg_simple_num.num());
+ EXPECT_EQ(456, neg_simple_num.denom());
+ ASSERT_NO_THROW(GncNumeric with_spaces("123 / 456"));
+ GncNumeric with_spaces("123 / 456");
+ EXPECT_EQ(123, with_spaces.num());
+ EXPECT_EQ(456, with_spaces.denom());
+ ASSERT_NO_THROW(GncNumeric neg_with_spaces("-123 / 456"));
+ GncNumeric neg_with_spaces("-123 / 456");
+ EXPECT_EQ(-123, neg_with_spaces.num());
+ EXPECT_EQ(456, neg_with_spaces.denom());
+ ASSERT_NO_THROW(GncNumeric simple_int("123456"));
+ GncNumeric simple_int("123456");
+ EXPECT_EQ(123456, simple_int.num());
+ EXPECT_EQ(1, simple_int.denom());
+ ASSERT_NO_THROW(GncNumeric neg_simple_int("-123456"));
+ GncNumeric neg_simple_int("-123456");
+ EXPECT_EQ(-123456, neg_simple_int.num());
+ EXPECT_EQ(1, neg_simple_int.denom());
+ ASSERT_NO_THROW(GncNumeric simple_hex("0x1e240"));
+ GncNumeric simple_hex("0x1e240");
+ EXPECT_EQ(123456, simple_int.num());
+ EXPECT_EQ(1, simple_int.denom());
+ ASSERT_NO_THROW(GncNumeric simple_decimal("123.456"));
+ GncNumeric simple_decimal("123.456");
+ EXPECT_EQ(123456, simple_decimal.num());
+ EXPECT_EQ(1000, simple_decimal.denom());
+ ASSERT_NO_THROW(GncNumeric neg_simple_decimal("-123.456"));
+ GncNumeric neg_simple_decimal("-123.456");
+ EXPECT_EQ(-123456, neg_simple_decimal.num());
+ EXPECT_EQ(1000, neg_simple_decimal.denom());
+ ASSERT_NO_THROW(GncNumeric continental_decimal("123,456"));
+ GncNumeric continental_decimal("123,456");
+ EXPECT_EQ(123456, continental_decimal.num());
+ EXPECT_EQ(1000, continental_decimal.denom());
+ ASSERT_NO_THROW(GncNumeric neg_continental_decimal("-123,456"));
+ GncNumeric neg_continental_decimal("-123,456");
+ EXPECT_EQ(-123456, neg_continental_decimal.num());
+ EXPECT_EQ(1000, neg_continental_decimal.denom());
+ ASSERT_NO_THROW(GncNumeric hex_rational("0x1e240/0x1c8"));
+ GncNumeric hex_rational("0x1e240/0x1c8");
+ EXPECT_EQ(123456, hex_rational.num());
+ EXPECT_EQ(456, hex_rational.denom());
+ ASSERT_NO_THROW(GncNumeric hex_over_num("0x1e240/456"));
+ GncNumeric hex_over_num("0x1e240/456");
+ EXPECT_EQ(123456, hex_over_num.num());
+ EXPECT_EQ(456, hex_over_num.denom());
+ ASSERT_NO_THROW(GncNumeric num_over_hex("123456/0x1c8"));
+ GncNumeric num_over_hex("123456/0x1c8");
+ EXPECT_EQ(123456, num_over_hex.num());
+ EXPECT_EQ(456, num_over_hex.denom());
+ ASSERT_NO_THROW(GncNumeric embedded("The number is 123456/456"));
+ GncNumeric embedded("The number is 123456/456");
+ EXPECT_EQ(123456, embedded.num());
+ EXPECT_EQ(456, embedded.denom());
+ ASSERT_NO_THROW(GncNumeric embedded("The number is -123456/456"));
+ GncNumeric neg_embedded("The number is -123456/456");
+ EXPECT_EQ(-123456, neg_embedded.num());
+ EXPECT_EQ(456, neg_embedded.denom());
+ EXPECT_THROW(GncNumeric throw_zero_denom("123/0"), std::invalid_argument);
+ EXPECT_THROW(GncNumeric overflow("12345678987654321.123456"),
+ std::overflow_error);
+ EXPECT_NO_THROW(GncNumeric overflow("12345678987654321.123456", true));
+ GncNumeric overflow("12345678987654321.123456", true);
+ EXPECT_EQ(6028163568190586486, overflow.num());
+ EXPECT_EQ(488, overflow.denom());
+ EXPECT_THROW(GncNumeric auto_round("12345678987654321234/256", true),
+ std::out_of_range);
+ EXPECT_THROW(GncNumeric bad_string("Four score and seven"),
+ std::invalid_argument);
+}
+
+TEST(gncnumeric_output, string_output)
+{
+ GncNumeric simple_int(123456, 1);
+ EXPECT_EQ("123456", simple_int.to_string());
+ GncNumeric neg_simple_int(-123456, 1);
+ EXPECT_EQ("-123456", neg_simple_int.to_string());
+ GncNumeric decimal_string(123456, 1000);
+ EXPECT_EQ("123.456", decimal_string.to_string());
+ GncNumeric neg_decimal_string(-123456, 1000);
+ EXPECT_EQ("-123.456", neg_decimal_string.to_string());
+ GncNumeric rational_string(123, 456);
+ EXPECT_EQ("123/456", rational_string.to_string());
+ GncNumeric neg_rational_string(-123, 456);
+ EXPECT_EQ("-123/456", neg_rational_string.to_string());
+}
+
+TEST(gncnumeric_stream, output_stream)
+{
+ std::ostringstream output;
+ GncNumeric simple_int(INT64_C(123456));
+ output << simple_int;
+ EXPECT_EQ("123456", output.str());
+ output.str("");
+ GncNumeric decimal_string(123456, 1000);
+ output << decimal_string;
+ EXPECT_EQ("123.456", output.str());
+ output.str("");
+ GncNumeric rational_string(123, 456);
+ output << rational_string;
+ EXPECT_EQ("123/456", output.str());
+}
+
+TEST(gncnumeric_stream, input_stream)
+{
+ std::istringstream input("123456");
+ GncNumeric numeric;
+ EXPECT_NO_THROW(input >> numeric);
+ EXPECT_EQ(123456, numeric.num());
+ EXPECT_EQ(1, numeric.denom());
+ input.clear();
+ input.str("123456/456");
+ EXPECT_NO_THROW(input >> numeric);
+ EXPECT_EQ(123456, numeric.num());
+ EXPECT_EQ(456, numeric.denom());
+ input.clear();
+ input.str("123456 / 456");
+ EXPECT_NO_THROW(input >> numeric);
+ EXPECT_EQ(123456, numeric.num());
+ EXPECT_EQ(1, numeric.denom());
+ input.clear();
+ input.str("0x1e240/0x1c8");
+ EXPECT_NO_THROW(input >> std::hex >> numeric);
+ EXPECT_EQ(123456, numeric.num());
+ EXPECT_EQ(456, numeric.denom());
+ input.clear();
+ input.str("0x1e240/456");
+ EXPECT_NO_THROW(input >> numeric);
+ EXPECT_EQ(123456, numeric.num());
+ EXPECT_EQ(456, numeric.denom());
+ input.clear();
+ input.str("123456/0x1c8");
+ EXPECT_NO_THROW(input >> numeric);
+ EXPECT_EQ(123456, numeric.num());
+ EXPECT_EQ(456, numeric.denom());
+ input.clear();
+ input.str("123/0");
+ EXPECT_THROW(input >> std::dec >> numeric, std::invalid_argument);
+ input.clear();
+ input.str("12345678987654321.123456");
+ EXPECT_NO_THROW(input >> numeric);
+ EXPECT_EQ(6028163568190586486, numeric.num());
+ EXPECT_EQ(488, numeric.denom());
+ input.clear();
+ input.str("12345678987654321234/256");
+ EXPECT_THROW(input >> numeric, std::out_of_range);
+ input.clear();
+ input.str("Four score and seven");
+ EXPECT_THROW(input >> numeric, std::invalid_argument);
+}
+
+TEST(gncnumeric_operators, gnc_numeric_conversion)
+{
+ GncNumeric a(123456789, 9876);
+ gnc_numeric b = static_cast<decltype(b)>(a);
+ EXPECT_EQ(123456789, b.num);
+ EXPECT_EQ(9876, b.denom);
+}
+
+TEST(gncnumeric_operators, double_conversion)
+{
+ GncNumeric a(123456789, 9876);
+ double b = static_cast<decltype(b)>(a);
+ EXPECT_EQ(12500.687424058324, b);
+}
+
+TEST(gncnumeric_operators, test_addition)
+{
+ GncNumeric a(123456789987654321, 1000000000);
+ GncNumeric b(65432198765432198, 100000000);
+ GncNumeric c = a + b;
+ EXPECT_EQ (777778777641976301, c.num());
+ EXPECT_EQ (1000000000, c.denom());
+ a += b;
+ EXPECT_EQ (777778777641976301, a.num());
+ EXPECT_EQ (1000000000, a.denom());
+}
+
+TEST(gncnumeric_operators, test_subtraction)
+{
+ GncNumeric a(123456789987654321, 1000000000);
+ GncNumeric b(65432198765432198, 100000000);
+ GncNumeric c = a - b;
+ EXPECT_EQ (-530865197666667659, c.num());
+ EXPECT_EQ (1000000000, c.denom());
+ c = b - a;
+ EXPECT_EQ (530865197666667659, c.num());
+ EXPECT_EQ (1000000000, c.denom());
+ a -= b;
+ EXPECT_EQ (-530865197666667659, a.num());
+ EXPECT_EQ (1000000000, a.denom());
+ GncNumeric d(2, 6), e(1, 4);
+ c = d - e;
+ EXPECT_EQ(1, c.num());
+ EXPECT_EQ(12, c.denom());
+}
+
+TEST(gncnumeric_operators, test_multiplication)
+{
+ GncNumeric a(123456789987654321, 1000000000);
+ GncNumeric b(65432198765432198, 100000000);
+ GncNumeric c = a * b;
+ EXPECT_EQ (4604488056206217807, c.num());
+ EXPECT_EQ (57, c.denom());
+ a *= b;
+ EXPECT_EQ (4604488056206217807, a.num());
+ EXPECT_EQ (57, a.denom());
+
+ GncNumeric d(215815575996, 269275978715);
+ GncNumeric e(1002837599929, 1);
+ GncNumeric f, g;
+ EXPECT_NO_THROW(f = d * e);
+ EXPECT_NO_THROW(g = f.convert<RoundType::half_up>(1));
+
+}
+
+TEST(gncnumeric_operators, test_division)
+{
+ GncNumeric a(123456789987654321, 1000000000);
+ GncNumeric b(65432198765432198, 100000000);
+ GncNumeric c = a / b;
+ EXPECT_EQ (123456789987654321, c.num());
+ EXPECT_EQ (654321987654321980, c.denom());
+
+ a /= b;
+ EXPECT_EQ (123456789987654321, a.num());
+ EXPECT_EQ (654321987654321980, a.denom());
+
+}
+
+TEST(gncnumeric_functions, test_cmp)
+{
+ GncNumeric a(123456789, 9876), b(567894321, 6543);
+ auto c = a;
+ EXPECT_EQ(0, a.cmp(c));
+ EXPECT_EQ(-1, a.cmp(b));
+ EXPECT_EQ(1, b.cmp(a));
+ EXPECT_EQ(-1, b.cmp(INT64_C(88888)));
+ EXPECT_EQ(1, a.cmp(INT64_C(12500)));
+}
+
+TEST(gncnumeric_functions, test_invert)
+{
+ GncNumeric a(123456789, 9876), b, c;
+ ASSERT_NO_THROW(c = b.inv());
+ EXPECT_EQ(0, c.num());
+ EXPECT_EQ(1, c.denom());
+ ASSERT_NO_THROW(b = a.inv());
+ EXPECT_EQ(9876, b.num());
+ EXPECT_EQ(123456789, b.denom());
+}
+
+TEST(gncnumeric_functions, test_reduce)
+{
+ GncNumeric a(123456789, 5202504), b;
+ ASSERT_NO_THROW(b = a.reduce());
+ EXPECT_EQ(3607, b.num());
+ EXPECT_EQ(152, b.denom());
+}
+
+TEST(gncnumeric_functions, test_convert)
+{
+ GncNumeric a(12345678, 456), b(-12345678, 456), c;
+ ASSERT_NO_THROW(c = a.convert<RoundType::never>(456));
+ EXPECT_EQ(12345678, c.num());
+ EXPECT_EQ(456, c.denom());
+ EXPECT_THROW(c = a.convert<RoundType::never>(128), std::domain_error);
+ ASSERT_NO_THROW(c = a.convert<RoundType::floor>(128));
+ EXPECT_EQ(3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::floor>(128));
+ EXPECT_EQ(-3465452, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::ceiling>(128));
+ EXPECT_EQ(3465454, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::ceiling>(128));
+ EXPECT_EQ(-3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::truncate>(128));
+ EXPECT_EQ(3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::truncate>(128));
+ EXPECT_EQ(-3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::promote>(128));
+ EXPECT_EQ(3465454, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::promote>(128));
+ EXPECT_EQ(-3465454, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::half_down>(128));
+ EXPECT_EQ(3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::half_down>(114));
+ EXPECT_EQ(3086419, c.num());
+ EXPECT_EQ(114, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::half_down>(118));
+ EXPECT_EQ(3194715, c.num());
+ EXPECT_EQ(118, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::half_down>(128));
+ EXPECT_EQ(-3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::half_down>(114));
+ EXPECT_EQ(-3086419, c.num());
+ EXPECT_EQ(114, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::half_down>(121));
+ EXPECT_EQ(-3275936, c.num());
+ EXPECT_EQ(121, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::half_up>(128));
+ EXPECT_EQ(3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::half_up>(114));
+ EXPECT_EQ(3086420, c.num());
+ EXPECT_EQ(114, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::half_up>(118));
+ EXPECT_EQ(3194715, c.num());
+ EXPECT_EQ(118, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::half_up>(128));
+ EXPECT_EQ(-3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::half_up>(114));
+ EXPECT_EQ(-3086420, c.num());
+ EXPECT_EQ(114, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::half_up>(121));
+ EXPECT_EQ(-3275936, c.num());
+ EXPECT_EQ(121, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::bankers>(128));
+ EXPECT_EQ(3465453, c.num());
+ EXPECT_EQ(128, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::bankers>(114));
+ EXPECT_EQ(3086420, c.num());
+ EXPECT_EQ(114, c.denom());
+ ASSERT_NO_THROW(c = a.convert<RoundType::bankers>(118));
+ EXPECT_EQ(3194715, c.num());
+ EXPECT_EQ(118, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::bankers>(127));
+ EXPECT_EQ(-3438380, c.num());
+ EXPECT_EQ(127, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::bankers>(114));
+ EXPECT_EQ(-3086420, c.num());
+ EXPECT_EQ(114, c.denom());
+ ASSERT_NO_THROW(c = b.convert<RoundType::bankers>(121));
+ EXPECT_EQ(-3275936, c.num());
+ EXPECT_EQ(121, c.denom());
+ GncNumeric o(123456789123456789, 128);
+ EXPECT_THROW(c = o.convert<RoundType::bankers>(54321098),
+ std::overflow_error);
+ GncNumeric d(7, 16);
+ ASSERT_NO_THROW(c = d.convert<RoundType::floor>(100));
+ EXPECT_EQ(43, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = d.convert<RoundType::ceiling>(100));
+ EXPECT_EQ(44, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = d.convert<RoundType::truncate>(100));
+ EXPECT_EQ(43, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = d.convert<RoundType::bankers>(100));
+ EXPECT_EQ(44, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = GncNumeric(1511, 1000).convert<RoundType::bankers>(100));
+ EXPECT_EQ(151, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = GncNumeric(1516, 1000).convert<RoundType::bankers>(100));
+ EXPECT_EQ(152, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = GncNumeric(1515, 1000).convert<RoundType::bankers>(100));
+ EXPECT_EQ(152, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = GncNumeric(1525, 1000).convert<RoundType::bankers>(100));
+ EXPECT_EQ(152, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = GncNumeric(1535, 1000).convert<RoundType::bankers>(100));
+ EXPECT_EQ(154, c.num());
+ EXPECT_EQ(100, c.denom());
+ ASSERT_NO_THROW(c = GncNumeric(1545, 1000).convert<RoundType::bankers>(100));
+ EXPECT_EQ(154, c.num());
+ EXPECT_EQ(100, c.denom());
+}
+
+TEST(gnc_numeric_functions, test_conversion_to_decimal)
+{
+ GncNumeric a(123456789, 1000), r;
+ EXPECT_NO_THROW(r = a.to_decimal());
+ EXPECT_EQ(123456789, r.num());
+ EXPECT_EQ(1000, r.denom());
+ EXPECT_THROW(r = a.to_decimal(2), std::range_error);
+ GncNumeric b(123456789, 456);
+ EXPECT_THROW(r = b.to_decimal(), std::range_error);
+ GncNumeric c(123456789, 450);
+ EXPECT_NO_THROW(r = c.to_decimal());
+ EXPECT_EQ(27434842, r.num());
+ EXPECT_EQ(100, r.denom());
+}
diff --git a/src/libqof/qof/test/test-gnc-numeric.c b/src/libqof/qof/test/test-gnc-numeric.c
deleted file mode 100644
index f6cf0d6..0000000
--- a/src/libqof/qof/test/test-gnc-numeric.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/********************************************************************
- * test_qofbackend.c: GLib g_test test suite for qofbackend. *
- * Copyright 2011 John Ralls <jralls at ceridwen.us> *
- * *
- * This program is free software; you can redistribute it and/or *
- * modify it under the terms of the GNU General Public License as *
- * published by the Free Software Foundation; either version 2 of *
- * the License, or (at your option) any later version. *
- * *
- * This program is distributed in the hope that it will be useful, *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
- * GNU General Public License for more details. *
- * *
- * You should have received a copy of the GNU General Public License*
- * along with this program; if not, contact: *
- * *
- * Free Software Foundation Voice: +1-617-542-5942 *
- * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
- * Boston, MA 02110-1301, USA gnu at gnu.org *
-\********************************************************************/
-#include "config.h"
-#include <string.h>
-#include <glib.h>
-#include <qof.h>
-#include <unittest-support.h>
-
-static const gchar *suitename = "/qof/gnc_numeric";
-
-static void
-test_gnc_numeric_add (void)
-{
- gnc_numeric a = { 123456789987654321, 1000000000 };
- gnc_numeric b = { 65432198765432198, 100000000 };
- gnc_numeric goal_ab = { 777778777641976301, 1000000000 };
- gnc_numeric result;
-
- result = gnc_numeric_add (a, b, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER);
- g_assert (gnc_numeric_equal (result, goal_ab));
-}
-
-void
-test_suite_gnc_numeric ( void )
-{
- GNC_TEST_ADD_FUNC( suitename, "gnc-numeric add", test_gnc_numeric_add );
-}
commit 4fef04c17b3df6b29ee3703af459adc3ea32c977
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 13:02:56 2017 -0800
Remove #ifdef __cplusplus from gnu-numeric.cpp
Itâs always compiled with C++.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 1a07735..05eb90c 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -21,11 +21,9 @@
* Boston, MA 02110-1301, USA gnu at gnu.org *
* *
*******************************************************************/
-#ifdef __cplusplus
+
extern "C"
{
-#endif
-
#include "config.h"
#include <glib.h>
@@ -33,10 +31,10 @@ extern "C"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#ifdef __cplusplus
+
#include "qof.h"
}
-#endif
+
#include <stdint.h>
#include "gnc-numeric.h"
commit 570c8a8d60048f66ede9300e7ddf331192543ef6
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 12:59:45 2017 -0800
Fix GncInt128 shift operators when shift amount will clear a leg.
diff --git a/src/libqof/qof/gnc-int128.cpp b/src/libqof/qof/gnc-int128.cpp
index 06e4d8a..f12ec91 100644
--- a/src/libqof/qof/gnc-int128.cpp
+++ b/src/libqof/qof/gnc-int128.cpp
@@ -309,10 +309,16 @@ GncInt128::operator<<= (unsigned int i) noexcept
m_lo = 0;
return *this;
}
- uint64_t carry {(m_lo & (((UINT64_C(1) << i) - 1) << (legbits - i)))};
- m_lo <<= i;
- m_hi <<= i;
- m_hi += carry;
+ if (i < legbits)
+ {
+ uint64_t carry {(m_lo & (((UINT64_C(1) << i) - 1) << (legbits - i)))};
+ m_lo <<= i;
+ m_hi <<= i;
+ m_hi += carry;
+ return *this;
+ }
+ m_hi = m_lo << (i - legbits);
+ m_lo = 0;
return *this;
}
@@ -326,10 +332,16 @@ GncInt128::operator>>= (unsigned int i) noexcept
m_lo = 0;
return *this;
}
- uint64_t carry {(m_hi & ((UINT64_C(1) << i) - 1))};
- m_lo >>= i;
- m_hi >>= i;
- m_lo += (carry << (legbits - i));
+ if (i < legbits)
+ {
+ uint64_t carry {(m_hi & ((UINT64_C(1) << i) - 1))};
+ m_lo >>= i;
+ m_hi >>= i;
+ m_lo += (carry << (legbits - i));
+ return *this;
+ }
+ m_lo = m_hi >> (i - legbits);
+ m_hi = 0;
return *this;
}
commit 4a134ae0b1980ed830f3cff954ab870dae67bcd1
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 12:56:24 2017 -0800
Declare GncInt128::div() const
Because it doesnât change the value of *this, it returns the results in
the return value args.
diff --git a/src/libqof/qof/gnc-int128.cpp b/src/libqof/qof/gnc-int128.cpp
index 7c32320..06e4d8a 100644
--- a/src/libqof/qof/gnc-int128.cpp
+++ b/src/libqof/qof/gnc-int128.cpp
@@ -605,7 +605,7 @@ div_single_leg (uint64_t* u, size_t m, uint64_t v, GncInt128& q, GncInt128& r) n
}// namespace
void
-GncInt128::div (const GncInt128& b, GncInt128& q, GncInt128& r) noexcept
+GncInt128::div (const GncInt128& b, GncInt128& q, GncInt128& r) const noexcept
{
if (isOverflow() || b.isOverflow())
{
diff --git a/src/libqof/qof/gnc-int128.hpp b/src/libqof/qof/gnc-int128.hpp
index 40b3dd8..700c69d 100644
--- a/src/libqof/qof/gnc-int128.hpp
+++ b/src/libqof/qof/gnc-int128.hpp
@@ -170,7 +170,7 @@ enum // Values for m_flags
* @param q The quotient; will be NaN if divisor = 0
* @param r The remainder; will be 0 if divisor = 0
*/
- void div (const GncInt128& d, GncInt128& q, GncInt128& r) noexcept;
+ void div (const GncInt128& d, GncInt128& q, GncInt128& r) const noexcept;
/**
* Explicit conversion to int64_t.
commit 06d22718f5ad8ec213d992a6bfb9b5f8c9540805
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 12:55:06 2017 -0800
Fix minor typos and whitespace issues.
diff --git a/src/libqof/qof/gnc-int128.cpp b/src/libqof/qof/gnc-int128.cpp
index 2df2008..7c32320 100644
--- a/src/libqof/qof/gnc-int128.cpp
+++ b/src/libqof/qof/gnc-int128.cpp
@@ -78,9 +78,9 @@ GncInt128::zero () noexcept
GncInt128::operator int64_t() const
{
if ((m_flags & neg) && isBig())
- throw std::underflow_error ("Negative value to large to represent as int64_t");
+ throw std::underflow_error ("Negative value too large to represent as int64_t");
if ((m_flags & (overflow | NaN)) || isBig())
- throw std::overflow_error ("Value to large to represent as int64_t");
+ throw std::overflow_error ("Value too large to represent as int64_t");
int64_t retval = static_cast<int64_t>(m_lo);
return m_flags & neg ? -retval : retval;
}
diff --git a/src/libqof/qof/gnc-int128.hpp b/src/libqof/qof/gnc-int128.hpp
index 2efc466..40b3dd8 100644
--- a/src/libqof/qof/gnc-int128.hpp
+++ b/src/libqof/qof/gnc-int128.hpp
@@ -88,7 +88,7 @@ enum // Values for m_flags
/** Default constructor. Makes 0. */
GncInt128();
template <typename T>
- GncInt128(T lower) : GncInt128 {INT64_C(0), static_cast<int64_t>(lower)}
+ GncInt128(T lower) : GncInt128(INT64_C(0), static_cast<int64_t>(lower))
{
static_assert (std::is_integral<T>(),
"GncInt128 can be constructed only with "
@@ -278,6 +278,7 @@ GncInt128 gcd (int64_t a, int64_t b);
/** Compute the least common multiple of two integers
*/
GncInt128 lcm (int64_t a, int64_t b);
+
#endif //GNCINT128_H
/** @} */
commit 3975b0b465ec181ba36fad86599db192524751c7
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 12:42:08 2017 -0800
Change signature of GncRational::round from taking a GncDenominatorâ¦
to a separate denominator and RoundType.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 4d8d45b..1a07735 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -34,6 +34,7 @@ extern "C"
#include <stdlib.h>
#include <string.h>
#ifdef __cplusplus
+#include "qof.h"
}
#endif
#include <stdint.h>
@@ -262,8 +263,14 @@ gnc_numeric_add(gnc_numeric a, gnc_numeric b,
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
-
- return static_cast<gnc_numeric>(an.add(bn, new_denom));
+ try
+ {
+ return static_cast<gnc_numeric>(an.add(bn, new_denom));
+ }
+ catch (const std::overflow_error& err)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
}
/* *******************************************************************
@@ -302,8 +309,15 @@ gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
+ try
+ {
+ return static_cast<gnc_numeric>(an.mul(bn, new_denom));
+ }
+ catch (const std::overflow_error& err)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
- return static_cast<gnc_numeric>(an.mul(bn, new_denom));
}
@@ -324,8 +338,14 @@ gnc_numeric_div(gnc_numeric a, gnc_numeric b,
GncDenom new_denom (an, bn, denom, how);
if (new_denom.m_error)
return gnc_numeric_error (new_denom.m_error);
-
- return static_cast<gnc_numeric>(an.div(bn, new_denom));
+ try
+ {
+ return static_cast<gnc_numeric>(an.div(bn, new_denom));
+ }
+ catch (const std::overflow_error& err)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
}
/* *******************************************************************
@@ -368,8 +388,16 @@ gnc_numeric_convert(gnc_numeric in, int64_t denom, int how)
{
GncNumeric a (in), b (gnc_numeric_zero());
GncDenom d (a, b, denom, how);
- a.round (d);
- return static_cast<gnc_numeric>(a);
+ try
+ {
+ d.reduce(a);
+ a.round (d.get(), d.m_round);
+ return static_cast<gnc_numeric>(a);
+ }
+ catch (const std::overflow_error& err)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
}
@@ -391,8 +419,16 @@ gnc_numeric_reduce(gnc_numeric in)
return in;
GncNumeric a (in), b (gnc_numeric_zero());
GncDenom d (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE | GNC_HOW_RND_ROUND);
- a.round (d);
- return static_cast<gnc_numeric>(a);
+ try
+ {
+ d.reduce(a);
+ a.round (d.get(), d.m_round);
+ return static_cast<gnc_numeric>(a);
+ }
+ catch (const std::overflow_error& err)
+ {
+ return gnc_numeric_error(GNC_ERROR_OVERFLOW);
+ }
}
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 23f4338..98bbe73 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -189,45 +189,41 @@ GncRational::operator/=(GncRational b)
}
GncRational&
-GncRational::mul (const GncRational& b, GncDenom& d) noexcept
+GncRational::mul (const GncRational& b, GncDenom& d)
{
*this *= b;
- round (d);
+ d.reduce(*this);
+ round (d.get(), d.m_round);
return *this;
}
GncRational&
-GncRational::div (GncRational b, GncDenom& d) noexcept
+GncRational::div (GncRational b, GncDenom& d)
{
*this /= b;
- round (d);
+ d.reduce(*this);
+ round (d.get(), d.m_round);
return *this;
}
GncRational&
-GncRational::add (const GncRational& b, GncDenom& d) noexcept
+GncRational::add (const GncRational& b, GncDenom& d)
{
*this += b;
- round (d);
+ d.reduce(*this);
+ round (d.get(), d.m_round);
return *this;
}
GncRational&
-GncRational::sub (const GncRational& b, GncDenom& d) noexcept
+GncRational::sub (const GncRational& b, GncDenom& d)
{
return add(-b, d);
}
void
-GncRational::round (GncDenom& denom) noexcept
+GncRational::round (GncInt128 new_den, RoundType rtype)
{
- denom.reduce (*this);
- if (m_error == GNC_ERROR_OK && denom.m_error != GNC_ERROR_OK)
- {
- m_error = denom.m_error;
- return;
- }
- GncInt128 new_den = denom.get();
if (new_den == 0) new_den = m_den;
if (!(m_num.isBig() || new_den.isBig() ))
{
@@ -243,9 +239,20 @@ GncRational::round (GncDenom& denom) noexcept
GncInt128 new_num {}, remainder {};
if (new_den.isNeg())
m_num.div(-new_den * m_den, new_num, remainder);
- else
+ else if (new_den != m_den)
(m_num * new_den).div(m_den, new_num, remainder);
-
+ else
+ {
+ new_num = m_num;
+ new_den = m_den;
+ remainder = 0;
+ }
+ if (new_num.isOverflow() || new_den.isOverflow() || remainder.isOverflow())
+ throw std::overflow_error("Overflow during rounding.");
+ if (new_num.isNan() || new_den.isNan() || remainder.isNan())
+ {
+ throw std::underflow_error("Underflow during rounding.");
+ }
if (remainder.isZero() && !(new_num.isBig() || new_den.isBig()))
{
m_num = new_num;
@@ -255,49 +262,52 @@ GncRational::round (GncDenom& denom) noexcept
if (new_num.isBig() || new_den.isBig())
{
- if (!denom.m_auto)
- {
- m_error = GNC_ERROR_OVERFLOW;
- return;
- }
-
/* First, try to reduce it */
GncInt128 gcd = new_num.gcd(new_den);
+ if (!(gcd.isNan() || gcd.isOverflow()))
+ {
new_num /= gcd;
new_den /= gcd;
remainder /= gcd;
+ }
-/* if that didn't work, shift both num and den down until neither is "big", th
+/* if that didn't work, shift both num and den down until neither is "big", then
* fall through to rounding.
*/
- while (new_num && new_num.isBig() && new_den && new_den.isBig())
+ while (rtype != RoundType::never && new_num && new_num.isBig() &&
+ new_den && new_den.isBig())
{
new_num >>= 1;
new_den >>= 1;
remainder >>= 1;
}
}
-
+ if (remainder == 0)
+ {
+ m_num = new_num;
+ m_den = new_den;
+ return;
+ }
/* If we got here, then we can't exactly represent the rational with
* new_denom. We must either round or punt.
*/
- switch (denom.m_round)
+ switch (rtype)
{
- case GncDenom::RoundType::never:
+ case RoundType::never:
m_error = GNC_ERROR_REMAINDER;
return;
- case GncDenom::RoundType::floor:
+ case RoundType::floor:
if (new_num.isNeg()) ++new_num;
break;
- case GncDenom::RoundType::ceiling:
+ case RoundType::ceiling:
if (! new_num.isNeg()) ++new_num;
break;
- case GncDenom::RoundType::truncate:
+ case RoundType::truncate:
break;
- case GncDenom::RoundType::promote:
+ case RoundType::promote:
new_num += new_num.isNeg() ? -1 : 1;
break;
- case GncDenom::RoundType::half_down:
+ case RoundType::half_down:
if (new_den.isNeg())
{
if (remainder * 2 > m_den * new_den)
@@ -306,7 +316,7 @@ GncRational::round (GncDenom& denom) noexcept
else if (remainder * 2 > m_den)
new_num += new_num.isNeg() ? -1 : 1;
break;
- case GncDenom::RoundType::half_up:
+ case RoundType::half_up:
if (new_den.isNeg())
{
if (remainder * 2 >= m_den * new_den)
@@ -315,7 +325,7 @@ GncRational::round (GncDenom& denom) noexcept
else if (remainder * 2 >= m_den)
new_num += new_num.isNeg() ? -1 : 1;
break;
- case GncDenom::RoundType::bankers:
+ case RoundType::bankers:
if (new_den.isNeg())
{
if (remainder * 2 > m_den * -new_den ||
@@ -366,7 +376,7 @@ GncRational::round_to_numeric() const
auto divisor = static_cast<int64_t>(m_den / (m_num.abs() >> 62));
GncDenom gnc_denom(new_rational, scratch, divisor,
GNC_HOW_RND_ROUND_HALF_DOWN);
- new_rational.round(gnc_denom);
+ new_rational.round(gnc_denom.get(), gnc_denom.m_round);
return new_rational;
}
auto quot(m_den / m_num);
@@ -387,15 +397,15 @@ GncRational::round_to_numeric() const
auto int_div = static_cast<int64_t>(m_den / divisor);
GncDenom gnc_denom(new_rational, scratch, int_div,
GNC_HOW_RND_ROUND_HALF_DOWN);
- new_rational.round(gnc_denom);
+ new_rational.round(gnc_denom.get(), gnc_denom.m_round);
return new_rational;
}
GncDenom::GncDenom (GncRational& a, GncRational& b,
int64_t spec, unsigned int how) noexcept :
m_value (spec),
- m_round (static_cast<GncDenom::RoundType>(how & GNC_NUMERIC_RND_MASK)),
- m_type (static_cast<GncDenom::DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
+ m_round (static_cast<RoundType>(how & GNC_NUMERIC_RND_MASK)),
+ m_type (static_cast<DenomType>(how & GNC_NUMERIC_DENOM_MASK)),
m_auto (spec == GNC_DENOM_AUTO),
m_sigfigs ((how & GNC_NUMERIC_SIGFIGS_MASK) >> 8),
m_error (GNC_ERROR_OK)
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 7c59ce5..fe363f5 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -28,6 +28,28 @@
struct GncDenom;
+enum class RoundType
+{
+ floor = GNC_HOW_RND_FLOOR,
+ ceiling = GNC_HOW_RND_CEIL,
+ truncate = GNC_HOW_RND_TRUNC,
+ promote = GNC_HOW_RND_PROMOTE,
+ half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
+ half_up = GNC_HOW_RND_ROUND_HALF_UP,
+ bankers = GNC_HOW_RND_ROUND,
+ never = GNC_HOW_RND_NEVER,
+};
+
+enum class DenomType
+{
+ den_auto = GNC_DENOM_AUTO,
+ exact = GNC_HOW_DENOM_EXACT,
+ reduce = GNC_HOW_DENOM_REDUCE,
+ lcd = GNC_HOW_DENOM_LCD,
+ fixed = GNC_HOW_DENOM_FIXED,
+ sigfigs = GNC_HOW_DENOM_SIGFIG,
+};
+
/** @ingroup QOF
* @brief Rational number class using GncInt128 for the numerator and denominator.
*/
@@ -62,14 +84,14 @@ public:
/** Round/convert this to the denominator provided by d, according to d's
* m_round value.
*/
- void round (GncDenom& d) noexcept;
/* These are mutators; in other words, they implement the equivalent of
* operators *=, /=, +=, and -=. They return a reference to this for chaining.
*/
- GncRational& mul(const GncRational& b, GncDenom& d) noexcept;
- GncRational& div(GncRational b, GncDenom& d) noexcept;
- GncRational& add(const GncRational& b, GncDenom& d) noexcept;
- GncRational& sub(const GncRational& b, GncDenom& d) noexcept;
+ GncRational& mul(const GncRational& b, GncDenom& d);
+ GncRational& div(GncRational b, GncDenom& d);
+ GncRational& add(const GncRational& b, GncDenom& d);
+ GncRational& sub(const GncRational& b, GncDenom& d);
+ void round (GncInt128 new_den, RoundType rtype);
void operator+=(GncRational b);
void operator-=(GncRational b);
void operator*=(GncRational b);
@@ -95,26 +117,6 @@ struct GncDenom
void reduce (const GncRational& a) noexcept;
GncInt128 get () const noexcept { return m_value; }
- enum class RoundType : int
- {
- floor = GNC_HOW_RND_FLOOR,
- ceiling = GNC_HOW_RND_CEIL,
- truncate = GNC_HOW_RND_TRUNC,
- promote = GNC_HOW_RND_PROMOTE,
- half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
- half_up = GNC_HOW_RND_ROUND_HALF_UP,
- bankers = GNC_HOW_RND_ROUND,
- never = GNC_HOW_RND_NEVER,
- };
- enum class DenomType : int
- {
- exact = GNC_HOW_DENOM_EXACT,
- reduce = GNC_HOW_DENOM_REDUCE,
- lcd = GNC_HOW_DENOM_LCD,
- fixed = GNC_HOW_DENOM_FIXED,
- sigfigs = GNC_HOW_DENOM_SIGFIG,
- };
-
GncInt128 m_value;
RoundType m_round;
DenomType m_type;
diff --git a/src/libqof/qof/test/test-numeric.cpp b/src/libqof/qof/test/test-numeric.cpp
index 6e6ecfb..04a3359 100644
--- a/src/libqof/qof/test/test-numeric.cpp
+++ b/src/libqof/qof/test/test-numeric.cpp
@@ -792,7 +792,7 @@ check_mult_div (void)
* the overflow is eliminated.
*/
- check_binary_op (gnc_numeric_error (GNC_ERROR_REMAINDER),
+ check_binary_op (gnc_numeric_error (GNC_ERROR_OVERFLOW),
gnc_numeric_div(a, b, GNC_DENOM_AUTO,
GNC_HOW_RND_NEVER | GNC_HOW_DENOM_EXACT),
a, b, "expected %s got %s = %s / %s for div exact");
commit 6f5d628b1258ee846eadb16afc392605371a9f59
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 10:56:10 2017 -0800
Move test-numeric from src/engine to src/libqof/qof.
Itâs testing a file in qof and doesnât need to be in engine just because
it uses some functions from test-engine-stuff.
diff --git a/src/engine/test/CMakeLists.txt b/src/engine/test/CMakeLists.txt
index c7682dd..bafbbec 100644
--- a/src/engine/test/CMakeLists.txt
+++ b/src/engine/test/CMakeLists.txt
@@ -27,7 +27,6 @@ ADD_DEPENDENCIES(check test-link)
ADD_ENGINE_TEST(test-load-engine test-load-engine.c)
ADD_ENGINE_TEST(test-guid test-guid.cpp)
-ADD_ENGINE_TEST(test-numeric test-numeric.cpp)
ADD_ENGINE_TEST(test-date test-date.cpp)
ADD_ENGINE_TEST(test-object test-object.c)
ADD_ENGINE_TEST(test-commodities test-commodities.cpp)
diff --git a/src/engine/test/Makefile.am b/src/engine/test/Makefile.am
index 762bfea..caa6b52 100644
--- a/src/engine/test/Makefile.am
+++ b/src/engine/test/Makefile.am
@@ -33,7 +33,6 @@ TEST_GROUP_1 = \
test-link \
test-load-engine \
test-guid \
- test-numeric \
test-date \
test-object \
test-commodities \
@@ -88,7 +87,7 @@ test_commodities_SOURCES = test-commodities.cpp
test_date_SOURCES = test-date.cpp
test_group_vs_book_SOURCES = test-group-vs-book.cpp
test_lots_SOURCES = test-lots.cpp
-test_numeric_SOURCES = test-numeric.cpp
+
test_query_SOURCES = test-query.cpp
test_scm_query_SOURCES = test-scm-query.cpp
test_split_vs_account_SOURCES = test-split-vs-account.cpp
diff --git a/src/libqof/qof/test/CMakeLists.txt b/src/libqof/qof/test/CMakeLists.txt
index 672fdeb..de62f16 100644
--- a/src/libqof/qof/test/CMakeLists.txt
+++ b/src/libqof/qof/test/CMakeLists.txt
@@ -19,6 +19,30 @@ SET(test_qof_SOURCES
${CMAKE_SOURCE_DIR}/src/test-core/unittest-support.c
)
+SET(TEST_ENGINE_INCLUDE_DIRS
+ ${TEST_QOF_INCLUDE_DIRS}
+ ${CMAKE_SOURCE_DIR}/src/engine
+ ${CMAKE_SOURCE_DIR}/src/engine/test-core
+ ${CMAKE_BINARY_DIR}/src # for config.h
+ )
+
+SET(TEST_ENGINE_LIBS
+ ${TEST_QOF_LIBS}
+ gncmod-test-engine
+ )
+
+SET(test_numeric_SOURCES
+ ${CMAKE_SOURCE_DIR}/src/engine/cashobjects.c
+ ${CMAKE_SOURCE_DIR}/src/engine/test-core/test-engine-stuff.cpp
+ ${CMAKE_SOURCE_DIR}/src/libqof/qof/gnc-numeric.cpp
+ ${CMAKE_SOURCE_DIR}/src/libqof/qof/gnc-rational.cpp
+ ${CMAKE_SOURCE_DIR}/src/libqof/qof/gnc-int128.cpp
+ ${CMAKE_SOURCE_DIR}/src/libqof/qof/test/test-numeric.cpp
+)
+
+
+GNC_ADD_TEST(test-numeric "${test_numeric_SOURCES}" TEST_ENGINE_INCLUDE_DIRS TEST_ENGINE_LIBS)
+
# This test does not on Win32. Worse, it causes a dialog box to
# pop up due to an assertion. This interferes with running the tests
# unattended.
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index 2d6b36f..ee82db8 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -25,7 +25,29 @@ test_qof_HEADERS = \
$(top_srcdir)/${MODULEPATH}/qofsession.h \
$(top_srcdir)/src/test-core/unittest-support.h
+test_numeric_SOURCES = \
+ ${top_srcdir}/src/engine/cashobjects.c \
+ ${top_srcdir}/src/test-core/test-stuff.c \
+ ${top_srcdir}/src/engine/test-core/test-engine-stuff.cpp \
+ ${top_srcdir}/${MODULEPATH}/gnc-numeric.cpp \
+ ${top_srcdir}/${MODULEPATH}/gnc-rational.cpp \
+ ${top_srcdir}/${MODULEPATH}/gnc-int128.cpp \
+ ${top_srcdir}/${MODULEPATH}/test/test-numeric.cpp
+
+test_numeric_CPPFLAGS = \
+ -I${top_srcdir}/src/engine \
+ -I${top_srcdir}/src/test-core \
+ -I${top_srcdir}/src/engine/test-core \
+ -I${top_srcdir}/src \
+ -I${top_srcdir}/${MODULEPATH} \
+ ${GLIB_CFLAGS}
+
+test_numeric_LDADD = \
+ ${top_builddir}/src/engine/libgncmod-engine.la \
+ ${GLIB_LIBS}
+
check_PROGRAMS = \
+ test-numeric \
test-qof
TESTS = ${check_PROGRAMS}
diff --git a/src/engine/test/test-numeric.cpp b/src/libqof/qof/test/test-numeric.cpp
similarity index 90%
rename from src/engine/test/test-numeric.cpp
rename to src/libqof/qof/test/test-numeric.cpp
index bb2f74f..6e6ecfb 100644
--- a/src/engine/test/test-numeric.cpp
+++ b/src/libqof/qof/test/test-numeric.cpp
@@ -33,7 +33,7 @@ extern "C"
#include "gnc-numeric.h"
}
-#define NREPS 2000
+#define NREPS 2
static char *
gnc_numeric_print(gnc_numeric in)
@@ -774,30 +774,30 @@ check_mult_div (void)
}
}
- a = gnc_numeric_create(782592055622866ULL, 89025);
- b = gnc_numeric_create(2222554708930978ULL, 85568);
+ a = gnc_numeric_create(INT64_C(1173888083434299), 93773);
+ b = gnc_numeric_create(INT64_C(2222554708930978), 89579);
/* Dividing the above pair overflows, in that after
* the division the denominator won't fit into a
* 64-bit quantity. This can be seen from
- * the factorization int primes:
- * 782592055622866 = 2 * 2283317 * 171371749
+ * the factorization into primes:
+ * 1173888083434299 = 3 * 2283317 * 171371749
* (yes, thats a seven and a nine digit prime)
* 2222554708930978 = 2 * 1111277354465489
* (yes, that's a sixteen-digit prime number)
- * 89025 = 3*5*5*1187
- * 85568= 64*7*191
+ * 93773 = 79*1187
+ * 89579 = 67*7*191
* If the rounding method is exact/no-round, then
* an overflow error should be signalled; else the
* divide routine should shift down the results till
* the overflow is eliminated.
*/
-/* Doesn't overflow any more! */
+
check_binary_op (gnc_numeric_error (GNC_ERROR_REMAINDER),
gnc_numeric_div(a, b, GNC_DENOM_AUTO,
GNC_HOW_RND_NEVER | GNC_HOW_DENOM_EXACT),
a, b, "expected %s got %s = %s / %s for div exact");
- check_binary_op (gnc_numeric_create(338441, 1000000),
+ check_binary_op (gnc_numeric_create(504548, 1000000),
gnc_numeric_div(a, b, GNC_DENOM_AUTO,
GNC_HOW_DENOM_SIGFIGS(6) | GNC_HOW_RND_ROUND),
a, b, "expected %s got %s = %s / %s for div round");
@@ -841,7 +841,7 @@ check_mult_div (void)
val_tot = gnc_numeric_create (-4280656418LL, 19873);
val_a = gnc_numeric_mul (frac, val_tot,
gnc_numeric_denom(val_tot),
- GNC_HOW_RND_ROUND | GNC_HOW_DENOM_EXACT);
+ GNC_HOW_RND_ROUND | GNC_HOW_DENOM_REDUCE);
check_binary_op (gnc_numeric_create(-2939846940LL, 19873),
val_a, val_tot, frac,
"expected %s got %s = %s * %s for mult round");
@@ -850,7 +850,7 @@ check_mult_div (void)
val_tot = gnc_numeric_create (467013515494988LL, 100);
val_a = gnc_numeric_mul (frac, val_tot,
gnc_numeric_denom(val_tot),
- GNC_HOW_RND_ROUND | GNC_HOW_DENOM_EXACT);
+ GNC_HOW_RND_ROUND | GNC_HOW_DENOM_REDUCE);
check_binary_op (gnc_numeric_create(562854124919LL, 100),
val_a, val_tot, frac,
"expected %s got %s = %s * %s for mult round");
@@ -869,81 +869,6 @@ check_mult_div (void)
}
-static void
-check_reciprocal(void)
-{
- gnc_numeric a, b, ans, val;
- double flo;
-
- val = gnc_numeric_create(-60, 20);
- check_unary_op (gnc_numeric_eq, gnc_numeric_create (-3, -1),
- gnc_numeric_convert(val, GNC_DENOM_RECIPROCAL(1),
- GNC_HOW_RND_NEVER),
- val, "expected %s got %s = (%s as RECIP(1))");
-
- a = gnc_numeric_create(200, 100);
- b = gnc_numeric_create(300, 100);
-
- /* 2 + 3 = 5 */
- ans = gnc_numeric_add(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER);
- check_binary_op (gnc_numeric_create(5, -1),
- ans, a, b, "expected %s got %s = %s + %s for reciprocal");
-
- /* 2 + 3 = 5 */
- a = gnc_numeric_create(2, -1);
- b = gnc_numeric_create(300, 100);
- ans = gnc_numeric_add(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER);
- check_binary_op (gnc_numeric_create(5, -1),
- ans, a, b, "expected %s got %s = %s + %s for reciprocal");
-
- /* check gnc_numeric_to_double */
- flo = gnc_numeric_to_double(gnc_numeric_create(5, -1));
- do_test ((5.0 == flo), "reciprocal conversion");
-
- /* check gnc_numeric_compare */
- a = gnc_numeric_create(2, 1);
- b = gnc_numeric_create(2, -1);
- do_test((0 == gnc_numeric_compare(a, b)), " 2 == 2 ");
- a = gnc_numeric_create(2, 1);
- b = gnc_numeric_create(3, -1);
- do_test((-1 == gnc_numeric_compare(a, b)), " 2 < 3 ");
- a = gnc_numeric_create(-2, 1);
- b = gnc_numeric_create(2, -1);
- do_test((-1 == gnc_numeric_compare(a, b)), " -2 < 2 ");
- a = gnc_numeric_create(2, -1);
- b = gnc_numeric_create(3, -1);
- do_test((-1 == gnc_numeric_compare(a, b)), " 2 < 3 ");
-
- /* check for equality */
- a = gnc_numeric_create(2, 1);
- b = gnc_numeric_create(2, -1);
- do_test(gnc_numeric_equal(a, b), " 2 == 2 ");
-
- /* check gnc_numeric_mul */
- a = gnc_numeric_create(2, 1);
- b = gnc_numeric_create(3, -1);
- ans = gnc_numeric_mul(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER);
- check_binary_op (gnc_numeric_create(6, -1),
- ans, a, b, "expected %s got %s = %s * %s for reciprocal");
-
- /* check gnc_numeric_div */
- /* -60 / 20 = -3 */
- a = gnc_numeric_create(-60, 1);
- b = gnc_numeric_create(2, -10);
- ans = gnc_numeric_div(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER);
- check_binary_op (gnc_numeric_create(-3, -1),
- ans, a, b, "expected %s got %s = %s / %s for reciprocal");
-
- /* 60 / 20 = 3 */
- a = gnc_numeric_create(60, 1);
- b = gnc_numeric_create(2, -10);
- ans = gnc_numeric_div(a, b, GNC_DENOM_RECIPROCAL(1), GNC_HOW_RND_NEVER);
- check_binary_op (gnc_numeric_create(3, -1),
- ans, a, b, "expected %s got %s = %s / %s for reciprocal");
-
-
-}
-
/* ======================================================= */
static void
@@ -958,7 +883,6 @@ run_test (void)
check_add_subtract();
check_add_subtract_overflow ();
check_mult_div ();
- check_reciprocal();
}
int
commit b0dfd96a93648cc890d9d89a0754f2567b1b2485
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 10:49:57 2017 -0800
Add GncRational::reduce() and GncRational::round_to_numeric().
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 7e63438..23f4338 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -20,6 +20,7 @@
* *
*******************************************************************/
+#include <sstream>
#include "gnc-rational.hpp"
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
@@ -334,6 +335,62 @@ GncRational::round (GncDenom& denom) noexcept
return;
}
+GncRational
+GncRational::reduce() const
+{
+ auto gcd = m_den.gcd(m_num);
+ if (gcd.isNan() || gcd.isOverflow())
+ throw std::overflow_error("Reduce failed, calculation of gcd overflowed.");
+ return GncRational(m_num / gcd, m_den / gcd);
+}
+
+GncRational
+GncRational::round_to_numeric() const
+{
+ if (m_num.isZero())
+ return GncRational(); //Default constructor makes 0/1
+ if (!(m_num.isBig() || m_den.isBig()))
+ return *this;
+ if (m_num.abs() > m_den)
+ {
+ auto quot(m_num / m_den);
+ if (quot.isBig())
+ {
+ std::ostringstream msg;
+ msg << " Cannot be represented as a "
+ << "GncNumeric. Its integer value is too large.\n";
+ throw std::overflow_error(msg.str());
+ }
+ GncRational new_rational(*this);
+ GncRational scratch(1, 1);
+ auto divisor = static_cast<int64_t>(m_den / (m_num.abs() >> 62));
+ GncDenom gnc_denom(new_rational, scratch, divisor,
+ GNC_HOW_RND_ROUND_HALF_DOWN);
+ new_rational.round(gnc_denom);
+ return new_rational;
+ }
+ auto quot(m_den / m_num);
+ if (quot.isBig())
+ return GncRational(); //Smaller than can be represented as a GncNumeric
+ auto divisor = m_den >> 62;
+ if (m_num.isBig())
+ {
+ GncInt128 oldnum(m_num), num, rem;
+ oldnum.div(divisor, num, rem);
+ auto den = m_den / divisor;
+ num += rem * 2 >= den ? 1 : 0;
+ GncRational new_rational(num, den);
+ return new_rational;
+ }
+ GncRational new_rational(*this);
+ GncRational scratch(1, 1);
+ auto int_div = static_cast<int64_t>(m_den / divisor);
+ GncDenom gnc_denom(new_rational, scratch, int_div,
+ GNC_HOW_RND_ROUND_HALF_DOWN);
+ new_rational.round(gnc_denom);
+ return new_rational;
+}
+
GncDenom::GncDenom (GncRational& a, GncRational& b,
int64_t spec, unsigned int how) noexcept :
m_value (spec),
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index affe6b2..7c59ce5 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -45,6 +45,20 @@ public:
operator gnc_numeric() const noexcept;
/** Make a new GncRational with the opposite sign. */
GncRational operator-() const noexcept;
+/**
+ * Reduce this to an equivalent fraction with the least common multiple as the
+ * denominator.
+ *
+ * @return reduced GncRational
+ */
+ GncRational reduce() const;
+/**
+ * Round to fit an int64_t, finding the closest possible approximation.
+ *
+ * Throws std::overflow_error if m_den is 1 and m_num is big.
+ * @return rounded GncRational
+ */
+ GncRational round_to_numeric() const;
/** Round/convert this to the denominator provided by d, according to d's
* m_round value.
*/
commit 340fb9761ca3c7de7c69bb55a7ce980633fb8013
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 10:37:45 2017 -0800
Fix inverted subtraction.
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 8045c95..7e63438 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -103,7 +103,7 @@ operator+(GncRational a, GncRational b)
GncRational
operator-(GncRational a, GncRational b)
{
- GncRational retval = -a + b;
+ GncRational retval = a + (-b);
return retval;
}
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index d9e553a..9179d72 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -87,18 +87,18 @@ TEST(gncrational_operators, test_subtraction)
GncRational a(123456789987654321, 1000000000);
GncRational b(65432198765432198, 100000000);
GncRational c = a - b;
- EXPECT_EQ (530865197666667659, c.m_num);
- EXPECT_FALSE(c.m_num.isNeg());
+ EXPECT_EQ (-530865197666667659, c.m_num);
+ EXPECT_TRUE(c.m_num.isNeg());
EXPECT_EQ (1000000000, c.m_den);
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
c = b - a;
- EXPECT_EQ (-530865197666667659, c.m_num);
- EXPECT_TRUE(c.m_num.isNeg());
+ EXPECT_EQ (530865197666667659, c.m_num);
+ EXPECT_FALSE(c.m_num.isNeg());
EXPECT_EQ (1000000000, c.m_den);
EXPECT_EQ (GNC_ERROR_OK, c.m_error);
a -= b;
- EXPECT_EQ (530865197666667659, a.m_num);
- EXPECT_FALSE(a.m_num.isNeg());
+ EXPECT_EQ (-530865197666667659, a.m_num);
+ EXPECT_TRUE(a.m_num.isNeg());
EXPECT_EQ (1000000000, a.m_den);
EXPECT_EQ (GNC_ERROR_OK, a.m_error);
}
commit a88d21245e348a5a49128c6f435c37ad72851597
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 30 10:37:15 2017 -0800
Add guard macro to gnu-rational.hpp.
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index ceba87d..affe6b2 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -19,8 +19,12 @@
* Boston, MA 02110-1301, USA gnu at gnu.org *
* *
*******************************************************************/
-#include "gnc-int128.hpp"
+
+#ifndef __GNC_RATIONAL_HPP__
+#define __GNC_RATIONAL_HPP__
+
#include "gnc-numeric.h"
+#include "gnc-int128.hpp"
struct GncDenom;
@@ -104,3 +108,4 @@ struct GncDenom
unsigned int m_sigfigs;
GNCNumericErrorCode m_error;
};
+#endif //__GNC_RATIONAL_HPP__
commit e1b280b36bd0a3f4834471ee776d0e368f437aa4
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 16 10:12:28 2017 -0800
Untabify gnu-numeric.cpp.
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index 3c364b3..4d8d45b 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -44,20 +44,20 @@ extern "C"
using GncNumeric = GncRational;
static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
- 10000000, 100000000, 1000000000,
- INT64_C(10000000000), INT64_C(100000000000),
- INT64_C(1000000000000), INT64_C(10000000000000),
- INT64_C(100000000000000),
- INT64_C(10000000000000000),
- INT64_C(100000000000000000),
- INT64_C(1000000000000000000)};
+ 10000000, 100000000, 1000000000,
+ INT64_C(10000000000), INT64_C(100000000000),
+ INT64_C(1000000000000), INT64_C(10000000000000),
+ INT64_C(100000000000000),
+ INT64_C(10000000000000000),
+ INT64_C(100000000000000000),
+ INT64_C(1000000000000000000)};
#define POWTEN_OVERFLOW -5
static inline gint64
powten (int exp)
{
if (exp > 18 || exp < -18)
- return POWTEN_OVERFLOW;
+ return POWTEN_OVERFLOW;
return exp < 0 ? -pten[-exp] : pten[exp];
}
@@ -507,7 +507,7 @@ double_to_gnc_numeric(double in, gint64 denom, gint how)
double sigfigs;
if (isnan (in) || fabs (in) > 1e18)
- return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
if ((denom == GNC_DENOM_AUTO) && (how & GNC_HOW_DENOM_SIGFIG))
{
@@ -522,7 +522,7 @@ double_to_gnc_numeric(double in, gint64 denom, gint how)
(floor(logval) + 1.0) : (ceil(logval)));
}
sigfigs = GNC_HOW_GET_SIGFIGS(how);
- if ((denom = powten (sigfigs - logval)) == POWTEN_OVERFLOW)
+ if ((denom = powten (sigfigs - logval)) == POWTEN_OVERFLOW)
return gnc_numeric_error(GNC_ERROR_OVERFLOW);
how = how & ~GNC_HOW_DENOM_SIGFIG & ~GNC_NUMERIC_SIGFIGS_MASK;
commit b1995932fc2b5c6439b142b01068860fd522fcc2
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jan 16 09:38:45 2017 -0800
Remove unused gnc_numeric_foo_with_error functions.
diff --git a/src/engine/test/test-numeric.cpp b/src/engine/test/test-numeric.cpp
index 7bbb9c4..bb2f74f 100644
--- a/src/engine/test/test-numeric.cpp
+++ b/src/engine/test/test-numeric.cpp
@@ -401,9 +401,6 @@ check_add_subtract (void)
{
int i;
gnc_numeric a, b, c, d, z;
-#if CHECK_ERRORS_TOO
- gnc_numeric c;
-#endif
a = gnc_numeric_create(2, 6);
b = gnc_numeric_create(1, 4);
@@ -500,22 +497,6 @@ check_add_subtract (void)
a, b, "expected %s got %s = %s - %s for sub 100ths (banker's)");
/* ------------------------------------------------------------ */
-#if CHECK_ERRORS_TOO
- c = gnc_numeric_add_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
- printf("add 100ths/error : %s + %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
- c = gnc_numeric_sub_with_error(a, b, 100, GNC_HOW_RND_FLOOR, &err);
- printf("sub 100ths/error : %s - %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
-#endif
-
- /* ------------------------------------------------------------ */
/* Add and subtract some random numbers */
for (i = 0; i < NREPS; i++)
{
@@ -722,22 +703,6 @@ check_mult_div (void)
gnc_numeric_div(a, b, 100, GNC_HOW_RND_ROUND),
a, b, "expected %s got %s = %s * %s for div 100th's");
-#if CHECK_ERRORS_TOO
- gnc_numeric c;
- c = gnc_numeric_mul_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
- printf("mul 100ths/error : %s * %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
- c = gnc_numeric_div_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
- printf("div 100ths/error : %s / %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
-#endif
-
/* Check for math with 2^63 < num*num < 2^64 which previously failed
* see http://bugzilla.gnome.org/show_bug.cgi?id=144980
*/
diff --git a/src/libqof/qof/gnc-numeric.cpp b/src/libqof/qof/gnc-numeric.cpp
index f9adbdf..3c364b3 100644
--- a/src/libqof/qof/gnc-numeric.cpp
+++ b/src/libqof/qof/gnc-numeric.cpp
@@ -595,93 +595,6 @@ gnc_numeric_error(GNCNumericErrorCode error_code)
}
-/* *******************************************************************
- * gnc_numeric_add_with_error
- ********************************************************************/
-
-gnc_numeric
-gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error)
-{
-
- gnc_numeric sum = gnc_numeric_add(a, b, denom, how);
- gnc_numeric exact = gnc_numeric_add(a, b, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
- gnc_numeric err = gnc_numeric_sub(sum, exact, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
-
- if (error)
- {
- *error = err;
- }
- return sum;
-}
-
-/* *******************************************************************
- * gnc_numeric_sub_with_error
- ********************************************************************/
-
-gnc_numeric
-gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error)
-{
- gnc_numeric diff = gnc_numeric_sub(a, b, denom, how);
- gnc_numeric exact = gnc_numeric_sub(a, b, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
- gnc_numeric err = gnc_numeric_sub(diff, exact, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
- if (error)
- {
- *error = err;
- }
- return diff;
-}
-
-
-/* *******************************************************************
- * gnc_numeric_mul_with_error
- ********************************************************************/
-
-gnc_numeric
-gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error)
-{
- gnc_numeric prod = gnc_numeric_mul(a, b, denom, how);
- gnc_numeric exact = gnc_numeric_mul(a, b, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
- gnc_numeric err = gnc_numeric_sub(prod, exact, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
- if (error)
- {
- *error = err;
- }
- return prod;
-}
-
-
-/* *******************************************************************
- * gnc_numeric_div_with_error
- ********************************************************************/
-
-gnc_numeric
-gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error)
-{
- gnc_numeric quot = gnc_numeric_div(a, b, denom, how);
- gnc_numeric exact = gnc_numeric_div(a, b, GNC_DENOM_AUTO,
- GNC_HOW_DENOM_REDUCE);
- gnc_numeric err = gnc_numeric_sub(quot, exact,
- GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
- if (error)
- {
- *error = err;
- }
- return quot;
-}
/* *******************************************************************
* gnc_numeric text IO
@@ -802,29 +715,6 @@ main(int argc, char ** argv)
gnc_numeric err;
- c = gnc_numeric_add_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
- printf("add 100ths/error : %s + %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
- c = gnc_numeric_sub_with_error(a, b, 100, GNC_HOW_RND_FLOOR, &err);
- printf("sub 100ths/error : %s - %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
- c = gnc_numeric_mul_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
- printf("mul 100ths/error : %s * %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
-
- c = gnc_numeric_div_with_error(a, b, 100, GNC_HOW_RND_ROUND, &err);
- printf("div 100ths/error : %s / %s = %s + (error) %s\n\n",
- gnc_numeric_print(a), gnc_numeric_print(b),
- gnc_numeric_print(c),
- gnc_numeric_print(err));
printf("multiply (EXACT): %s * %s = %s\n",
gnc_numeric_print(a), gnc_numeric_print(b),
diff --git a/src/libqof/qof/gnc-numeric.h b/src/libqof/qof/gnc-numeric.h
index db71f6b..c67cba7 100644
--- a/src/libqof/qof/gnc-numeric.h
+++ b/src/libqof/qof/gnc-numeric.h
@@ -450,35 +450,6 @@ gnc_numeric gnc_numeric_sub_fixed(gnc_numeric a, gnc_numeric b)
}
/** @} */
-/** @name Arithmetic Functions with Exact Error Returns
- @{
-*/
-/** The same as gnc_numeric_add, but uses 'error' for accumulating
- * conversion roundoff error. */
-gnc_numeric gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error);
-
-/** The same as gnc_numeric_sub, but uses error for accumulating
- * conversion roundoff error. */
-gnc_numeric gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error);
-
-/** The same as gnc_numeric_mul, but uses error for
- * accumulating conversion roundoff error.
- */
-gnc_numeric gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error);
-
-/** The same as gnc_numeric_div, but uses error for
- * accumulating conversion roundoff error.
- */
-gnc_numeric gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
- gint64 denom, gint how,
- gnc_numeric * error);
-/** @} */
/** @name Change Denominator
@{
commit a852dfb4eff74594acb55b10c0ba2fe6bc6f9f7e
Author: John Ralls <jralls at ceridwen.us>
Date: Sun Jan 15 12:33:31 2017 -0800
Implement basic arithmetic operators for GncRational.
The operators do no rounding or reducing yet.
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index 49ecde6..8045c95 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -83,57 +83,122 @@ GncRational::inv () noexcept
return *this;
}
-GncRational&
-GncRational::mul (const GncRational& b, GncDenom& d) noexcept
+GncRational
+operator+(GncRational a, GncRational b)
{
- if (m_error || b.m_error)
+ if (a.m_error || b.m_error)
{
if (b.m_error)
- m_error = b.m_error;
- return *this;
+ return GncRational(0, 1, b.m_error);
+ return GncRational(0, 1, a.m_error);
}
- m_num *= b.m_num;
- m_den *= b.m_den;
- round (d);
- return *this;
+ GncInt128 lcm = a.m_den.lcm(b.m_den);
+ GncInt128 num(a.m_num * lcm / a.m_den + b.m_num * lcm / b.m_den);
+ if (lcm.isOverflow() || lcm.isNan() || num.isOverflow() || num.isNan())
+ return GncRational(0, 1, GNC_ERROR_OVERFLOW);
+ GncRational retval(num, lcm);
+ return retval;
}
-GncRational&
-GncRational::div (GncRational b, GncDenom& d) noexcept
+GncRational
+operator-(GncRational a, GncRational b)
+{
+ GncRational retval = -a + b;
+ return retval;
+}
+
+GncRational
+operator*(GncRational a, GncRational b)
{
- if (m_error || b.m_error)
+ if (a.m_error || b.m_error)
{
if (b.m_error)
- m_error = b.m_error;
- return *this;
+ return GncRational(0, 1, b.m_error);
+ return GncRational(0, 1, a.m_error);
}
+ GncInt128 num (a.m_num * b.m_num), den(a.m_den * b.m_den);
+ if (num.isOverflow() || num.isNan() || den.isOverflow() || den.isNan())
+ return GncRational(0, 1, GNC_ERROR_OVERFLOW);
+ GncRational retval(num, den);
+ return retval;
+}
- if (b.m_num.isNeg())
+GncRational
+operator/(GncRational a, GncRational b)
+{
+ if (a.m_error || b.m_error)
{
- m_num = -m_num;
+ if (b.m_error)
+ return GncRational(0, 1, b.m_error);
+ return GncRational(0, 1, a.m_error);
+ }
+ if (b.m_num.isNeg())
+ {
+ a.m_num = -a.m_num;
b.m_num = -b.m_num;
}
/* q = (a_num * b_den)/(b_num * a_den). If a_den == b_den they cancel out
* and it's just a_num/b_num.
*/
- if (m_den == b.m_den)
- {
- m_den = b.m_num;
- round(d);
- return *this;
- }
+ if (a.m_den == b.m_den)
+ return GncRational(a.m_num, b.m_num);
+
/* Protect against possibly preventable overflow: */
- if (m_num.isBig() || m_den.isBig() ||
+ if (a.m_num.isBig() || a.m_den.isBig() ||
b.m_num.isBig() || b.m_den.isBig())
{
- GncInt128 gcd = b.m_den.gcd(m_den);
+ GncInt128 gcd = b.m_den.gcd(a.m_den);
b.m_den /= gcd;
- m_den /= gcd;
+ a.m_den /= gcd;
}
- m_num *= b.m_den;
- m_den *= b.m_num;
+ GncInt128 num(a.m_num * b.m_den), den(a.m_den * b.m_num);
+ if (num.isOverflow() || num.isNan() || den.isOverflow() || den.isNan())
+ return GncRational(0, 1, GNC_ERROR_OVERFLOW);
+ return GncRational(num, den);
+}
+
+void
+GncRational::operator+=(GncRational b)
+{
+ GncRational new_val = *this + b;
+ *this = std::move(new_val);
+}
+
+void
+GncRational::operator-=(GncRational b)
+{
+ GncRational new_val = *this - b;
+ *this = std::move(new_val);
+}
+
+void
+GncRational::operator*=(GncRational b)
+{
+ GncRational new_val = *this * b;
+ *this = std::move(new_val);
+}
+
+void
+GncRational::operator/=(GncRational b)
+{
+ GncRational new_val = *this / b;
+ *this = std::move(new_val);
+}
+
+GncRational&
+GncRational::mul (const GncRational& b, GncDenom& d) noexcept
+{
+ *this *= b;
+ round (d);
+ return *this;
+}
+
+GncRational&
+GncRational::div (GncRational b, GncDenom& d) noexcept
+{
+ *this /= b;
round (d);
return *this;
}
@@ -141,15 +206,7 @@ GncRational::div (GncRational b, GncDenom& d) noexcept
GncRational&
GncRational::add (const GncRational& b, GncDenom& d) noexcept
{
- if (m_error || b.m_error)
- {
- if (b.m_error)
- m_error = b.m_error;
- return *this;
- }
- GncInt128 lcm = m_den.lcm (b.m_den);
- m_num = m_num * lcm / m_den + b.m_num * lcm / b.m_den;
- m_den = lcm;
+ *this += b;
round (d);
return *this;
}
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 1772fb9..ceba87d 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -52,7 +52,10 @@ public:
GncRational& div(GncRational b, GncDenom& d) noexcept;
GncRational& add(const GncRational& b, GncDenom& d) noexcept;
GncRational& sub(const GncRational& b, GncDenom& d) noexcept;
-
+ void operator+=(GncRational b);
+ void operator-=(GncRational b);
+ void operator*=(GncRational b);
+ void operator/=(GncRational b);
/** Inverts the number, equivalent of /= {1, 1} */
GncRational& inv() noexcept;
@@ -61,6 +64,12 @@ public:
GNCNumericErrorCode m_error;
};
+GncRational operator+(GncRational a, GncRational b);
+GncRational operator-(GncRational a, GncRational b);
+GncRational operator*(GncRational a, GncRational b);
+GncRational operator/(GncRational a, GncRational b);
+
+
/** Encapsulates the rounding specifications computations. */
struct GncDenom
{
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 4005819..d9e553a 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -67,4 +67,74 @@ TEST(gncrational_constructors, test_with_error_code)
EXPECT_EQ(456, value.m_den);
EXPECT_EQ(GNC_ERROR_OVERFLOW, value.m_error);
}
+
+TEST(gncrational_operators, test_addition)
+{
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a + b;
+ EXPECT_EQ (777778777641976301, c.m_num);
+ EXPECT_EQ (1000000000, c.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, c.m_error);
+ a += b;
+ EXPECT_EQ (777778777641976301, a.m_num);
+ EXPECT_EQ (1000000000, a.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, a.m_error);
+}
+
+TEST(gncrational_operators, test_subtraction)
+{
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a - b;
+ EXPECT_EQ (530865197666667659, c.m_num);
+ EXPECT_FALSE(c.m_num.isNeg());
+ EXPECT_EQ (1000000000, c.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, c.m_error);
+ c = b - a;
+ EXPECT_EQ (-530865197666667659, c.m_num);
+ EXPECT_TRUE(c.m_num.isNeg());
+ EXPECT_EQ (1000000000, c.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, c.m_error);
+ a -= b;
+ EXPECT_EQ (530865197666667659, a.m_num);
+ EXPECT_FALSE(a.m_num.isNeg());
+ EXPECT_EQ (1000000000, a.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, a.m_error);
+}
+
+TEST(gncrational_operators, test_multiplication)
+{
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a * b;
+ EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
+ UINT64_C(8081008345983448486)), c.m_num);
+ EXPECT_EQ (100000000000000000, c.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, c.m_error);
+ a *= b;
+ EXPECT_EQ (GncInt128(UINT64_C(437911925765117),
+ UINT64_C(8081008345983448486)), a.m_num);
+ EXPECT_EQ (100000000000000000, a.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, a.m_error);
+}
+
+TEST(gncrational_operators, test_division)
+{
+ GncRational a(123456789987654321, 1000000000);
+ GncRational b(65432198765432198, 100000000);
+ GncRational c = a / b;
+ EXPECT_EQ (GncInt128(UINT64_C(669260), UINT64_C(11059994577585475840)),
+ c.m_num);
+ EXPECT_EQ (GncInt128(UINT64_C(3547086), UINT64_C(11115994079396609024)),
+ c.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, c.m_error);
+
+ a /= b;
+ EXPECT_EQ (GncInt128(UINT64_C(669260), UINT64_C(11059994577585475840)),
+ a.m_num);
+ EXPECT_EQ (GncInt128(UINT64_C(3547086), UINT64_C(11115994079396609024)),
+ a.m_den);
+ EXPECT_EQ (GNC_ERROR_OK, c.m_error);
+
}
commit d9aa5e1ad5d70c12ec43d89860da262299b8e035
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Jan 14 17:25:31 2017 -0800
Reorder test parameters so that the expected value is first.
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 4c6baf8..4005819 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -45,25 +45,26 @@ TEST(gncrational_constructors, test_gnc_int128_constructor)
{
GncInt128 num(123), denom(456);
GncRational value(num, denom);
- EXPECT_EQ(value.m_num, 123);
- EXPECT_EQ(value.m_den, 456);
- EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+ EXPECT_EQ(123, value.m_num);
+ EXPECT_EQ(456, value.m_den);
+ EXPECT_EQ(GNC_ERROR_OK, value.m_error);
}
TEST(gncrational_constructors, test_implicit_int_constructor)
{
int num(123), denom(456);
GncRational value(num, denom);
- EXPECT_EQ(value.m_num, 123);
- EXPECT_EQ(value.m_den, 456);
- EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+ EXPECT_EQ(123, value.m_num);
+ EXPECT_EQ(456, value.m_den);
+ EXPECT_EQ(GNC_ERROR_OK, value.m_error);
}
TEST(gncrational_constructors, test_with_error_code)
{
int num(123), denom(456);
GncRational value(num, denom, GNC_ERROR_OVERFLOW);
- EXPECT_EQ(value.m_num, 123);
- EXPECT_EQ(value.m_den, 456);
- EXPECT_EQ(value.m_error, GNC_ERROR_OVERFLOW);
+ EXPECT_EQ(123, value.m_num);
+ EXPECT_EQ(456, value.m_den);
+ EXPECT_EQ(GNC_ERROR_OVERFLOW, value.m_error);
+}
}
commit b5f06ab6dcbc14d67ef3b203bb72881a7b4c1c2d
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Jan 14 14:59:13 2017 -0800
Add an error parameter on the GncInt128 constructor.
diff --git a/src/libqof/qof/gnc-rational.cpp b/src/libqof/qof/gnc-rational.cpp
index a3db422..49ecde6 100644
--- a/src/libqof/qof/gnc-rational.cpp
+++ b/src/libqof/qof/gnc-rational.cpp
@@ -47,11 +47,6 @@ GncRational::GncRational (gnc_numeric n) noexcept :
}
}
-GncRational::GncRational (GncInt128 num, GncInt128 den) noexcept :
- m_num (num), m_den (den), m_error {}
-{
-}
-
GncRational::operator gnc_numeric () const noexcept
{
if (m_num.isOverflow() || m_num.isNan() ||
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 0d22c65..1772fb9 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -33,7 +33,10 @@ class GncRational
public:
GncRational() : m_num(0), m_den(1), m_error(GNC_ERROR_OK) {}
GncRational (gnc_numeric n) noexcept;
- GncRational (GncInt128 num, GncInt128 den) noexcept;
+ GncRational (GncInt128 num, GncInt128 den,
+ GNCNumericErrorCode err=GNC_ERROR_OK) noexcept
+ : m_num(num), m_den(den), m_error(err) {}
+
/** Conversion operator; use static_cast<gnc_numeric>(foo). */
operator gnc_numeric() const noexcept;
/** Make a new GncRational with the opposite sign. */
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
index 7a749cf..4c6baf8 100644
--- a/src/libqof/qof/test/gtest-gnc-rational.cpp
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -58,3 +58,12 @@ TEST(gncrational_constructors, test_implicit_int_constructor)
EXPECT_EQ(value.m_den, 456);
EXPECT_EQ(value.m_error, GNC_ERROR_OK);
}
+
+TEST(gncrational_constructors, test_with_error_code)
+{
+ int num(123), denom(456);
+ GncRational value(num, denom, GNC_ERROR_OVERFLOW);
+ EXPECT_EQ(value.m_num, 123);
+ EXPECT_EQ(value.m_den, 456);
+ EXPECT_EQ(value.m_error, GNC_ERROR_OVERFLOW);
+}
commit 43fbb338af1cc56f2ab9588282d8f3271dbe8e28
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Jan 14 14:34:30 2017 -0800
Add GTest test program for GncRational.
Starting off with the constructors.
diff --git a/src/libqof/qof/test/CMakeLists.txt b/src/libqof/qof/test/CMakeLists.txt
index 47834e4..672fdeb 100644
--- a/src/libqof/qof/test/CMakeLists.txt
+++ b/src/libqof/qof/test/CMakeLists.txt
@@ -61,6 +61,13 @@ IF (NOT WIN32)
GNC_ADD_TEST(test-gnc-int128 "${test_gnc_int128_SOURCES}"
gtest_qof_INCLUDES gtest_qof_LIBS)
+ SET(test_gnc_rational_SOURCES
+ ${MODULEPATH}/gnc-rational.cpp
+ gtest-gnc-rational.cpp
+ ${GTEST_SRC})
+ GNC_ADD_TEST(test-gnc-rational "${test_gnc_rational_SOURCES}"
+ gtest_qof_INCLUDES gtest_qof_LIBS)
+
SET(test_gnc_timezone_SOURCES
${MODULEPATH}/gnc-timezone.cpp
gtest-gnc-timezone.cpp
diff --git a/src/libqof/qof/test/Makefile.am b/src/libqof/qof/test/Makefile.am
index 5e2a70e..2d6b36f 100644
--- a/src/libqof/qof/test/Makefile.am
+++ b/src/libqof/qof/test/Makefile.am
@@ -108,6 +108,18 @@ nodist_test_gnc_int128_SOURCES = \
endif
check_PROGRAMS += test-gnc-int128
+test_gnc_rational_SOURCES = \
+ $(top_srcdir)/${MODULEPATH}/gnc-rational.cpp \
+ gtest-gnc-rational.cpp
+test_gnc_rational_CPPFLAGS = -I${GTEST_HEADERS}
+
+test_gnc_rational_LDADD = ${GTEST_LIBS}
+if !GOOGLE_TEST_LIBS
+nodist_test_gnc_rational_SOURCES = \
+ ${GTEST_SRC}/src/gtest_main.cc
+endif
+check_PROGRAMS += test-gnc-rational
+
test_gnc_timezone_SOURCES = \
$(top_srcdir)/${MODULEPATH}/gnc-timezone.cpp \
gtest-gnc-timezone.cpp
diff --git a/src/libqof/qof/test/gtest-gnc-rational.cpp b/src/libqof/qof/test/gtest-gnc-rational.cpp
new file mode 100644
index 0000000..7a749cf
--- /dev/null
+++ b/src/libqof/qof/test/gtest-gnc-rational.cpp
@@ -0,0 +1,60 @@
+/********************************************************************
+ * Gtest-gnc-rational.cpp -- unit tests for the GncInt128 class *
+ * Copyright (C) 2017 John Ralls <jralls at ceridwen.us> *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License, or (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License*
+ * along with this program; if not, contact: *
+ * *
+ * Free Software Foundation Voice: +1-617-542-5942 *
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
+ * Boston, MA 02110-1301, USA gnu at gnu.org *
+ * *
+ *******************************************************************/
+
+#include <gtest/gtest.h>
+#include "../gnc-rational.hpp"
+
+TEST(gncrational_constructors, test_default_constructor)
+{
+ GncRational value;
+ EXPECT_EQ(value.m_num, 0);
+ EXPECT_EQ(value.m_den, 1);
+ EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+}
+
+TEST(gncrational_constructors, test_gnc_numeric_constructor)
+{
+ gnc_numeric input = gnc_numeric_create(123, 456);
+ GncRational value(input);
+ EXPECT_EQ(input.num, value.m_num);
+ EXPECT_EQ(input.denom, value.m_den);
+ EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+}
+
+TEST(gncrational_constructors, test_gnc_int128_constructor)
+{
+ GncInt128 num(123), denom(456);
+ GncRational value(num, denom);
+ EXPECT_EQ(value.m_num, 123);
+ EXPECT_EQ(value.m_den, 456);
+ EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+}
+
+TEST(gncrational_constructors, test_implicit_int_constructor)
+{
+ int num(123), denom(456);
+ GncRational value(num, denom);
+ EXPECT_EQ(value.m_num, 123);
+ EXPECT_EQ(value.m_den, 456);
+ EXPECT_EQ(value.m_error, GNC_ERROR_OK);
+}
commit 848f77dacfe99592504aaa5bd412bd448b6c8c7b
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Jan 14 14:33:47 2017 -0800
Add explicit and correct default GncRational constructor.
As suggested by Geert Janssens.
diff --git a/src/libqof/qof/gnc-rational.hpp b/src/libqof/qof/gnc-rational.hpp
index 2e04fd1..0d22c65 100644
--- a/src/libqof/qof/gnc-rational.hpp
+++ b/src/libqof/qof/gnc-rational.hpp
@@ -31,6 +31,7 @@ struct GncDenom;
class GncRational
{
public:
+ GncRational() : m_num(0), m_den(1), m_error(GNC_ERROR_OK) {}
GncRational (gnc_numeric n) noexcept;
GncRational (GncInt128 num, GncInt128 den) noexcept;
/** Conversion operator; use static_cast<gnc_numeric>(foo). */
commit b30a547d18f08ff16268fb92d1f8545add5eb7cd
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Feb 20 15:50:14 2017 -0800
Add ICU libraries on which Boost:locale is dependent to CSV-Import LINK_LIBRARIES.
diff --git a/src/import-export/csv-imp/CMakeLists.txt b/src/import-export/csv-imp/CMakeLists.txt
index daeeef0..46690e6 100644
--- a/src/import-export/csv-imp/CMakeLists.txt
+++ b/src/import-export/csv-imp/CMakeLists.txt
@@ -45,8 +45,16 @@ SET(csv_import_noinst_HEADERS
ADD_LIBRARY(gncmod-csv-import ${csv_import_noinst_HEADERS} ${csv_import_SOURCES})
-TARGET_LINK_LIBRARIES(gncmod-csv-import ${Boost_LIBRARIES} gncmod-generic-import gncmod-gnome-utils
- gncmod-app-utils gncmod-engine gnc-core-utils gnc-module)
+TARGET_LINK_LIBRARIES(
+ gncmod-csv-import
+ ${Boost_LIBRARIES}
+ gncmod-generic-import
+ gncmod-gnome-utils
+ gncmod-app-utils
+ gncmod-engine
+ gnc-core-utils
+ gnc-module
+ icuuc icui18n icudata)
TARGET_COMPILE_DEFINITIONS(gncmod-csv-import PRIVATE -DG_LOG_DOMAIN=\"gnc.import.csv\")
Summary of changes:
src/app-utils/gnc-ui-util.c | 3 +-
src/app-utils/test/test-print-parse-amount.cpp | 34 +-
src/engine/test/CMakeLists.txt | 1 -
src/engine/test/Makefile.am | 3 +-
src/import-export/csv-imp/CMakeLists.txt | 12 +-
src/libqof/CMakeLists.txt | 8 +-
src/libqof/qof/Makefile.am | 3 +
src/libqof/qof/gnc-int128.cpp | 325 +++++--
src/libqof/qof/gnc-int128.hpp | 27 +-
src/libqof/qof/gnc-numeric.cpp | 1062 +++++++++++++++-------
src/libqof/qof/gnc-numeric.h | 32 -
src/libqof/qof/gnc-numeric.hpp | 433 +++++++++
src/libqof/qof/gnc-rational-rounding.hpp | 133 +++
src/libqof/qof/gnc-rational.cpp | 443 ++++-----
src/libqof/qof/gnc-rational.hpp | 290 ++++--
src/libqof/qof/test/CMakeLists.txt | 38 +
src/libqof/qof/test/Makefile.am | 190 ++--
src/libqof/qof/test/gtest-gnc-int128.cpp | 404 ++++----
src/libqof/qof/test/gtest-gnc-numeric.cpp | 525 +++++++++++
src/libqof/qof/test/gtest-gnc-rational.cpp | 173 ++++
src/libqof/qof/test/test-gnc-numeric.c | 47 -
src/{engine => libqof/qof}/test/test-numeric.cpp | 135 +--
22 files changed, 3145 insertions(+), 1176 deletions(-)
create mode 100644 src/libqof/qof/gnc-numeric.hpp
create mode 100644 src/libqof/qof/gnc-rational-rounding.hpp
create mode 100644 src/libqof/qof/test/gtest-gnc-numeric.cpp
create mode 100644 src/libqof/qof/test/gtest-gnc-rational.cpp
delete mode 100644 src/libqof/qof/test/test-gnc-numeric.c
rename src/{engine => libqof/qof}/test/test-numeric.cpp (87%)
More information about the gnucash-changes
mailing list