Karatsuba-Algorithmus

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8807
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Karatsuba-Algorithmus

Beitrag von NicTheQuick »

Hallo! Da wir letztens eine kleine Vorlesung über das Multiplizieren großer
Zahlen gehört haben, habe ich die vorgestellte Methode von Karatsuba
nach PureBasic übersetzt und mit der normalen Schulmethode verglichen.

Hier also der leider gering kommentierte Code: (getestet mit PB V4.01 Linux)

Code: Alles auswählen

EnableExplicit

Structure bignum
  *v
  n.l
EndStructure

Procedure Convert2bignum_10(a.s, *bn.bignum = 0) ;funktioniert nur mit base = 10
  Protected *c.character, *cx.long, *alloc = *bn
  
  If *bn = 0
    *bn = AllocateMemory(SizeOf(bignum))
    If *bn = 0 : ProcedureReturn #False : EndIf
  EndIf
  
  a = Trim(a)
  
  *bn\n = Len(a)
  *bn\v = AllocateMemory(*bn\n * SizeOf(long))
  If *bn\v = 0
    If Not *alloc : FreeMemory(*bn) : EndIf
    ProcedureReturn #False
  EndIf
  
  *c = @a
  *cx = *bn\v + (*bn\n - 1) * SizeOf(long)
  While *c\c
    *cx\l = *c\c - '0'
    *c + SizeOf(character)
    *cx - SizeOf(long)
  Wend
  
  ProcedureReturn *bn
EndProcedure

Procedure.s Convert2Str_10(*bn.bignum) ;funktioniert nur mit base = 10
  Protected a.s = Space(*bn\n), *cx.long, *c.character, t.l = #False, n.l = *bn\n
  
  *c = @a
  *cx = *bn\v + (*bn\n - 1) * SizeOf(long)
  While n
    *c\c = *cx\l + '0'
    If t Or *cx\l Or n = 1
      *c + SizeOf(character)
      t = #True
    EndIf
    n - 1
    *cx - SizeOf(long)
  Wend
  *c\c = 0
  
  ProcedureReturn a
EndProcedure

Global base.l = 10

Procedure CreateBignum(n.l, *bn.bignum = 0) ;Erstellt eine Bignum oder ändert ihre Größe
  Protected *bnt = *bn
  
  If Not *bn
    *bn = AllocateMemory(SizeOf(bignum))
    If *bn = 0 : ProcedureReturn #False : EndIf
  EndIf
  
  If Not *bn\v
    *bn\n = n
    *bn\v = AllocateMemory(n * SizeOf(long))
    If *bn\v = 0
      If Not *bnt : FreeMemory(*bn) : EndIf
      ProcedureReturn #False
    EndIf
  Else
    *bn\n = n
    *bnt = ReAllocateMemory(*bn\v, n * SizeOf(long))
    If Not *bnt : ProcedureReturn #False : EndIf
    *bn\v = *bnt
  EndIf
  
  ProcedureReturn *bn
EndProcedure

;Beispiel:
; - Sum(111, 33, 2) = 111 + 3300 = 1411
Procedure Sum(*a.bignum, *b.bignum, shift.l = 0) ;Summiert *a und *b und speichert das Ergebnis in *a, na muss >= nb + shift sein, sonst falsches Ergebnis
  Protected *ca.long, *cb.long, carry.l, na.l, nb.l
  
  *ca = *a\v + shift * SizeOf(long)
  *cb = *b\v
  
  carry = 0
  na = *a\n - shift
  nb = *b\n
  While na
    *ca\l + carry
    If nb > 0 : *ca\l + *cb\l : EndIf
    carry = *ca\l / base
    *ca\l % base
    *ca + SizeOf(long) : *cb + SizeOf(long)
    na - 1
    nb - 1
  Wend
  
  If carry
    *a\n + 1
    *a\v = ReAllocateMemory(*a\v, *a\n * SizeOf(long))
    If Not *a\v : Debug "Out of memory" : End : EndIf
    PokeL(*a\v + (*a\n - 1) * SizeOf(long), carry)
  EndIf
  
  ProcedureReturn carry
