GMP unboxed number representation

Marcin 'Qrczak' Kowalczyk qrczak@knm.org.pl
Mon, 7 May 2001 18:45:26 +0200


On Fri, May 04, 2001 at 11:08:59PM +0200, Hans Aberg wrote:

> GMP has a very interesting multi-precision floating number type as
> well, but it has the same problem as the integers: It does not use
> the native double's for small float, so it probably becomes slow.

I think that it's easier to check machine-size int for overflow
than to check double for overflow or loss of precision, so it's
impractical to use native double and keep predictable precision.

> >Indeed. ghc solves it by using gmp format only temporarily to perform
> >operations, and keeping numbers in its own structures.
> 
> Does that mean that you are getting extra memory allocations,

No. When mpz is constructed to perform an operation, _mp_d is set to
point to an already allocated memory on the ghc heap.

Ghc's heap address can generally move during GC. gmp is called from
blocks of C or assembler code which don't cause GC in the middle. Using
persistent mpz objects would require allocation of _mp_d on the C
heap which is slower, and a finalization hook for each Integer (which
would be an additional overhead given the way custom finalization
hooks are implemented).

> Perhaps GMP should provide small number arithmetic, with a fast
> way to determine if the answer fits in a small number representation.

Indeed. Ghc has those primops, used for Integer arithmetic, with
tricky implementations in the case the code is generated via C:

/* -----------------------------------------------------------------------------
 * Int operations with carry.
 * -------------------------------------------------------------------------- */

/* With some bit-twiddling, we can define int{Add,Sub}Czh portably in
 * C, and without needing any comparisons.  This may not be the
 * fastest way to do it - if you have better code, please send it! --SDM
 *
 * Return : r = a + b,  c = 0 if no overflow, 1 on overflow.
 *
 * We currently don't make use of the r value if c is != 0 (i.e. 
 * overflow), we just convert to big integers and try again.  This
 * could be improved by making r and c the correct values for
 * plugging into a new J#.  
 */
#define addIntCzh(r,c,a,b)			\
{ r = a + b;					\
  c = ((StgWord)(~(a^b) & (a^r)))		\
    >> (BITS_IN (I_) - 1);			\
}


#define subIntCzh(r,c,a,b)			\
{ r = a - b;					\
  c = ((StgWord)((a^b) & (a^r)))		\
    >> (BITS_IN (I_) - 1);			\
}

/* Multiply with overflow checking.
 *
 * This is slightly more tricky - the usual sign rules for add/subtract
 * don't apply.  
 *
 * On x86 hardware we use a hand-crafted assembly fragment to do the job.
 *
 * On other 32-bit machines we use gcc's 'long long' types, finding
 * overflow with some careful bit-twiddling.
 *
 * On 64-bit machines where gcc's 'long long' type is also 64-bits,
 * we use a crude approximation, testing whether either operand is
 * larger than 32-bits; if neither is, then we go ahead with the
 * multiplication.
 */

#if i386_TARGET_ARCH

#define mulIntCzh(r,c,a,b)				\
{							\
  __asm__("xorl %1,%1\n\t				\
	   imull %2,%3\n\t				\
	   jno 1f\n\t					\
	   movl $1,%1\n\t				\
	   1:" 						\
	: "=r" (r), "=&r" (c) : "r" (a), "0" (b));	\
}

#elif SIZEOF_VOID_P == 4

#ifdef WORDS_BIGENDIAN
#define C 0
#define R 1
#else
#define C 1
#define R 0
#endif

typedef union {
    StgInt64 l;
    StgInt32 i[2];
} long_long_u ;

#define mulIntCzh(r,c,a,b)			\
{						\
  long_long_u z;				\
  z.l = (StgInt64)a * (StgInt64)b;		\
  r = z.i[R];					\
  c = z.i[C];					\
  if (c == 0 || c == -1) {			\
    c = ((StgWord)((a^b) ^ r))			\
      >> (BITS_IN (I_) - 1);			\
  }						\
}
/* Careful: the carry calculation above is extremely delicate.  Make sure
 * you test it thoroughly after changing it.
 */

#else

#define HALF_INT  (1 << (BITS_IN (I_) / 2))

#define stg_abs(a) ((a) < 0 ? -(a) : (a))

#define mulIntCzh(r,c,a,b)			\
{						\
  if (stg_abs(a) >= HALF_INT			\
      stg_abs(b) >= HALF_INT) {			\
    c = 1;					\
  } else {					\
    r = a * b;					\
    c = 0;					\
  }						\
}
#endif

> So this, in the end, suggests that one perhaps should get a better GMP
> interface for perhaps both small and large number representations. But it
> should then be so that GHC could use that interface, rather than abusing
> its internals.

Yes, except that I'm not sure how much harder for ghc would be to
use a GMP's interface than to do it itself.

BTW, it could be nice to have a better way for writing large integer
literals. Ghc used to convert them from a decimal string, and now it
builds them from pieces by * and + in base 2^31-1 or 2^63-1. Both
approaches are a bit ugly. But probably large integer literals are
not that common for it to matter much.

-- 
 __("<  Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
 \__/
  ^^                      SYGNATURA ZASTĘPCZA
QRCZAK