[Gnucash-changes] add 128-bit division & remainder support.
Linas Vepstas
linas at cvs.gnucash.org
Sun May 30 01:04:38 EDT 2004
Log Message:
-----------
add 128-bit division & remainder support.
This helps reduce the failing gnc-lot problem.
Modified Files:
--------------
gnucash/src/engine:
gnc-numeric.c
Revision Data
-------------
Index: gnc-numeric.c
===================================================================
RCS file: /home/cvs/cvsroot/gnucash/src/engine/gnc-numeric.c,v
retrieving revision 1.32
retrieving revision 1.33
diff -Lsrc/engine/gnc-numeric.c -Lsrc/engine/gnc-numeric.c -u -r1.32 -r1.33
--- src/engine/gnc-numeric.c
+++ src/engine/gnc-numeric.c
@@ -41,28 +41,34 @@
/* static short module = MOD_ENGINE; */
/* =============================================================== */
-/* Quick-n-dirty 128-bit math lib */
+/* Quick-n-dirty 128-bit math lib. The mult128 routine should work
+ * great; I think that div128 works, but its not really tested.
+ */
typedef struct {
- gint64 hi;
+ guint64 hi;
guint64 lo;
+ short isneg;
} gncint128;
+/** Multiply a pair of signed 64-bit numbers,
+ * returning a signed 128-bit number.
+ */
static inline gncint128
mult128 (gint64 a, gint64 b)
{
gncint128 prod;
- short aneg=0, bneg=0;
+ prod.isneg = 0;
if (0>a)
{
- aneg = 1;
+ prod.isneg = !prod.isneg;
a = -a;
}
if (0>b)
{
- bneg = 1;
+ prod.isneg = !prod.isneg;
b = -b;
}
@@ -104,12 +110,111 @@
prod.lo = d0 + (sum<<32);
prod.hi = carry + e1 + f1 + g0 + (g1<<32);
- if (aneg) prod.hi = -prod.hi;
- if (bneg) prod.hi = -prod.hi;
-
return prod;
}
+/** Divide a signed 128-bit number by a signed 64-bit,
+ * returning a signed 128-bit number.
+ */
+static inline gncint128
+div128 (gncint128 n, gint64 d)
+{
+ gncint128 quotient;
+ guint64 hirem; /* hi remainder */
+ guint64 qlo;
+
+ quotient.isneg = n.isneg;
+ if (0 > d)
+ {
+ d = -d;
+ quotient.isneg = !quotient.isneg;
+ }
+
+ quotient.hi = n.hi / d;
+ hirem = n.hi - quotient.hi * d;
+
+ guint64 lo = 1<<30;
+ lo <<= 33;
+ lo /= d;
+ lo <<= 1;
+
+ lo *= hirem;
+ quotient.lo = lo + n.lo/d;
+
+ /* Deal with low remainder bits.
+ * There's probably a more efficient way of doing this.
+ * XXX This algo breaks if the value of teh denominator
+ * is larger than 2 billion.
+ */
+ guint64 rnd = quotient.lo;
+ // rnd &= 0x7fffffff;
+ rnd *= d;
+ rnd &= 0x7fffffff;
+ rnd = (n.lo & 0x7fffffff) - rnd;
+ rnd &= 0x7fffffff;
+ rnd /= d;
+
+ /* ?? will this ever overflow ? */
+ qlo = quotient.lo;
+ quotient.lo += rnd;
+ if (lo > quotient.lo)
+ {
+ quotient.hi += 1;
+ }
+
+ return quotient;
+}
+
+/** Return the remainder of a signed 128-bit number modulo a signed 64-bit,
+ * XXX the current algo only works for divisor values less than 2 billion.
+ */
+static inline gint64
+rem128 (gncint128 n, gint64 d)
+{
+ gncint128 quotient = div128 (n,d);
+
+ guint64 rnd = quotient.lo;
+ // rnd &= 0x7fffffff;
+ rnd *= d;
+ rnd &= 0x7fffffff;
+ rnd = (n.lo & 0x7fffffff) - rnd;
+ rnd &= 0x7fffffff;
+ return rnd;
+}
+
+/** Return the ratio n/d reduced so that there are no common factors. */
+static inline gnc_numeric
+reduce128(gncint128 n, gint64 d)
+{
+ gint64 t;
+ gint64 num;
+ gint64 denom;
+ gnc_numeric out;
+
+ t = rem128 (n, d);
+ num = d;
+ denom = t;
+
+ /* The strategy is to use Euclid's algorithm */
+ while (denom > 0)
+ {
+ t = num % denom;
+ num = denom;
+ denom = t;
+ }
+ /* num now holds the GCD (Greatest Common Divisor) */
+
+ gncint128 red = div128 (n, num);
+ if (red.hi)
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
+ out.num = red.lo;
+ if (red.isneg) out.num = -out.num;
+ out.denom = d / num;
+ return out;
+}
+
#ifdef TEST_128_BIT_MULT
void pr (gint64 a, gint64 b)
{
@@ -117,6 +222,15 @@
printf ("%lld * %lld = %lld %llu (0x%llx %llx)\n", a,b, prod.hi, prod.lo, prod.hi, prod.lo);
}
+void prd (gint64 a, gint64 b, gint64 c)
+{
+ gncint128 prod = mult128 (a,b);
+ gncint128 quot = div128 (prod, c);
+ gint64 rem = rem128 (prod, c);
+ printf ("%lld * %lld / %lld = %lld %llu + %lld (0x%llx %llx)\n", a,b, c, quot.hi,
+quot.lo, rem, quot.hi, quot.lo);
+}
+
main ()
{
pr (2,2);
@@ -137,7 +251,23 @@
x <<= 1;
pr (x,x);
pr (x,-x);
+
+ prd (x,x,2);
+ prd (x,x,3);
+ prd (x,x,4);
+ prd (x,x,5);
+ prd (x,x,6);
+
+ x <<= 29;
+ prd (3,x,3);
+ prd (6,x,3);
+ prd (99,x,3);
+ prd (100,x,5);
+ prd (540,x,5);
+ prd (777,x,7);
+ prd (1111,x,11);
}
+
#endif /* TEST_128_BIT_MULT */
/* =============================================================== */
@@ -504,10 +634,14 @@
product.num = a.num*b.num;
product.denom = a.denom*b.denom;
+ /* If it looks to be overflowing, try to reduce the fraction ... */
if (0 != bigprod.hi)
{
- /* We can try to do better, by reducing the fraction ... */
- return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ product = reduce128 (bigprod, product.denom);
+ if (gnc_numeric_check (product))
+ {
+ return gnc_numeric_error (GNC_ERROR_OVERFLOW);
+ }
}
#if 0 /* currently, product denom won't ever be zero */
More information about the gnucash-changes
mailing list