EndProcedure
Procedure Diff(*a.bignum, *b.bignum) ;Subtrahiert *b von *a und speichert das Ergebnis in *a, *a muss >= *b sein
  Protected *ca.long, *cb.long, carry.l, na.l, nb.l, t.l
  
  *ca = *a\v
  *cb = *b\v
  
  carry = 0
  na = *a\n
  nb = *b\n
  While na
    If nb > 0 : carry + *cb\l : EndIf
    If *ca\l - carry => 0
      *ca\l - carry
      carry = 0
    Else
      *ca\l - carry + base
      carry = 1
    EndIf
    na - 1 : nb - 1
    *ca + SizeOf(long) : *cb + SizeOf(long)
  Wend
EndProcedure

Procedure MulSchool(*a.bignum, *b.bignum, *s.bignum = 0) ;multipliziert *a mit *b nach der Schulmethode und speichert Ergebnis in *s
  Protected *ca.long, s.bignum, n.l = *a\n + *b\n, *cb.long, t.bignum, na.l, nb.l, *ct.long, carry.l, *st = *s
  
  If *s = 0 : *s = CreateBignum(n)
  ElseIf *s\n < n : *s = CreateBignum(n, *s) : EndIf
  If *s = 0 : ProcedureReturn #False : EndIf
  
  t\v = AllocateMemory(n * SizeOf(long))
  If t\v = 0
    If Not *st : FreeMemory(*s) : EndIf
    FreeMemory(*s\v)
    ProcedureReturn #False
  EndIf
  t\n = n
  
  *cb = *b\v
  nb = 0
  While nb < *b\n
    *ca = *a\v : na = *a\n
    *ct = t\v + nb * SizeOf(long)
    carry = 0
    While na
      *ct\l = *ca\l * *cb\l + carry
      carry = *ct\l / base
      *ct\l % base
      *ct + SizeOf(long)
      *ca + SizeOf(long)
      na - 1
    Wend
    *ct\l = carry
    Sum(*s, t)
    
    PokeL(t\v + nb * SizeOf(long), 0)
    nb + 1
    *cb + SizeOf(long)
  Wend
  
  FreeMemory(t\v)
  
  ProcedureReturn *s 
EndProcedure

; Beispiele:
; - a = 123456
;   Copy(a, b, 3) ==> b = 123
; - a = 123456
;   Copy(a, b, -3) ==> b = 456 
Procedure Copy(*s.bignum, *d.bignum, shift.l = 0) ;kopiert *s in *d (*d muss allokiert sein)
  Protected *cd.long, nd.l, *cs.long, ns.l
  
  If *s\n <= shift Or *s\n + shift <= 0
    If Not *d\v
      *d\v = AllocateMemory(SizeOf(long))
      If Not *d\v : ProcedureReturn #False : EndIf
      *d\n = 1
    EndIf
    nd = *d\n
    *cd = *d\v
    While nd
      *cd\l = 0
      *cd + SizeOf(long) : nd - 1
    Wend
  ElseIf shift < 0
    shift = -shift
    If *s\n < shift : shift = *s\n : EndIf
    If Not *d\v
      *d\v = AllocateMemory(shift * SizeOf(long))
      If Not *d\v : ProcedureReturn #False : EndIf
      *d\n = shift
    EndIf
    *cd = *d\v
    *cs = *s\v
    While shift
      *cd\l = *cs\l
      *cd + SizeOf(long) : *cs + SizeOf(long)
      shift - 1
    Wend
  Else
    If Not *d\v
      *d\n = *s\n - shift
      *d\v = AllocateMemory(*d\n * SizeOf(long))
      If Not *d\v : ProcedureReturn #False : EndIf
    EndIf
    *cs = *s\v + shift * SizeOf(long)
    *cd = *d\v
    nd = *d\n : ns = *s\n - shift
    While nd
      If ns > 0
        *cd\l = *cs\l
      Else
        *cd\l = 0
      EndIf
      *cd + SizeOf(long) : *cs + SizeOf(long)
      nd - 1 : ns - 1
    Wend
  EndIf
  
  ProcedureReturn #True
