Re: big number arithmetic

Terje Mathisen <[email protected]> Thu, 22 Mar 2007 21:22:00 +0100
Newsgroups gmane.comp.djb.bignum.devel
Organization Hydro
Message-ID <[email protected]>
[email protected] wrote:
> First of all, portability is an issue. I cannot afford the price of
> coding in assembler. So i sticked with C.
> 
> For evaluating the (a0 - a1)(b1 - b0) it could be that some of the
> members lead to a positive value although setting the bit sign. b.v:
> 
> a0 = 2 ^ 32 - 1, a1 = 0. How to handle in that cases ? Does anybody
> have any suggestion ?

As several others have already said: Don't do it!!!!

An unsigned integer MUL is only 3-5 times slower than ADD/SUB on most 
architectures, and the MUL is sometimes pipelined as well, making the 
cost even closer to a simple operation.

Using Kara to save a single MUL will _not_ be a win: A single missed 
branch is about twice as costly as a MUL.

Anyway, here's your portable C code:

uint64_t mul64x64_to_128(uint64_t a, uint64_t b; uint64_t *high)
{
   uint32_t al, ah, bl, bh;
   uint64_t t0,

   /* Separate out all four 32-bit words: */
   al = (uint32_t) (a & 0xffffffff); ah = (uint32_t) (a >> 32);
   bl = (uint32_t) (b & 0xffffffff); bh = (uint32_t) (b >> 32);

   /* Calculate the four 32x32->64 MULs: */
   t0 = (uint64_t) al * (uint64_t) bl;
   t1 = (uint64_t) al * (uint64_t) bh;
   t2 = (uint64_t) ah * (uint64_t) bl;
   t3 = (uint64_t) ah * (uint64_t) bh;

   /* Add & propagate carries: */
   c1 = (t0 >> 32) + (t1 & 0xffffffff) + (t2 & 0xffffffff);
   t0 = (t0 & 0xffffffff) | (c1 << 32);
   t3 += (t1 >> 32) + (t2 >> 32) + (c1 >> 32);

   *high = t3;
   return t0;
}


