Mathe-Experimente mit Python

Der Modulo-Operator

Der Modulo-Operator % steht für „Rest nach ganzzahliger Division“.
10 % 3 fragt also: Was bleibt über, wenn ich 10 durch 3 teile?

10 % 3 ergibt 1
15 % 4 ergibt 3
21 % 7 ergibt 0

Wenn das Ergebnis von x % y gleich 0 ist, bedeutet das: y ist ein Teiler von x!

Primzahlen

Primzahlen sind natürliche Zahlen größer als 1, die ausschließlich durch 1 und sich selbst ohne Rest teilbar sind.

Der Fundamentalsatz der Arithmetik

Jede natürliche Zahl größer als 1 lässt sich als Produkt von Primzahlen darstellen.
Diese Darstellung ist eindeutig.
4 = 2 * 2
12 = 2 * 2 * 3
100 = 2 * 2 * 5 * 5
17 = 17

Naiver Primzahl Test

def ist_primzahl(n):

    if n <= 1:
        return False
    
    for teiler in range(2, n):
        if n % teiler == 0:
            return False
    
    return True

Naiver Primzahl Test – kommentierte Fassung

def ist_primzahl(n):
 
    if n <= 1:
        print("n ist kleiner oder gleich 1!")
        return False
     
    for teiler in range(2, n):
        print("Teiler:", teiler)
        if n % teiler == 0:
            print(n, "ist durch", teiler, "teilbar!")
            print("Daher kann", n, "keine Primzahl sein.")
            return False
     
    print("Alle möglichen Teiler wurden untersucht und keiner gefunden!")
    print("Daher muss", n, "eine Primzahl sein.")
    return True

Die Anwendung sieht dann so aus:

ist_prim(7) ergibt True
ist_prim(12) ergibt False

Dieser Test funktioniert, ist aber sehr umständlich.

Eine erste Verbesserung besteht darin, dass wir nur bis zur Wurzel der Zahl n die Teiler suchen.

Begründung:
Wenn eine Zahl n keine Primzahl ist, muss sie mindestens zwei Faktoren a und b haben.
Nehmen wir also an, dass die Zahl n genau zwei Primfaktoren hat.
Wenn wir zudem annehmen, dass a und b größer als die Wurzel aus n sind, dann wäre das Produkt größer als n.
Das ist ein Widerspruch.
Es muss also zumindest einer der Primfaktoren kleiner als die Wurzel aus n sein.

Das nennen wir übrigens einen Beweis durch Widerspruch

1. Wir nehmen das Gegenteil von dem an, was wir beweisen wollen
2. Wir zeigen, dass diese Annahme zu einem logischen Widerspruch führt
3. Daraus folgt, dass die Annahme falsch sein muss
4. Also muss die ursprüngliche Behauptung wahr sein

Wurzeln ziehen in Python

Die Wurzel einer Zahl n ist die Zahl, die mit sich selbst multipliziert n ergibt:

√n × √n = n
In Python gibt es mehrere Möglichkeiten, die Wurzel zu berechnen:
Variante 1: Potenzierung mit 0.5

wurzel = n ** 0.5

Variante 2: Mit dem math-Modul

import math
wurzel = math.sqrt(n)

Variante 3: Ganzzahlige Wurzel (abgerundet)

import math
wurzel = math.isqrt(n)

Für unseren Primzahltest brauchen wir die ganzzahlige Wurzel, da wir nur ganze Zahlen als Teiler testen. Deshalb verwenden wir int(n ** 0.5).

Verbesserter Primzahl Test I – Nur bis zur Wurzel

def ist_primzahl(n):

    if n <= 1:
        return False

    wurzel_von_n = int(n ** 0.5)
    
    for teiler in range(2, wurzel_von_n + 1):
        if n % teiler == 0:
            return False
    
    return True

Verbesserter Primzahl Test II – Vielfache von 2 auslassen

Vielfache von 2 können keine Primzahlen sein.
Und bei range können wir eine Schrittweite einstellen.
Jetzt müssen wir nur noch die 2 gesondert behandeln:

def ist_primzahl(n):
 
    if n <= 1:
        return False
    
    if n % 2 == 0:
        return False
    
    wurzel_von_n = int(n ** 0.5)
     
    for teiler in range(3, wurzel_von_n + 1, 2):
        if n % teiler == 0:
            return False
     
    return True

Das Sieb des Erasthotenes

Wir lassen in der letzten Version unseres Primzahltests die Vielfachen von 2 weg. Warum nicht auch die Vielfachen von 3, 5, 6 und so weiter? Das ist die Idee hinter einem Verfahren, dass sich „Das Sieb des Erasthothenes“ nennt.

