
/* Test file created by Linas Vepstas <linas@linas.org>
 * Review operation of the gnc-numeric tools by verifying results
 * of vairous operations.
 *
 * June 2004
 */
/*
 *  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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *  02110-1301, USA.
 */

extern "C"
{
#include <config.h>
#include <ctype.h>
#include <glib.h>
#include "cashobjects.h"
#include "test-stuff.h"
#include "test-engine-stuff.h"
#include "gnc-numeric.h"
}

#define NREPS 2

static char *
gnc_numeric_print(gnc_numeric in)
{
    char * retval;
    if (gnc_numeric_check(in))
    {
        retval = g_strdup_printf("<ERROR> [%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
                                 in.num,
                                 in.denom);
    }
    else
    {
        retval = g_strdup_printf("[%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT "]",
                                 in.num,
                                 in.denom);
    }
    return retval;
}

/* ======================================================= */

#define check_unary_op(eq,ex,a,i,e) check_unary_op_r(eq,ex,a,i,e,__LINE__)
static void
check_unary_op_r (gboolean (*eqtest) (gnc_numeric, gnc_numeric),
                  gnc_numeric expected,
                  gnc_numeric actual,
                  gnc_numeric input,
                  const char * errmsg,
                  int line)
{
    char *e = gnc_numeric_print (expected);
    char *r = gnc_numeric_print (actual);
    char *a = gnc_numeric_print (input);
    char *str = g_strdup_printf (errmsg, e, r, a);

    do_test_call (eqtest(expected, actual), str, __FILE__, line);

    g_free (a);
    g_free (r);
    g_free (e);
    g_free (str);
}

/* ======================================================= */

#define check_binary_op(ex,a,ia,ib,e) check_binary_op_r(ex,a,ia,ib,e,__LINE__,gnc_numeric_eq)
#define check_binary_op_equal(ex,a,ia,ib,e) check_binary_op_r(ex,a,ia,ib,e,__LINE__,gnc_numeric_equal)
static void
check_binary_op_r (gnc_numeric expected,
                   gnc_numeric actual,
                   gnc_numeric input_a,
                   gnc_numeric input_b,
                   const char * errmsg,
                   int line,
                   gboolean (*eq)(gnc_numeric, gnc_numeric))
{
    char *e = gnc_numeric_print (expected);
    char *r = gnc_numeric_print (actual);
    char *a = gnc_numeric_print (input_a);
    char *b = gnc_numeric_print (input_b);
    char *str = g_strdup_printf (errmsg, e, r, a, b);

    do_test_call ((eq)(expected, actual), str, __FILE__, line);

    g_free (a);
    g_free (b);
    g_free (r);
    g_free (e);
    g_free (str);
}

/* ======================================================= */

static gboolean
gnc_numeric_unequal (gnc_numeric a, gnc_numeric b)
{
    return (0 == gnc_numeric_equal (a, b));
}

/* ======================================================= */

/* Make sure that the equivalence operator we use for
 * later tests actually works */
static void
check_eq_operator (void)
{
    gnc_numeric a = gnc_numeric_create (42, 58);
    gnc_numeric b = gnc_numeric_create (42, 58);
    gnc_numeric c = gnc_numeric_create (40, 58);

    /* Check strict equivalence and non-equivalence */
    do_test (gnc_numeric_eq(a, a), "expected self-equivalence");
    do_test (gnc_numeric_eq(a, b), "expected equivalence");
    do_test (0 == gnc_numeric_eq(a, c), "expected inequivalence");
}

/* ======================================================= */