Terje
> 
> Thanks in advance.
> 
> On 3/22/07, Buhrow, Benjamin <[email protected]> wrote:
>> I agree with Felix that you should stick with the straightforward method
>> for small multiplications such as 64x64.  Assembler level instructions
>> will probably be the fastest.  These C routines are strictly for if you
>> can't (for portability) or don't want to write assembly.  Libraries like
>> GMP usually use assembly for these "low level" tasks, and only utilize
>> Karatsuba or FFT if the number of bits to be multiplied is huge.
>>
>> As far as your sign handling, you are throwing away information by
>> casting to int's and then multipling.  What's to stop (a0-a1) or (b1-b0)
>> from being greater than 2^31 ?
>>
>> Just do the subtraction with a1 and a0 as uint64 values and then
>> multiply.  If the result is negative (which requires if statements...
>> slow slow slow in a time critical routine such as this), then the
>> magnitude can be found like so:
>>
>> -u = ~u + 1, where ~ is bitwise not.
>>
>> Hope this helps,
>> - ben.
>>
>> > -----Original Message-----
>> > From: John Nietzsche [mailto:[email protected]]
>> > Sent: Thursday, March 22, 2007 11:30 AM
>> > To: Buhrow, Benjamin
>> > Cc: [email protected]
>> > Subject: Re: big number arithmetic
>> >
>> > Thank you all a lot!
>> >
>> > I would like to go for karatsuba at a first try. I have
>> > implemented a code to multiple two 64 bit number on intel P4
>> > machine. Here it is.
>> >
>> > (a0 + a1 * 2 ^ 32) * ( b0 + b1 * 2 ^ 32) = a0b0 + (a0b0 + a1b1 + (a0 -
>> > a1)*(b1 - b0)) * 2 ^ 32 + a1b1 * 2 ^ 64.
>> >
>> > the problem is that i am having a hard time to implement sign
>> > manipulation. Does anybody here have a function that
>> > implements such algorithm (karatsuba).
>> >
>> > Here is mine, but it is buggy.
>> >
>> > typedef unsigned long long xadk64_t;
>> > typedef unsigned xadk32_t;
>> > typedef int adk32_t;
>> >
>> > void
>> > karatsuba(xadk64_t * const r, xadk64_t a, xadk64_t b) {
>> >         xadk32_t        a0, a1, b0, b1;
>> >         xadk64_t        x, y;
>> >
>> >         a0 = a, a1 = a >> 32, b0 = b, b1 = b >> 32;
>> >         a = (xadk64_t)a0 * a1, b = (xadk64_t)b0 * b1;
>> >         x = (xadk64_t)(adk32_t)(a0 - a1) *
>> > (xadk64_t)(adk32_t)(b1 - b0);
>> >         if ((y = a + b) < b) b += (xadk64_t)1 << 32;
>> >         x += y;
>> >         if ((a += x << 32) < x << 32) b++;
>> >         b += x >> 32;
>> >         r[0] = a, r[1] = b;
>> > }
>> >
>> > I am really having a bad time trying to implement it correctly.
>> > Any code anyone has that implements the above?
>> >
>> > Thanks a lot for your time and cooperation.
>> >
>> > Best regards.
>> >
>> > On 3/22/07, Buhrow, Benjamin <[email protected]> wrote:
>> > > To elaborate on Terje's comment about combining shorter building
>> > > blocks...
>> > >
>> > > Assuming the word size of your cpu is 64 bits, form upper
>> > and lower 32
>> > > bit words from each 64 bit value:
>> > > A_upper = a64  >> 32
>> > > A_lower = a64 & 0x00000000FFFFFFFF
>> > > B_upper = b64 >> 32
>> > > B_lower = b64 & 0x00000000FFFFFFFF
>> > >
>> > > So we have that a64 = A_lower + A_upper * 2^32, and b64 = B_lower +
>> > > B_upper * 2^32
>> > >
>> > > The product is
>> > > a64*b64 = (A_lower + A_upper * 2^32) * (B_lower + B_upper * 2^32)
>> > >
>> > > Expanding:
>> > > a64*b64 = (A_lower * B_lower) + (A_lower * B_upper)*2^32 +
>> > (A_upper *
>> > > B_lower)*2^32 + (A_upper * B_upper)*2^64
>> > >
>> > > Each term in parentheses is a product of 32 bit numbers,
>> > and so will
>> > > fit in the machine's 64 bit word.
>> > >
>> > > The multiplication function will return a product (lower 64
>> > bits) and
>> > > a carry (upper 64 bits).  Here's an example I wrote in C
>> > for a machine
>> > > with 32 bit words:
>> > >
>> > > void mul(unsigned long u, unsigned long v, unsigned long *product,
>> > > unsigned long *carry) {
>> > >         unsigned long x0,x1,y0,y1;
>> > >         unsigned long ax0,ax1,ay1,az1;
>> > >
>> > >         //split into half words
>> > >         x0 = u & 0x0000FFFF;
>> > >         x1 = u >> 16;
>> > >         y0 = v & 0x0000FFFF;
>> > >         y1 = v >> 16;
>> > >
>> > >         //half words for product calculation
>> > >         ax0 = (x0*y0) & 0x0000FFFF;
>> > >         ax1 = (x0*y0) >> 16;
>> > >         ay1 = (x1*y0) & 0x0000FFFF;
>> > >         az1 = (x0*y1) & 0x0000FFFF;
>> > >
>> > >         //calculate product
>> > >         *carry = ax1+ay1+az1;
>> > >         *product = ax0 + ((*carry & 0x0000FFFF) << 16);
>> > >         *carry = (*carry) >> 16;
>> > >
>> > >         //calculate carry
>> > >         *carry += ((x1*y0) >> 16) + ((x0*y1) >> 16) + y1*x1;
>> > >
>> > >         return;
>> > > }
>> > >
>> > > It should be easy to modify it for 64 bit operands.
>> > >
>> > > For generic N bit multiplies, there are a number of methods
>> > depending
>> > > on how big your numbers are.  In each case, the input numbers are
>> > > represented as arrays of machine words.  Smallish numbers can use
>> > > straightforward O(N^2) gradeschool methods and use the
>> > above code for
>> > > each individual multiply operation.  Bigger numbers can be
>> > tackled by
>> > > Karatsuba, Toom-Cook, or FFT methods.  Knuth TAOCP vol 2,
>> > or the GMP
>> > > website might provide more details.
>> > >
>> > > Regards,
>> > > - ben.
>> > >
>> > >
>> > >
>> > > > -----Original Message-----
>> > > > From: [email protected]
>> > > > [mailto:[email protected]]
>> > > >  On Behalf Of Terje Mathisen
>> > > > Sent: Thursday, March 22, 2007 7:19 AM
>> > > > To: [email protected]
>> > > > Cc: [email protected]
>> > > > Subject: Re: big number arithmetic
>> > > >
>> > > > [email protected] wrote:
>> > > > > Dear list members,
>> > > > >
>> > > > > i am trying to imlement multiplication function for two 64
>> > > > bit number.
>> > > > > I wondered which would it be the best possible
>> > algorithm for such
>> > > > > a feat? May someone suggest one?
>> > > > > After have implemented, what about make it generic, i.e.,
>> > > > for n bits?
>> > > >
>> > > > What kind of cpu are you working on?
>> > > >
>> > > > Do you need a 128-bit full product, or just the low half?
>> > > >
>> > > > The latter is easy, since any conforming C(++) compiler has to
>> > > > support
>> > > > t_uint64 and t_int64 these days.
>> > > >
>> > > > If your building blocks are shorter multiplies, maybe
>> > > > 32x32->64, then you can combine those to generate the full
>> > > > product you need.
>> > > >
>> > > > Assuming you have a cpu with no multiplication support at
>> > all, then
>> > > > you need to do some more work:
>> > > >
>> > > > Either a full 64-iteration loop using shift & add, or you can
>> > > > construct a small multiplier using lookup tables:
>> > > >
>> > > >   (a+b)^2 = a^2 + 2ab + b^2
>> > > >   (a-b)^2 = a^2 - 2ab + b^2
>> > > >
>> > > > so (a+b)^2 - (a-b)^2 = 4ab.
>> > > >
>> > > > With a lookup table of the first 2N squares, you can use this to
>> > > > multiply 2 N-bit numbers!
>> > > >
>> > > > unsigned mul8x8(unsigned a, unsigned b) {
>> > > >    static unsigned square[512] = {0,1,4,9,16,25,36,49....};
>> > > >    if (a < b) { unsigned t = a; a = b; b = t};
>> > > >    unsigned prod = (square[a+b] - square[a-b]) >> 2;
>> > > >    return prod;
>> > > > }
>> > > >
>> > > > If we note that the sum and difference of two even
>> > numbers will both
>> > > > be even, the sum/diff of an odd and an even number will
>> > both be odd,
>> > > > and the square of an odd number is odd, then we realize that the
>> > > > table can store the truncated half of each square, it will still
>> > > > come out correct, and we'll only need a single shift at the end.
>> > > >
>> > > > Terje
>> > > >
>> > > > --
>> > > > - <[email protected]>
>> > > > "almost all programming can be viewed as an exercise in caching"
>> > > >
>> > >
>> >
>>
> 
> 


-- 
- <[email protected]>
"almost all programming can be viewed as an exercise in caching"