Russian Peasant Multiplication
Go to file
2020-02-05 09:37:54 -08:00
disk Raw Applesoft BASIC disk files 2016-08-18 18:21:06 -07:00
blank_prontodos.dsk Added bootable blank ProntoDOS .dsk 2016-08-18 17:41:35 -07:00
build.sh Added build script 2016-08-18 17:43:21 -07:00
ca65_fixes.inc Assembly ca65 version 2016-08-18 17:42:31 -07:00
README.MD Add other bases section 2020-02-05 09:37:54 -08:00
rpm_ca65.s Assembly ca65 version 2016-08-18 17:42:31 -07:00
rpm_m32.s Assembly merlin 32 version 2016-08-18 17:42:48 -07:00
rpm_oop.js
rpm_proc.js Procedural Javascript - optimize Shr1() 2016-08-18 10:03:46 -07:00
rpm.bas Applesoft BASIC normal and debug versions 2016-08-18 18:20:12 -07:00
rpm.debug.bas Applesoft BASIC normal and debug versions 2016-08-18 18:20:12 -07:00
rpm.dsk Raw Applesoft BASIC disk files 2016-08-18 18:21:06 -07:00
tasc_disasm_clean.s Cleanup bad Vim remove trailing whitespace 2016-08-29 20:19:53 -06:00
tasc_disasm_org.s Cleanup bad Vim remove trailing whitespace 2016-08-29 20:19:53 -06:00

Russian Peasant Multiplication

From Assembly to Basic to C to Javascript!

Here are my implementations of Russian Peasant Multiplication implemented in various languages:

  • 6502 Assembly Language (Both ca65 and merlin32 sources)
  • Applesoft BASIC
  • JavaScript (Procedural version)
  • JavaScript (OOP version)

A .dsk image has been provided as an convenience.

To see how much faster the Assembly version is then the BASIC version:

RUN  RPM.BAS
BRUN RPM.BIN

And enter in 123456789 * 987654321 respectively for A and B ...

Version Time
Applesoft 33 s
Assembly ~1 s

So what the heck is it?

An alternative algorithm to implement multiplication using only:

  • bit-shifts (left and right), and
  • addition.

Algorithm

  1. Initialize Sum <- zero. In C nomenclature: Sum = 0;
  2. If B is odd then add A to Sum. In C nomenclature: Sum += A;
  3. Multiply A by 2 -- that is, Shift A left by one. In C nomenclature: A <<= 1;
  4. Divide B by 2 -- that is, Shift B right by one. In C nomenclature: B >>= 1;
  5. If B is zero then STOP. while( b ) { ... }
  6. Goto step 2

Paste the following program into an online C compiler

#include <stdio.h>

int RPM( int a, int b )
{
    int sum = 0;

    while( b )
    {
        if( b & 1 )
            sum += a;

        a <<= 1;
        b >>= 1;
    }

    return sum;
}

int main()
{
    return printf( "%d\n", RPM( 86, 57 ) );
}

Examples

Example of "traditional" multiplication:

In base 10:

             86
           x 57
           ----
            602
           430
           ====
           4902

In base 2:

        01010110  (86)
        00111001  (57)
        --------
        01010110  (86 * 2^0 =   86)
       00000000   (86 * 2^1 =  172) <- wasted work, partial sum = 0
      00000000    (86 * 2^2 =  344) <- wasted work, partial sum = 0
     01010110     (86 * 2^3 =  688)
    01010110      (86 * 2^4 = 1376)
   01010110       (86 * 2^5 = 2752)
  ==============
  01001100100110  (4902 = 86*2^0 + 86*2^3 + 86*2^4 + 86*2^5)

Example of Russian Peasant multiplication:

In Base 10:

               A          B   B Odd?   Sum =    0
              86         57   Yes      + A =   86
      x 2 =  172   / 2 = 28   No           =   86
      x 2 =  344   / 2 = 14   No           =   86
      x 2 =  688   / 2 =  7   Yes      + A =  774
      x 2 = 1376   / 2 =  3   Yes      + A = 2150
      x 2 = 2752   / 2 =  1   Yes      + A = 4902

In Base 2:

               A          B   B Odd?   Sum = 0
        01010110   00111001   Yes      + A = 00000001010110
       010101100   00011100   No           = 00000001010110
      0101011000   00001110   No           = 00000001010110
     01010110000   00000111   Yes      + A = 00001100000110
    010101100000   00000011   Yes      + A = 00100001100110
   0101011000000   00000001   Yes      + A = 01001100100110

In Base 8:

                A          B   B Odd?   Sum =     0
              126         71   Yes      + A =   126
       x 2 =  254   / 2 = 34   No           =   126
       x 2 =  530   / 2 = 16   No           =   126
       x 2 = 1260   / 2 =  7   Yes      + A =  1406
       x 2 = 2540   / 2 =  3   Yes      + A =  4146
       x 2 = 5300   / 2 =  1   Yes      + A = 11446

In Base 16:

               A          B   B Odd?   Sum =    0
              56         39   Yes      + A =   56
       x 2 =  AC   / 2 = 1C   No           =   56
       x 2 = 158   / 2 =  E   No           =   56
       x 2 = 2B0   / 2 =  7   Yes      + A =  306
       x 2 = 560   / 2 =  3   Yes      + A =  866
       x 2 = AC0   / 2 =  1   Yes      + A = 1326

Bases

Does this algorithm work in other bases such as 2, 8, or 16?

Consider the question:

Q. Does multipling by 2 work in other bases? A. Yes.

Q. Why? A. When we write a number in a different base we have the same representation but a different presentation.

Adding, Multiplying, Dividing all function the same regardless of which base we use.

Efficiency

For a "BigInt" or "BigNumber" library this is NOT the most efficient (*) way to multiply numbers as it doesn't scale (**). However, it is rather trivial to implement. You only need a few functions:

  • isEven()
  • isZero()
  • Shl()
  • Shr()
  • AddTo()

Notes:

(*) Almost everyone uses FFT (Fast Fourier Transforms), Toom, and/or Karatsuba for multiplication. For example GMP, GNU Multiple Precision arithmetic library, uses seven different multiplication algorithms!:

  • Basecase
  • Karatsuba
  • Toom-3
  • Toom-4
  • Toom-6.5
  • Toom-8.5
  • FFT

(**) What do we mean by "Doesn't scale"? As the input numbers uses more bits we end up doing more work other other algorithms.

References