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