EndProcedure

Procedure Mul(*a.bignum, *b.bignum, *s.bignum = 0) ;Multipliert *a und *b nach Karatsuba und speichert Ergebnis in *s oder gibt *s zurück
  Protected ah.bignum, bh.bignum, al.bignum, bl.bignum, p1.bignum, p3.bignum, n.l, n2.l
  
  n = *a\n : If *b\n > n : n = *b\n : EndIf ;n = Max(*b\n, *a\n)
  If n <= 3 ;Bei weniger als 4 Stellen nutze die Schulmethode
    If *s = 0 : *s = CreateBignum(2 * n)
    ElseIf *s\n < 2 * n : CreateBignum(2 * n, *s) : EndIf
    MulSchool(*a, *b, *s)
    ProcedureReturn *s
  EndIf
  n2 = n / 2
  If n & 1 : n2 + 1 : EndIf ;Wenn n ungerade war, dann addiere zu n2 eins
  CreateBignum(n, p1) ;Reserviert Speicher für Zwischenergebnisse
  CreateBignum(n, p3)
  If *s = 0 : *s = CreateBignum(2 * n) ;Reserviert Speicher für Zwischen- und Endergebnis
  ElseIf *s\n < 2 * n : CreateBignum(2 * n, *s) : EndIf
  
  Copy(*a, ah, n2) ;Nimmt obere und untere Hälfte von *a und *b und speichert sie in ah, al, bh, bl
  Copy(*a, al, -n2)
  Copy(*b, bh, n2)
  Copy(*b, bl, -n2)
  
  Mul(ah, bh, p1) ;p1 = ah * bh
  Mul(al, bl, *s) ;*s = al * bl
  
  Sum(al, ah) ;al = al + ah
  Sum(bl, bh) ;bl = bl + bh
  Mul(al, bl, p3) ;p3 = al * bl
  
  Diff(p3, p1) ;p3 = p3 - p1
  Diff(p3, *s) ;p3 = p3 - *s
  
  Sum(*s, p3, n2) ;*s = *s + p3 * base^n2
  Sum(*s, p1, 2 * n2) ;*s = *s + p1 * base^(2 * n2)
  
  ProcedureReturn *s
EndProcedure

Define.bignum a, b, r1, r2
Define s1.s, s2.s, n.l, time1.l, max.l, time2.l, s.s
max = 10000   ;10000: Mul()->1920, MulSchool()->7016
s = "" : For n = 1 To max : s + Str(Random(9)) : Next : Convert2bignum_10(s, a)
s = "" : For n = 1 To max : s + Str(Random(9)) : Next : Convert2bignum_10(s, b)

;Convert2bignum_10("84232332233", a)
;Convert2bignum_10("1532664392", b)

time1 = ElapsedMilliseconds()   ;Multipliziert nach Karatsuba
  Mul(a, b, r1)
time1 = ElapsedMilliseconds() - time1

time2 = ElapsedMilliseconds()   ;Multipliziert nach Schulmethode
  MulSchool(a, b, r2)
time2 = ElapsedMilliseconds() - time2

s1 = Convert2Str_10(r1)
s2 = Convert2Str_10(r2)
If OpenWindow(0, 0, 0, 400, 80, "Ergebnis", #PB_Window_ScreenCentered)
  If CreateGadgetList(WindowID(0))
    TextGadget(0, 0, 0, 400, 20, "Zeit (Karatsuba): " + Str(time1) + " ms")
    StringGadget(1, 0, 20, 400, 20, s1)
    TextGadget(2, 0, 40, 400, 20, "Zeit (Schulmethode): " + Str(time2) + " ms")
    StringGadget(3, 0, 60, 400, 20, s2)
    Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
  EndIf
  CloseWindow(0)
EndIf