static void
check_reduce (void)
{
    gnc_numeric one, rone;
    gnc_numeric four, rfour;
    gnc_numeric val, rval;
    /* Check common factor elimination (needed for equality checks) */
    one = gnc_numeric_create (1, 1);
    rone = gnc_numeric_create (1000000, 1000000);
    rone = gnc_numeric_reduce (rone);
    do_test (gnc_numeric_eq(one, rone), "reduce to one");

    four = gnc_numeric_create (4, 1);
    rfour = gnc_numeric_create (480, 120);
    rfour = gnc_numeric_reduce (rfour);
    do_test (gnc_numeric_eq(four, rfour), "reduce to four");

    val = gnc_numeric_create(10023234LL, 334216654LL);
    rval = gnc_numeric_reduce (val);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (5011617, 167108327),
                    rval,
                    val, "check_reduce(1) expected %s got %s = reduce(%s)");

    val = gnc_numeric_create(17474724864LL, 136048896LL);
    rval = gnc_numeric_reduce (val);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (4 * 17 * 17, 9),
                    rval,
                    val, "check_reduce(2) expected %s got %s = reduce(%s)");

    val = gnc_numeric_create(1024LL, 1099511627776LL);
    rval = gnc_numeric_reduce (val);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (1, 1024 * 1024 * 1024),
                    rval,
                    val, "check_reduce(3): expected %s got %s = reduce(%s)");
}

/* ======================================================= */

static void
check_equality_operator (void)
{
    int i, m;
    gint mult;
    gint64 f, deno, numer;
    gnc_numeric big, rbig;
    gnc_numeric val, mval;
    gnc_numeric bval, rval;
    /* Check equality operator for some large numer/denom values */
    numer = 1 << 30;
    numer <<= 30;   /* we don't trust cpp to compute 1<<60 correctly */
    deno = 1 << 30;
    deno <<= 20;
    rbig = gnc_numeric_create (numer, deno);

    big = gnc_numeric_create (1 << 10, 1);
    do_test (gnc_numeric_equal(big, rbig), "equal to billion");

    big = gnc_numeric_create (1 << 20, 1 << 10);
    do_test (gnc_numeric_equal(big, rbig), "equal to 1<<20/1<<10");

    big = gnc_numeric_create (1 << 30, 1 << 20);
    do_test (gnc_numeric_equal(big, rbig), "equal to 1<<30/1<<20");

    numer = 1 << 30;
    numer <<= 30;   /* we don't trust cpp to compute 1<<60 correctly */
    deno = 1 << 30;
    rbig = gnc_numeric_create (numer, deno);

    big = gnc_numeric_create (1 << 30, 1);
    do_test (gnc_numeric_equal(big, rbig), "equal to 1<<30");

    numer = 1 << 30;
    numer <<= 10;
    big = gnc_numeric_create (numer, 1 << 10);
    do_test (gnc_numeric_equal(big, rbig), "equal to 1<<40/1<<10");

    numer <<= 10;
    big = gnc_numeric_create (numer, 1 << 20);
    do_test (gnc_numeric_equal(big, rbig), "equal to 1<<50/1<<20");

    /* We assume RAND_MAX is less that 1<<32 */
    for (i = 0; i < NREPS; i++)
    {
        deno = rand() / 2;
        mult = rand() / 2;
        numer = rand() / 2;

        /* avoid 0 */
        if (deno == 0 || mult == 0)
        {
            i--;
            continue;
        }

        val = gnc_numeric_create (numer, deno);
        mval = gnc_numeric_create (numer * mult, deno * mult);

        /* The reduced version should be equivalent */
        bval = gnc_numeric_reduce (val);
        rval = gnc_numeric_reduce (mval);
        check_unary_op (gnc_numeric_eq,
                        bval, rval, mval, "expected %s got %s = reduce(%s)");

        /* The unreduced versions should be equal */
        check_unary_op (gnc_numeric_equal,
                        val, mval, mval, "expected %s = %s");

        /* Certain modulo's should be very cleary un-equal; this
         * helps stop funky modulo-64 aliasing in compares that
         * might creep in. */
        mval.denom >>= 1;
        mval.num >>= 1;
        m = 0;
        f = mval.denom;
        while (f % 2 == 0)
        {
            f >>= 1;
            m++;
        }
        if (1 < m)
        {
            gint64 nn = 1 << (32 - m);
            nn <<= 32;
            nn += mval.num;
            val = gnc_numeric_create (2 * nn, 2 * mval.denom);
            check_unary_op (gnc_numeric_unequal,
                            val, mval, mval, "expected unequality %s != %s");

        }
    }
}