Das Sieb ist nur dann von Nutzen, wenn wir einen Zahlenbereich von 2 bis zu einer bestimmten Obergrenze nach Primzahlen durchsuchen wollen.

Es funktioniert folgendermaßen:
1. Erstelle eine Liste aller Zahlen von 0 bis zur Grenze und markiere zunächst alle als „prim“ (= True).
2. Markiere 0 und 1 als „nicht prim“ (= False).
3. Beginne mit der kleinsten Primzahl 2.
4. Streiche alle Vielfachen dieser Primzahl (außer der Zahl selbst), indem du sie als „nicht prim“ (= False) markierst.
5. Suche die nächste Zahl, die noch als „prim“ markiert ist.
6. Wiederhole Schritt 4 und 5, bis du die Wurzel der Grenze erreicht hast.
7. Alle Zahlen, die noch als „prim“ markiert sind, sind die gesuchten Primzahlen.

Das klingt komplizierter, als es ist. Hier ist eine Visualisierung:
LINK: Visualisierung des Sieb des Erasthothenes

Primzahlsuche mit dem Sieb des Erasthotenes

def primzahlen_bis(grenze):
    if grenze < 2:
        return []
    
    ist_prim = [True] * (grenze + 1)
    ist_prim[0] = ist_prim[1] = False
    
    wurzel_von_grenze = int(grenze ** 0.5)
    
    for zahl in range(2, wurzel_von_grenze + 1):
        if ist_prim[zahl]:
            for vielfaches in range(zahl * zahl, grenze + 1, zahl):
                ist_prim[vielfaches] = False
    
    return [zahl for zahl in range(grenze + 1) if ist_prim[zahl]]

Primzahlsuche mit dem Sieb des Erasthotenes – kommentierte Fassung

def primzahlen_bis(grenze):
    
    if grenze < 2:
        return []
     
    ist_prim = [True] * (grenze + 1)
    print("ist_prim am Anfang der Funktion:")
    print(ist_prim)
    
    ist_prim[0] = ist_prim[1] = False
    print("ist_prim im nächsten Schritt:")
    print(ist_prim)
     
    wurzel_von_grenze = int(grenze ** 0.5)
     
    for zahl in range(2, wurzel_von_grenze + 1):
        print("untersucht wird", zahl)
        if ist_prim[zahl]:
            print(zahl, "ist Primzahl")
            for vielfaches in range(zahl * zahl, grenze + 1, zahl):
                ist_prim[vielfaches] = False
                print(ist_prim)
     
    return [zahl for zahl in range(grenze + 1) if ist_prim[zahl]]

Echte Teiler

Echte Teiler sind alle Teiler einer Zahl außer der Zahl selbst.
Beispiel: Die Zahl 12 hat die Teiler 1, 2, 3, 4, 6, 12. Die echten Teiler sind 1, 2, 3, 4, 6.

def echte_teiler(n):
    if n <= 1:
        return []
    
    ergebnis = []
    for zahl in range(1, n):
        if n % zahl == 0:
            ergebnis.append(zahl)
    
    return ergebnis

Vollkommene Zahlen

Vollkommene Zahlen sind Zahlen, bei denen die Summe der echten Teiler die Zahl selbst ergibt.
Sie werden auch perfekte Zahlen genannt.

Wir können mit dem folgenden Programm nach vollkommenen Zahlen im Bereich bis 10000 suchen:

def echte_teiler(n):
    if n <= 1:
        return []
    
    ergebnis = []
    for zahl in range(1, n):
        if n % zahl == 0:
            ergebnis.append(zahl)
    
    return ergebnis

for n in range(1, 10000):
    echte_teiler_von_n = echte_teiler(n)
    summe = sum(echte_teiler_von_n)
    if summe == n:
        print(n)
def echte_teiler_schnell(n):
    if n <= 1:
        return {}
     
    ergebnis = {1}
    
    grenze = int(n**0.5)
    
    for potentieller_teiler in range(2, grenze+1):
        if n % potentieller_teiler == 0:
            ergebnis.add(potentieller_teiler)
            korrespondierender_teiler = n // potentieller_teiler
            ergebnis.add(korrespondierender_teiler)
    
    return ergebnis

Begriffe, offene Fragen, Stichworte

– Gibt es eine ungerade vollkommene Zahl?
– Gibt es unendlich viele vollkommene Zahlen?
– Primzahlzwillinge
– Gibt es unendlich viele Primzahlzwillinge
– Collatz-Vermutung
– Goldbachsche Vermutung