Previously I posted a naive implementation of an algorithm to solve Project Euler problem 1 with Easy68k. Below is a slightly cleverer version which takes advantage of some properties of arithmetic progressions.
In a nutshell, we work out the sum of elements which are multiples of 3, and the sum of elements which are multiples of 5. This also includes the sum of elements which are multiples of 15, so we subtract that value for the total.
The number of instructions overall is larger, but the other version takes 335308 cycles to reach a result, while this version takes only 1308 – a whopping 256% speed increase!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | org $1000 start: ; Calculate sum of multiples of 3 and 5 between ; 0 and 999. calculates the sum of an arithmetic ; progression. ; sum = sumprogression(3) + sumprogression(5) - sumprogression(15) ; get progressive sum for 0..999 step 3 move.l #3,d0 move.l #0,d1 move.l #limit,d2 bsr sumprogression move.l d0,d6 ; get progressive sum for 0..999 step 5 move.l #5,d0 bsr sumprogression add.l d0,d6 ; get progressive sum for 0..999 step 15 move.l #15,d0 bsr sumprogression sub.l d0,d6 move.l #3,d0 ; output result move.l d6,d1 trap #15 move.b #9,d0 ; halt simulator trap #15 ; Works out the sum of an arithmetic progression ; of s(n) = s(n - 1) + d0, starting at d1 up to d2 ; result is stored in d0 sumprogression: ; Save registers movem.l d1,-(a7) movem.l d2,-(a7) movem.l d3,-(a7) ; Caculate the number of terms in the range ; n = max value / step ; d3 = d2 / d0 move.l d2,d3 divu d0,d3 swap d3 ; clear remainder clr.w d3 swap d3 ; Get the largest term in the range ; nth term = step + ((n - 1) * step) ; d2 = d0 + ((d3 - 1) * d0) move.l d3,d2 ; Calculate n - 1 sub.l #1,d2 mulu d0,d2 ; (n - 1) * step add.l d0,d2 ; step + ((n - 1) * step) ; Find the sum ; sum = n * (step + nth term) / 2 ; d0 = (d3 * (d0 + d2)) / 2 add.l d2,d0 ; step + nth term mulu d3,d0 ; n * (step + nth term) asr.l #1,d0 ; (n * (step + nth term)) / 2 ; Restore register values movem.l (a7)+,d3 movem.l (a7)+,d2 movem.l (a7)+,d1 rts limit equ 999 ; We are searching for numbers between 0 and 999. end start |
It was much, much easier to write this in C# first and then translate it to assembly, and it also helped double check the result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace Euler1B { class Program { static int ProgressionSum(int step, int max) { int n = (max) / step; int nth = step + ((n - 1) * step); return n * (step + nth) / 2; } static void Main(string[] args) { int sum3 = ProgressionSum(3, 999); int sum5 = ProgressionSum(5, 999); int sum15 = ProgressionSum(15, 999); int total = sum3 + sum5 - sum15; Console.WriteLine(ProgressionSum(3, 999) + ProgressionSum(5, 999) - ProgressionSum(15, 999)); Console.ReadLine(); } } } |