/* ======================================================= */

static void
check_rounding (void)
{
    gnc_numeric val;

    val = gnc_numeric_create(7, 16);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (43, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_FLOOR),
                    val, "expected %s got %s = (%s as 100th's floor)");
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (44, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_CEIL),
                    val, "expected %s got %s = (%s as 100th's ceiling)");
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (43, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_TRUNC),
                    val, "expected %s got %s = (%s as 100th's trunc)");
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (44, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");

    val = gnc_numeric_create(1511, 1000);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (151, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");

    val = gnc_numeric_create(1516, 1000);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (152, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");

    /* Half-values always get rounded to nearest even number */
    val = gnc_numeric_create(1515, 1000);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (152, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");

    val = gnc_numeric_create(1525, 1000);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (152, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");

    val = gnc_numeric_create(1535, 1000);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (154, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");

    val = gnc_numeric_create(1545, 1000);
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (154, 100),
                    gnc_numeric_convert (val, 100, GNC_HOW_RND_ROUND),
                    val, "expected %s got %s = (%s as 100th's round)");
}

/* ======================================================= */

static void
check_double (void)
{
    double flo;
    gnc_numeric val = gnc_numeric_create (0, 1);

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (112346, 100000),
                    double_to_gnc_numeric(1.1234567890123,
                                          GNC_DENOM_AUTO,
                                          GNC_HOW_DENOM_SIGFIGS(6) |
                                          GNC_HOW_RND_ROUND),
                    val, "expected %s = %s double 6 figs");

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (112346, 10000000),
                    double_to_gnc_numeric(0.011234567890123,
                                          GNC_DENOM_AUTO,
                                          GNC_HOW_DENOM_SIGFIGS(6) |
                                          GNC_HOW_RND_ROUND),
                    val, "expected %s = %s double 6 figs");

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (112346, 100),
                    double_to_gnc_numeric(1123.4567890123,
                                          GNC_DENOM_AUTO,
                                          GNC_HOW_DENOM_SIGFIGS(6) |
                                          GNC_HOW_RND_ROUND),
                    val, "expected %s = %s double 6 figs");
    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (112346, 10000000000LL),
                    double_to_gnc_numeric(1.1234567890123e-5,
                                          GNC_DENOM_AUTO,
                                          GNC_HOW_DENOM_SIGFIGS(6) |
                                          GNC_HOW_RND_ROUND),
                    val, "expected %s = %s double 6 figs");

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (961600000, 10000000),
                    double_to_gnc_numeric(96.16,
                                          GNC_DENOM_AUTO,
                                          GNC_HOW_DENOM_SIGFIGS(9) |
                                          GNC_HOW_RND_ROUND),
                    val, "expected %s = %s GncNumeric from 96.16");

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (9616000000, 1),
                    double_to_gnc_numeric(9616000000.0,
                                          GNC_DENOM_AUTO,
                                          GNC_HOW_DENOM_SIGFIGS(9) |
                                          GNC_HOW_RND_ROUND),
                    val, "expected %s = %s GncNumeric from 9616000000.0");

    flo = gnc_numeric_to_double(gnc_numeric_create(7, 16));
    do_test ((0.4375 == flo), "float pt conversion");
}

/* ======================================================= */

static void
check_neg (void)
{
    gnc_numeric a = gnc_numeric_create(2, 6);
    gnc_numeric b = gnc_numeric_create(1, 4);
    gnc_numeric c = gnc_numeric_neg (a);
    gnc_numeric d = gnc_numeric_neg (b);

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (-2, 6), c,
                    a, "expected %s got %s = -(%s)");

    check_unary_op (gnc_numeric_eq,
                    gnc_numeric_create (-1, 4), d,
                    b, "expected %s got %s = -(%s)");

}

