mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
This will avoid a ninja-build from picking up a config.h generated by the autotools build (in the root build directory). Picking up the wrong config.h may lead to all kinds of subtle issues if the autotools run was done with different options than the cmake run.
918 lines
33 KiB
C++
918 lines
33 KiB
C++
|
|
/* 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 http://bugzilla.gnome.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 ====================== */
|