/* ======================================================= */

static void
check_add_subtract (void)
{
    int i;
    gnc_numeric a, b, c, d, z;

    a = gnc_numeric_create(2, 6);
    b = gnc_numeric_create(1, 4);

    /* Well, actually 14/24 would be acceptable/better in this case */
    check_binary_op (gnc_numeric_create(7, 12),
                     gnc_numeric_add(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s + %s for add exact");

    check_binary_op (gnc_numeric_create(58, 100),
                     gnc_numeric_add(a, b, 100, GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s + %s for add 100ths (banker's)");

    check_binary_op (gnc_numeric_create(5833, 10000),
                     gnc_numeric_add(a, b, GNC_DENOM_AUTO,
                                     GNC_HOW_DENOM_SIGFIGS(4) |
                                     GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s + %s for add 4 sig figs");

    check_binary_op (gnc_numeric_create(583333, 1000000),
                     gnc_numeric_add(a, b, GNC_DENOM_AUTO,
                                     GNC_HOW_DENOM_SIGFIGS(6) |
                                     GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s + %s for add 6 sig figs");

    check_binary_op (gnc_numeric_create(1, 12),
                     gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s - %s for sub exact");

    /* We should try something trickier for reduce & lcd */
    check_binary_op (gnc_numeric_create(1, 12),
                     gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE),
                     a, b, "expected %s got %s = %s - %s for sub reduce");

    check_binary_op (gnc_numeric_create(1, 12),
                     gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD),
                     a, b, "expected %s got %s = %s - %s for sub reduce");

    check_binary_op (gnc_numeric_create(8, 100),
                     gnc_numeric_sub(a, b, 100, GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s - %s for sub 100ths (banker's)");

    /* ------------------------------------------------------------ */
    /* This test has failed before */
    c = gnc_numeric_neg (a);
    d = gnc_numeric_neg (b);
    z = gnc_numeric_zero();
    check_binary_op (c, gnc_numeric_add_fixed(z, c),
                     z, c, "expected %s got %s = %s + %s for add fixed");

    check_binary_op (d, gnc_numeric_add_fixed(z, d),
                     z, d, "expected %s got %s = %s + %s for add fixed");

    /* ------------------------------------------------------------ */
    /* Same as above, but with signs reviersed */
    a = c;
    b = d;
    /* Well, actually 14/24 would be acceptable/better in this case */
    check_binary_op (gnc_numeric_create(-7, 12),
                     gnc_numeric_add(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s + %s for add exact");

    check_binary_op (gnc_numeric_create(-58, 100),
                     gnc_numeric_add(a, b, 100, GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s + %s for add 100ths (banker's)");

    check_binary_op (gnc_numeric_create(-5833, 10000),
                     gnc_numeric_add(a, b, GNC_DENOM_AUTO,
                                     GNC_HOW_DENOM_SIGFIGS(4) |
                                     GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s + %s for add 4 sig figs");

    check_binary_op (gnc_numeric_create(-583333, 1000000),
                     gnc_numeric_add(a, b, GNC_DENOM_AUTO,
                                     GNC_HOW_DENOM_SIGFIGS(6) |
                                     GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s + %s for add 6 sig figs");

    check_binary_op (gnc_numeric_create(-1, 12),
                     gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s - %s for sub exact");

    /* We should try something trickier for reduce & lcd */
    check_binary_op (gnc_numeric_create(-1, 12),
                     gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE),
                     a, b, "expected %s got %s = %s - %s for sub reduce");

    check_binary_op (gnc_numeric_create(-1, 12),
                     gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD),
                     a, b, "expected %s got %s = %s - %s for sub reduce");

    check_binary_op (gnc_numeric_create(-8, 100),
                     gnc_numeric_sub(a, b, 100, GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s - %s for sub 100ths (banker's)");

    /* ------------------------------------------------------------ */
    /* Add and subtract some random numbers */
    for (i = 0; i < NREPS; i++)
    {
        gnc_numeric e;
        gint64 deno = rand() + 1;
        gint64 na = get_random_gint64();
        gint64 nb = get_random_gint64();
        gint64 ne;

        /* avoid overflow; */
        na /= 2;
        nb /= 2;

        a = gnc_numeric_create(na, deno);
        b = gnc_numeric_create(nb, deno);

        /* Add */
        ne = na + nb;
        e = gnc_numeric_create(ne, deno);
        check_binary_op (e,
                         gnc_numeric_add(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                         a, b, "expected %s got %s = %s + %s for exact addition");

        /* Subtract */
        ne = na - nb;
        e = gnc_numeric_create(ne, deno);
        check_binary_op (e,
                         gnc_numeric_sub(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                         a, b, "expected %s got %s = %s - %s for exact subtraction");
    }
}

static const gint64 pten[] = { 1, 10, 100, 1000, 10000, 100000, 1000000,
			       10000000, 100000000, 1000000000, 10000000000,
			       100000000000, 1000000000000, 10000000000000,
			       100000000000000, 10000000000000000,
			       100000000000000000, 1000000000000000000};
#define POWTEN_OVERFLOW -5

static inline gint64
powten (int exp)
{
    if (exp > 18 || exp < -18)
	return POWTEN_OVERFLOW;
    return exp < 0 ? -pten[-exp] : pten[exp];
}

static void
check_add_subtract_overflow (void)
{
    int i;

    for (i = 0; i < NREPS; i++)
    {
	/* Div to avoid addition overflows; we're looking for lcd conversion overflows here. */

	int exp_a = rand () % 1000;
	int exp_b = rand () % 1000;
        gint64 bin_deno_a = (exp_a == 0 ? 1 : exp_a);
	gint64 bin_deno_b = (exp_b == 0 ? 1 : exp_b);
/*
	int exp_a = rand () % 11;
	int exp_b = rand () % 11;
	gint64 bin_deno_a = (1 << exp_a);
	gint64 bin_deno_b = (1 << exp_a);
*/
	gint64 dec_deno_a = powten (exp_a % 7);
	gint64 dec_deno_b = powten (exp_b % 7);
        gint64 na = get_random_gint64 () % (1000000 * dec_deno_a);
        gint64 nb = get_random_gint64 () % (1000000 * dec_deno_b);
	gnc_numeric result;
	GNCNumericErrorCode err;
	gchar *errmsg;

        gnc_numeric ba = gnc_numeric_create(na, bin_deno_a);
        gnc_numeric bb = gnc_numeric_create(nb, bin_deno_b);
        gnc_numeric da = gnc_numeric_create(na, dec_deno_a);
        gnc_numeric db = gnc_numeric_create(nb, dec_deno_b);
	gchar *ba_str = gnc_numeric_to_string (ba);
	gchar *bb_str = gnc_numeric_to_string (bb);
	gchar *da_str = gnc_numeric_to_string (da);
	gchar *db_str = gnc_numeric_to_string (db);


        /* Add */

	result = gnc_numeric_add(ba, bb, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", ba_str, bb_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);

	result = gnc_numeric_add(da, bb, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", da_str, bb_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);
	result = gnc_numeric_add(ba, db, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", ba_str, db_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);

	result = gnc_numeric_add(da, db, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", da_str, db_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);
        /* Subtract */

	result = gnc_numeric_sub(ba, bb, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", ba_str, bb_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);

	result = gnc_numeric_sub(da, bb, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", da_str, bb_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);
	result = gnc_numeric_sub(ba, db, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", ba_str, db_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);

	result = gnc_numeric_sub(da, db, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
	err = gnc_numeric_check (result);
	errmsg = g_strdup_printf ("%s + %s raised %s", da_str, db_str,
				  gnc_numeric_errorCode_to_string (err));
	do_test (err == 0, errmsg);
	g_free (errmsg);

	g_free (ba_str);
	g_free (bb_str);
	g_free (da_str);
	g_free (db_str);
    }

}

/* ======================================================= */


static void
check_mult_div (void)
{
    int i, j;
    gint64 v;
    gnc_numeric c, d;
    gnc_numeric amt_a, amt_tot, frac, val_tot, val_a;
    gnc_numeric a, b;

    a = gnc_numeric_create(-100, 100);
    b = gnc_numeric_create(1, 1);
    check_binary_op (gnc_numeric_create(-100, 100),
                     gnc_numeric_div(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s / %s div exact");

    a = gnc_numeric_create(-100, 100);
    b = gnc_numeric_create(-1, 1);
    check_binary_op (gnc_numeric_create(100, 100),
                     gnc_numeric_div(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s / %s div exact");

    a = gnc_numeric_create(-100, 100);
    b = gnc_numeric_create(-1, 1);
    check_binary_op (gnc_numeric_create(100, 100),
                     gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s * %s mult exact");

    a = gnc_numeric_create(2, 6);
    b = gnc_numeric_create(1, 4);

    check_binary_op (gnc_numeric_create(2, 24),
                     gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s * %s for mult exact");

    check_binary_op (gnc_numeric_create(1, 12),
                     gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE),
                     a, b, "expected %s got %s = %s * %s for mult reduce");

    check_binary_op (gnc_numeric_create(8, 100),
                     gnc_numeric_mul(a, b, 100, GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s * %s for mult 100th's");

    check_binary_op (gnc_numeric_create(8, 6),
                     gnc_numeric_div(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                     a, b, "expected %s got %s = %s / %s for div exact");

    check_binary_op (gnc_numeric_create(4, 3),
                     gnc_numeric_div(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE),
                     a, b, "expected %s got %s = %s / %s for div reduce");

    check_binary_op (gnc_numeric_create(133, 100),
                     gnc_numeric_div(a, b, 100, GNC_HOW_RND_ROUND),
                     a, b, "expected %s got %s = %s * %s for div 100th's");

    /* Check for math with 2^63 < num*num < 2^64 which previously failed
     * see https://bugs.gnucash.org/show_bug.cgi?id=144980
     */
    v = 1000000;
    a = gnc_numeric_create(1 * v, v);
    b = gnc_numeric_create(10000000 * v, v);

    check_binary_op (b,
                     gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD),
                     a, b, "expected %s got %s = %s * %s for multiply");

    /* Multiply some random numbers.  This test presumes that
     * RAND_MAX is approx 2^32
     */
    for (i = 0; i < NREPS; i++)
    {
        gint64 deno = 1;
        gint64 na = rand();
        gint64 nb = rand();
        gint64 ne;

        /* avoid 0 */
        if (nb / 4 == 0)
        {
            i--;
            continue;
        }

        /* avoid overflow; */
        na /= 2;
        nb /= 2;
        ne = na * nb;

        a = gnc_numeric_create(na, deno);
        b = gnc_numeric_create(nb, deno);

        check_binary_op_equal (gnc_numeric_create(ne, 1),
                               gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                               a, b, "expected %s got %s = %s * %s for mult exact");

        /* Force 128-bit math to come into play */
        for (j = 1; j < 31; j++)
        {
            a = gnc_numeric_create(na << j, 1 << j);
            b = gnc_numeric_create(nb << j, 1 << j);
            check_binary_op (gnc_numeric_create(ne, 1),
                             gnc_numeric_mul(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE),
                             a, b, "expected %s got %s = %s * %s for mult reduce");
        }

        /* Do some hokey random 128-bit division too */
        b = gnc_numeric_create(deno, nb);

        check_binary_op_equal (gnc_numeric_create(ne, 1),
                               gnc_numeric_div(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT),
                               a, b, "expected %s got %s = %s / %s for div exact");

        /* avoid overflow; */
        na /= 2;
        nb /= 2;
        ne = na * nb;
        for (j = 1; j < 16; j++)
        {
            a = gnc_numeric_create(na << j, 1 << j);
            b = gnc_numeric_create(1 << j, nb << j);
            check_binary_op (gnc_numeric_create(ne, 1),
                             gnc_numeric_div(a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE),
                             a, b, "expected %s got %s = %s / %s for div reduce");
        }
    }

    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 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)
     * 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.
     */

    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");

    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");

    /* The below is a 'typical' value calculation:
     * value_frac = value_tot * amt_frace / amt_tot
     * and has some typical potential-overflow values.
     * 82718 = 2 * 59 * 701
     * 47497125586 = 2 * 1489 * 15949337
     * 69100955 = 5 * 7 * 11 * 179483
     * 32005637020 = 4 * 5 * 7 * 43 * 71 * 103 * 727
     */
    a = gnc_numeric_create (-47497125586LL, 82718);
    b = gnc_numeric_create (-69100955LL, 55739);
    c = gnc_numeric_mul (a, b, GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT);
    d = gnc_numeric_create (-32005637020LL, 55739);

    check_binary_op (gnc_numeric_create(-102547458LL, 82718),
                     gnc_numeric_div(c, d, 82718,
                                     GNC_HOW_DENOM_EXACT),
                     c, d, "expected %s got %s = %s / %s for div round");

    /* If we specify GNC_HOW_RND_NEVER, then we shoukld get an error,
     * since the exact result won't fit into a 64-bit quantity. */
    check_binary_op (gnc_numeric_error (GNC_ERROR_REMAINDER),
                     gnc_numeric_div(c, d, 82718,
                                     GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER),
                     c, d, "expected %s got %s = %s / %s for div round");

    /* A simple irreducible ratio, involving negative numbers */
    amt_a = gnc_numeric_create (-6005287905LL, 40595);
    amt_tot = gnc_numeric_create (-8744187958LL, 40595);
    frac = gnc_numeric_div (amt_a, amt_tot,
                            GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);

    check_binary_op (gnc_numeric_create(6005287905LL, 8744187958LL),
                     frac, amt_a, amt_tot,
                     "expected %s got %s = %s / %s for div reduce");

    /* Another overflow-prone condition */
    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_REDUCE);
    check_binary_op (gnc_numeric_create(-2939846940LL, 19873),
                     val_a, val_tot, frac,
                     "expected %s got %s = %s * %s for mult round");

    frac = gnc_numeric_create (396226789777979LL, 328758834367851752LL);
    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_REDUCE);
    check_binary_op (gnc_numeric_create(562854124919LL, 100),
                     val_a, val_tot, frac,
                     "expected %s got %s = %s * %s for mult round");

    /* Yet another bug from bugzilla ... */
    a = gnc_numeric_create (40066447153986554LL, 4518);
    b = gnc_numeric_create (26703286457229LL, 3192);
    frac = gnc_numeric_div(a, b,
                           GNC_DENOM_AUTO,
                           GNC_HOW_DENOM_SIGFIGS(6) |
                           GNC_HOW_RND_ROUND);

    check_binary_op (gnc_numeric_create(106007, 100),
                     frac, a, b,
                     "expected %s got %s = %s / %s for mult sigfigs");

}

/* ======================================================= */

static void
run_test (void)
{
    check_eq_operator ();
    check_reduce ();
    check_equality_operator ();
    check_rounding();
    check_double();
    check_neg();
    check_add_subtract();
    check_add_subtract_overflow ();
    check_mult_div ();
}

int
main (int argc, char **argv)
{
    qof_init();
    if (cashobjects_register())
    {
        run_test ();
        print_test_results();
    }
    qof_close();
    return get_rv();
}

/* ======================== END OF FILE ====================== */
