Add files via upload

This commit is contained in:
Peter Norvig authored and GitHub committed 2026-01-07 23:51:50 -08:00
1 parent 051b5ed609
commit 8a40e42170
1 file changed
+195 -180
+195 -180
View File
@@ -23,9 +23,9 @@
" 37 is prime\n",
" 7 is prime\n",
"\n",
"Numbers like this are called [**truncatable primes**](https://en.wikipedia.org/wiki/Truncatable_prime). I thought I would write a program to find other truncatable primes.\n",
"Numbers like this are called [**truncatable primes**](https://en.wikipedia.org/wiki/Truncatable_prime). \n",
"\n",
"My function `left_truncatable_primes` below starts with the list of one-digit primes: [2, 3, 5, 7]. Then it places each possible digit 1–9 to the left of each of the one-digit primes, giving a list of two-digit candidates. From the candidates it filters out just the primes. If there are any new primes, it recursively adds digits to them, giving us three-digit primes, then four-digit primes, and so on, stopping when there are no more new primes. In the end, the function gathers up all the truncatable primes and returns them in sorted order."
"I thought I would write a program for this. My function `left_truncatable_primes` below starts with the list of one-digit primes: [2, 3, 5, 7]. Then it creates a list of one-digit-longer new primes by placing each possible digit 1–9 to the left of each of the one-digit primes, and checking if each resulting number is prime. If there are any new primes, it recursively adds digits to them, giving us three-digit primes, then four-digit primes, and so on, stopping when there are no more new primes. In the end, the function gathers up all the truncatable primes and returns them in sorted order."
]
},
{
@@ -35,13 +35,12 @@
"metadata": {},
"outputs": [],
"source": [
"from sympy import isprime # isprime checks if a number is a prime\n",
"from sympy import isprime # isprime(n) returns True if n is a prime\n",
"\n",
"def left_truncatable_primes(starting_primes=[2, 3, 5, 7]) -> list[int]:\n",
"def left_truncatable_primes(primes=[2, 3, 5, 7]) -> list[int]:\n",
" \"\"\"All left-truncatable primes, in ascending order.\"\"\"\n",
" candidates = [int(d + str(p)) for d in \"123456789\" for p in starting_primes]\n",
" new_primes = [n for n in candidates if isprime(n)]\n",
" return starting_primes + (left_truncatable_primes(new_primes) if new_primes else [])"
" new_primes = [dp for d in \"123456789\" for p in primes if isprime(dp := int(d + str(p)))]\n",
" return (primes + left_truncatable_primes(new_primes)) if new_primes else primes"
]
},
{
@@ -229,11 +228,10 @@
"metadata": {},
"outputs": [],
"source": [
"def right_truncatable_primes(starting_primes=[2, 3, 5, 7]) -> list[int]:\n",
"def right_truncatable_primes(primes=[2, 3, 5, 7]) -> list[int]:\n",
" \"\"\"All right-truncatable primes, in ascending order.\"\"\"\n",
" candidates = [10 * p + d for p in starting_primes for d in (1, 3, 7, 9)]\n",
" new_primes = list(filter(isprime, candidates))\n",
" return starting_primes + (right_truncatable_primes(new_primes) if new_primes else [])"
" new_primes = [pd for p in primes for d in (1, 3, 7, 9) if isprime(pd := 10 * p + d)]\n",
" return (primes + right_truncatable_primes(new_primes)) if new_primes else primes"
]
},
{
@@ -241,7 +239,7 @@
"id": "976c7f1f-79c5-48c3-8909-62b809993057",
"metadata": {},
"source": [
"(Note that I only try placing the digits (1, 3, 7, 9) to the right: placing an even digit or a 5 would always result in a composite number. Also, note that I don't have to do string concatenation to form the new candidates; it is simpler to do `10 * p + d`.)\n",
"(Note that I only try placing the digits (1, 3, 7, 9) to the right: placing an even digit or a 5 would always result in a composite number. Since there are fewer digits to add, I expect there to be fewer right-truncatable primes in total. Also, note that I don't have to do string concatenation to form the new candidates; it is simpler to do `10 * p + d`.)\n",
"\n",
"Let's find and count the right-truncatable primes:"
]
@@ -354,63 +352,16 @@
"\n",
"# Note on Primality Checking\n",
"\n",
"I was very impressed by the speed of `sympy.isprime`, so I wanted to look more deeply into the topic of primality checking. I'm going to write some prime-checking functions, so the first thing I will do is define a function to test that a prime checker correctly returns True when passed a prime and False when passed a composite number, for some given test cases."
"I was very impressed by the speed of `sympy.isprime`, and I wanted to examine the topic of primality checking. I'll start by defining `isprime_simple`, which follows the definition of a prime number almost verbatim:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "4f377075-e352-46f9-a716-f1de673a7086",
"metadata": {},
"outputs": [],
"source": [
"from typing import Callable, Iterable\n",
"\n",
"def test_prime_checker(checker: Callable, \n",
" primes=(2, 3, 5, 7, 11, 37, 73, 101, 11939, 65537, 117223, 7629137),\n",
" composites=(0, 1, 4, 6, 8, 9, 10, 256, 11939*11939, 11939*117223)) -> Callable:\n",
" \"\"\"Test that a primality checking function correctly handles some primes and composites.\"\"\"\n",
" for n in primes:\n",
" assert checker(n) == True, f'{n} should be prime'\n",
" for n in composites:\n",
" assert checker(n) == False, f'{n} should be composite'\n",
" return checker # This allows us to use @test_prime_checker as a @decorator"
]
},
{
"cell_type": "markdown",
"id": "c08ed813-2942-42da-aed3-d5044b0b403a",
"metadata": {},
"source": [
"Here's how we would use this to check the `isprime` function:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "008cd6fd-86eb-4b45-bea9-c030273c4eb1",
"metadata": {},
"outputs": [],
"source": [
"assert test_prime_checker(isprime)"
]
},
{
"cell_type": "markdown",
"id": "6b307b24-a93e-4353-9bee-fba739072bd1",
"metadata": {},
"source": [
"But I can also use `@test_prime_checker` as a decorator to test the function right where it is defined; I'll do that for `isprime_simple`, which follows the definition of a prime number almost verbatim:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "1d460931-3d70-485d-807f-8fb360ef4ef2",
"metadata": {},
"outputs": [],
"source": [
"@test_prime_checker\n",
"def isprime_simple(n: int) -> bool:\n",
" \"\"\"Simple primality checker. A prime number is defined as an integer greater than 1 \n",
" that cannot be evenly divided by any whole number other than 1 and itself.\"\"\"\n",
@@ -425,21 +376,20 @@
"source": [
"To test a *d*-digit prime we have to iterate through nearly 10<sup>d</sup> divisors, which will be noticeably slow for anything larger than about 7-digit numbers.\n",
"\n",
"We can speed things up a bit with two ideas, one small and one big. The small idea is to cut the run time in half by only checking the odd divisors. Once we determine that 2 is a prime and all other even numbers are composite, we only need to check odd divisors. The big idea is that we only need to check divisors up to √*n*, not up to *n*. If *n* is composite, then *n* is the product of two numbers, and one of them must be less than or equal to √*n*, so we can stop there."
"We can speed things up a bit with two ideas, one small and one big. The small idea is to cut the run time in half by only checking the odd divisors (after determining that 2 is a prime and all other even numbers are composite). The big idea is that we only need to check divisors up to √*n*, not up to *n*. If *n* is composite, then *n* is the product of two numbers, and one of them must be less than or equal to √*n*, so we can stop there."
]
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 11,
"id": "647fb66b-32ae-494a-8945-cf7358c2f958",
"metadata": {},
"outputs": [],
"source": [
"from math import sqrt\n",
"\n",
"@test_prime_checker\n",
"def isprime_faster(n: int) -> bool:\n",
" \"\"\"More sophisticated primality checker: go to square root, checking odd divisors only.\"\"\"\n",
" \"\"\"More sophisticated primality checker: check only odd divisors up to √n.\"\"\"\n",
" if n <= 10: # Handle small numbers up to 10\n",
" return n in (2, 3, 5, 7)\n",
" elif n % 2 == 0: # Even numbers other than 2 are composite\n",
@@ -458,37 +408,37 @@
"\n",
"Fortunately, [Pierre de Fermat](https://en.wikipedia.org/wiki/Pierre_de_Fermat) provided that breakthrough in 1640, [showing](https://en.wikipedia.org/wiki/Fermat_primality_test) that if *n* is prime and *a* is not divisible by *n*, then *a*<sup>(*n* - 1)</sup> ≡ 1 (mod *n*). \n",
"\n",
"That means we can check if *n* is prime by choosing a random *a* and testing if *a*<sup>(*n* - 1)</sup> ≡ 1 (mod *n*). If the test is false then *n* is definitely composite. If the test is true, then we're not sure: *n* might be prime, might be composite. But if we choose multiple values of *a* and they all give a remainder of 1 (mod *n*), then that is stronger evidence that *n* is probably prime. This is called a [Monte Carlo algorithm](https://en.wikipedia.org/wiki/Monte_Carlo_algorithm); an algorithm that uses randomization, and can sometimes be wrong.\n",
"Some examples:\n",
"That means we can check if *n* is prime by choosing a random *a* and testing if *a*<sup>(*n* - 1)</sup> ≡ 1 (mod *n*). If the test is false then *n* is definitely composite. If the test is true, then we're not sure: *n* might be prime, might be composite. But if we choose multiple values of *a* and they all give a remainder of 1 (mod *n*), then that is stronger evidence that *n* is probably prime. \n",
"For example:\n",
"\n",
"\n",
"|*n*|*a*|*a*<sup>(*n* - 1)</sup> (mod *n*)|Conclusion|\n",
"|--:|--:|:--:|:--|\n",
"|12|5|5|*12 is definitely composite*|\n",
"|221|18|1|*221 could be prime or composite*|\n",
"|221|2|16|*221 is definitely composite*|\n",
"|221|2|16|*try again with new **a** value: 221 is definitely composite*|\n",
"|5|2|1|*5 could be prime or composite*|\n",
"|5|3|1|*5 could be prime or composite*|\n",
"|5|4|1|*5 could be prime or composite, but we have a lot of evidence that it is prime*|\n",
"\n",
"\n",
"Here is an implementation:"
"This is called a [Monte Carlo algorithm](https://en.wikipedia.org/wiki/Monte_Carlo_algorithm); an algorithm that uses randomization, and can sometimes be wrong. Here is an implementation:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 12,
"id": "476282dd-a48a-4eb5-8fed-8495d4878ec2",
"metadata": {},
"outputs": [],
"source": [
"import random\n",
"from typing import Iterable\n",
"\n",
"def sample(lo: int, hi: int, k: int) -> Iterable[int]:\n",
" \"\"\"Randomly sample k integers from range(lo, hi), one by one.\"\"\"\n",
" return (random.randrange(lo, hi) for _ in range(k))\n",
"def sample(start: int, stop: int, k: int) -> Iterable[int]:\n",
" \"\"\"Randomly sample `k` integers from range(start, stop), one by one.\"\"\"\n",
" return (random.randrange(start, stop) for _ in range(k))\n",
" \n",
"@test_prime_checker\n",
"def isprime_fermat(n: int, k=20) -> bool:\n",
" \"\"\"n is probably a prime if this returns True; definitely composite if it returns False.\"\"\"\n",
" if n <= 10:\n",
@@ -502,7 +452,7 @@
"id": "1ec7bba6-ea89-4255-a73c-bc25a32da883",
"metadata": {},
"source": [
"Note that the built-in [modular exponentiation](https://en.wikipedia.org/wiki/Modular_exponentiation) function `pow(b, e, m)` computes *b*<sup>*e*</sup> (mod *m*), and does it in an efficient way that doesn't have to multiply *b* by itself *e* times, and doesn't have to deal with any numbers larger than *m*."
"(Note that `pow(a, n - 1, n)` efficiently computes *a*<sup>(*n* - 1)</sup> (mod *n*); more on this at the bottom of this notebook.)"
]
},
{
@@ -510,23 +460,22 @@
"id": "15ef6884-253d-4a3a-b58f-397ac73f552d",
"metadata": {},
"source": [
"**The problem** is that there are some composite numbers *n* for which *a*<sup>(*n* - 1)</sup> ≡ 1 (mod *n*) for some values of *a*, and thus `isprime_fermat` can lie: it can incorrectly report a **false prime** for a composite number *n*. \n",
"**The problem** is that there are some composite numbers *n* for which *a*<sup>(*n* - 1)</sup> ≡ 1 (mod *n*) for some values of *a*. In other words `isprime_fermat` can lie: it can incorrectly report a **false prime** for a composite number *n*. \n",
"\n",
"How common are these false primes? Here's a function to estimate the percentage of false primes in the range `lo` to `hi` when `isprime_fermat` is allowed `k` choices of the `a` parameter:"
"How common are these false primes? Here's a function to test a sample of numbers and report the the percentage of false primes when `isprime_fermat` is allowed `k` choices of the `a` parameter:"
]
},
{
"cell_type": "code",
"execution_count": 15,
"execution_count": 13,
"id": "45a11a93-9ef3-48d1-80fa-e0113041346e",
"metadata": {},
"outputs": [],
"source": [
"def false_prime_percent(lo: int, hi: int, k: int, repeat=100_000) -> float:\n",
" \"\"\"The estimated probability of a false prime from isprime_fermat(n, k) for n in range(lo, hi).\"\"\"\n",
" composites = (n for n in sample(lo, hi, repeat) if not isprime(n))\n",
" counts = Counter(isprime_fermat(n, k) for n in composites) \n",
" return 100 * counts[True] / (counts[True] + counts[False])"
"def false_prime_percent(samples: Iterable[int], k: int) -> float:\n",
" \"\"\"The estimated percentage of false primes from isprime_fermat(n, k) for n across the samples.\"\"\"\n",
" composites = [n for n in samples if not isprime(n)]\n",
" return 100 * sum(isprime_fermat(n, k) for n in composites) / len(composites)"
]
},
{
@@ -534,37 +483,37 @@
"id": "778c3a9b-dcc7-4ad2-9092-d66aecd1d93c",
"metadata": {},
"source": [
"We'll choose ranges that represent digit-lengths, and for now use 1 for the value of `k`:"
"We'll choose sample ranges that represent digit-lengths, sample 100,000 times for each digit-length, and for now use 1 for the value of `k`:"
]
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 14,
"id": "9eba96df-c298-4d71-b68a-3bf36bf15095",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{3: 1.5219868933421978,\n",
" 4: 0.44898977301072585,\n",
" 5: 0.15980955110048164,\n",
" 6: 0.02713174088102189,\n",
" 7: 0.007483109552723852,\n",
" 8: 0.003178740582981023,\n",
"{3: 1.4432622005587588,\n",
" 4: 0.46280016787849226,\n",
" 5: 0.12571126108244013,\n",
" 6: 0.0335378057620114,\n",
" 7: 0.007482149728504852,\n",
" 8: 0.0021168501270110076,\n",
" 9: 0.0,\n",
" 10: 0.0,\n",
" 11: 0.0,\n",
" 12: 0.0}"
]
},
"execution_count": 16,
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{d: false_prime_percent(10**(d-1), 10**d, k=1) for d in range(3, 13)}"
"{d: false_prime_percent(sample(10**(d-1), 10**d, 100_000), k=1) for d in range(3, 13)}"
]
},
{
@@ -572,12 +521,14 @@
"id": "f3fe25d6-5f0a-478b-90d0-6c89e092e92e",
"metadata": {},
"source": [
"We see that 3-digit composites are falsely called prime about 1.5% of the time, but onnce we get to 10-digit composites, false primes are very rare indeed. Let's see how we reduce false primes by allowing up to 30 choices of `a`:"
"We see that 3-digit composites are falsely called prime about 1.4% of the time, and 4-digit comnposites about 0.5%, but once we get to 10-digit composites, false primes are very rare indeed. \n",
"\n",
"Let's see how we reduce false primes by allowing `k=25` choices of `a`:"
]
},
{
"cell_type": "code",
"execution_count": 17,
"execution_count": 15,
"id": "8880c588-32f5-46e8-b59c-f9b49d492d3b",
"metadata": {},
"outputs": [
@@ -587,7 +538,7 @@
"{3: 0.0,\n",
" 4: 0.0,\n",
" 5: 0.0,\n",
" 6: 0.001082461951462406,\n",
" 6: 0.0,\n",
" 7: 0.0,\n",
" 8: 0.0,\n",
" 9: 0.0,\n",
@@ -596,13 +547,13 @@
" 12: 0.0}"
]
},
"execution_count": 17,
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{d: false_prime_percent(10**(d-1), 10**d, k=30) for d in range(3, 13)}"
"{d: false_prime_percent(sample(10**(d-1), 10**d, 100_000), k=25) for d in range(3, 13)}"
]
},
{
@@ -617,27 +568,27 @@
},
{
"cell_type": "code",
"execution_count": 18,
"execution_count": 16,
"id": "7871aaa7-af1c-462b-85bb-d46818ccabea",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{561: 55.4,\n",
" 1105: 68.8,\n",
" 1729: 77.0,\n",
" 2465: 76.2,\n",
" 2821: 76.6,\n",
" 6601: 81.7,\n",
" 8911: 80.0,\n",
" 41041: 68.2,\n",
" 101101: 70.3,\n",
" 825265: 59.4,\n",
" 321197185: 68.6}"
"{561: 54.7,\n",
" 1105: 69.8,\n",
" 1729: 74.0,\n",
" 2465: 72.0,\n",
" 2821: 75.2,\n",
" 6601: 79.7,\n",
" 8911: 78.4,\n",
" 41041: 69.8,\n",
" 101101: 74.0,\n",
" 825265: 61.4,\n",
" 321197185: 63.5}"
]
},
"execution_count": 18,
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
@@ -645,7 +596,7 @@
"source": [
"carmichael_numbers = (561, 1105, 1729, 2465, 2821, 6601, 8911, 41041, 101101, 825265, 321197185)\n",
"\n",
"{n: false_prime_percent(n, n + 1, k=1, repeat=1000) for n in carmichael_numbers}"
"{n: false_prime_percent([n] * 1000, k=1) for n in carmichael_numbers}"
]
},
{
@@ -653,12 +604,12 @@
"id": "4ecd2289-66d5-4b0f-ac00-5db0ad92dde9",
"metadata": {},
"source": [
"That seems seriously bad; they all are above 50% error rate. But if we set `k=50`, we eliminate most (and on some runs all) of the errors:"
"That seems seriously bad; they all are above 50% error rate. But if we set `k=40`, we eliminate most (and on some runs all) of the errors:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": 17,
"id": "de492047-77b6-46a3-8a77-46308f219efd",
"metadata": {},
"outputs": [
@@ -669,7 +620,7 @@
" 1105: 0.0,\n",
" 1729: 0.0,\n",
" 2465: 0.0,\n",
" 2821: 0.0,\n",
" 2821: 0.01,\n",
" 6601: 0.0,\n",
" 8911: 0.0,\n",
" 41041: 0.0,\n",
@@ -678,13 +629,13 @@
" 321197185: 0.0}"
]
},
"execution_count": 19,
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{n: false_prime_percent(n, n + 1, k=50, repeat=10_000) for n in carmichael_numbers}"
"{n: false_prime_percent([n] * 10_000, k=40) for n in carmichael_numbers}"
]
},
{
@@ -698,25 +649,41 @@
"\n",
"# Speed of Primality Checking\n",
"\n",
"**The great thing** about `isprime_fermat` and `sympy.isprime` is that they are **very fast**, handling 32-digit primes in well under a millisecond. Let's make a chart:"
"**The great thing** about `isprime_fermat` and `sympy.isprime` is that they are **very fast**, handling 32-digit primes in well under a millisecond. Let's create a list of big primes in ascending order of digit-length from 5 to 32, and measure how fast the functions are (while also verifying that they work correctly on Carmichael numbers):"
]
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 18,
"id": "be5f2fe7-b1e0-4a02-b7f0-7903e865c4b8",
"metadata": {},
"outputs": [],
"source": [
"big_primes = ([int('357686312646216567629137'[-i:]) for i in range(5, 25)] # left-truncated primes\n",
" + # The following primes sourced from t5k.org/curios/\n",
" [1000000000000000035000061, 59999999999899999999999999, 100000109999990000011000001,\n",
" 2728487949505050529272727777, 24444666666888888889999999991, 100003100019100043100057100069,\n",
" 9999999999999999777777775555331, 55555555555555555555555555555559])"
]
},
{
"cell_type": "markdown",
"id": "6b2cbdc7-e8a9-4b83-aa0d-1e7cc83de071",
"metadata": {},
"source": [
"Now let's make a chart of all the prime functions running on the big primes:"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "8109d9fc-bbae-4ef0-a75c-908d9746a557",
"metadata": {},
"outputs": [],
"source": [
"import timeit\n",
"\n",
"big_primes = ([int('357686312646216567629137'[-i:]) for i in range(5, 25)] +\n",
" # The following primes sourced from t5k.org/curios/\n",
" [1000000000000000035000061, 59999999999899999999999999, 100000109999990000011000001,\n",
" 2728487949505050529272727777, 24444666666888888889999999991, 100003100019100043100057100069,\n",
" 9999999999999999777777775555331, 55555555555555555555555555555559])\n",
"\n",
"def plot_run_times(functions=(isprime_simple, isprime_faster, isprime_fermat, isprime), primes=big_primes):\n",
"def plot_run_times(primes, functions=(isprime_simple, isprime_faster, isprime_fermat, isprime)):\n",
" \"\"\"For each primality-checking function, plot its run time on primes of different digit-lengths.\"\"\"\n",
" plt.figure(figsize=(9, 6))\n",
" for function in functions:\n",
@@ -732,13 +699,13 @@
" \"\"\"Time the function on each of the primes, stopping when one exceeds a second.\n",
" Return a list of the digit sizes and a list of the corresponding run times.\"\"\"\n",
" D, T = [], [] # D is length of primes in digits; T is time in seconds\n",
" repeat = 100 # Start with 100 repeats, but reduce when times get longer\n",
" repeat = (1 if function == isprime_simple else 500)\n",
" for p in primes:\n",
" time = timeit.timeit(lambda: function(p), number=repeat) / repeat\n",
" D.append(len(str(p)))\n",
" T.append(time)\n",
" if time > 1:\n",
" break\n",
" break # Don't make a function do bigger primes if it took over a second on this one\n",
" if time > 1/1000:\n",
" repeat = 1\n",
" return D, T"
@@ -746,13 +713,13 @@
},
{
"cell_type": "code",
"execution_count": 21,
"execution_count": 20,
"id": "b1ab0f3d-6ce6-4b8c-9d4d-bb78bb1ac432",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"image/png": "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 truncated
"text/plain": [
"<Figure size 900x600 with 1 Axes>"
]
@@ -762,58 +729,12 @@
}
],
"source": [
"plot_run_times()"
"plot_run_times(big_primes)"
]
},
{
"cell_type": "markdown",
"id": "408ba0fc-9bd9-4929-8ff3-1f9582ffa483",
"metadata": {},
"source": [
"We can test that our two Fermat functions work on the big primes and on the carmichael numbers, and get some more timing data:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "7eacb754-e6ee-482e-82fd-8725845aad7e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 1.25 ms, sys: 0 ns, total: 1.25 ms\n",
"Wall time: 1.25 ms\n"
]
}
],
"source": [
"%time test_prime_checker(isprime, primes=big_primes, composites=carmichael_numbers);"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "389cc455-6b9c-40f6-9fc2-2ae129159761",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 3.47 ms, sys: 0 ns, total: 3.47 ms\n",
"Wall time: 3.47 ms\n"
]
}
],
"source": [
"%time test_prime_checker(isprime_fermat, primes=big_primes, composites=carmichael_numbers);"
]
},
{
"cell_type": "markdown",
"id": "1347399a-24f2-4d5f-aa7f-822951126bf5",
"id": "159833b0-afaf-486f-9c8e-1c8d2a11d9bf",
"metadata": {},
"source": [
"What have we learned about timing?\n",
@@ -822,10 +743,104 @@
"- `isprime_fermat` and `isprime` ([according to Wikipedia](https://en.wikipedia.org/wiki/Fermat_primality_test)) have a run time of *O*(*k* log<sup>2</sup>*n*), where *k* is the number of repeats \n",
"- `isprime_faster` is more than 10,000 times faster than `isprime_simple` on 8-digit primes\n",
"- `isprime_fermat` is more than 10,000 times faster than `isprime_faster` on 17 digit primes\n",
"- `isprime` is about twice as fast as `isprime_fermat`; but that's quite good for `isprime_fermat` considering how simple it is\n",
"- `isprime` produces no known incorrect answers; `isprime_fermat` will on rare occasion produce a false prime\n",
"- `isprime` is about twice as fast as `isprime_fermat`\n",
" - (but that's quite good for `isprime_fermat` considering how much simpler it is: 10 lines versus 800)\n",
"- `isprime` produces no known incorrect answers; `isprime_fermat` will on rare occasion produce a false prime"
]
},
{
"cell_type": "markdown",
"id": "4c76de9d-4803-4db5-b6c5-811a6a1c6da1",
"metadata": {},
"source": [
"It is good to get the timing results, but I should also run some test cases to demonstrate correctness:"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "4f377075-e352-46f9-a716-f1de673a7086",
"metadata": {},
"outputs": [],
"source": [
"for fn in (isprime, isprime_simple, isprime_faster, isprime_fermat):\n",
" assert all(fn(p) for p in (2, 3, 5, 7, 11, 37, 73, 101, 11939, 65537, 117223, 7629137)) # primes\n",
" assert all(fn(n) is False for n in (0, 1, 4, 6, 8, 9, 10, 256, 11939*11939, 11939*65537)) # composites"
]
},
{
"cell_type": "markdown",
"id": "1347399a-24f2-4d5f-aa7f-822951126bf5",
"metadata": {},
"source": [
"This was fun for me! I remember learning about Fermat's Little Theorem and its application to primality testing in [Michael Rosen's](https://mathematics.brown.edu/people/michael-rosen) number theory class in college, and was impressed with it then, but I never worked through the details of how often the test gives a false prime result until now.\n",
"\n",
"I hope you have enjoyed this excursion into the land of prime numbers.\n"
"# Note on Modular Exponentiation\n",
"\n",
"Just one more thing: none of this would work unless we can efficiently compute *a*<sup>(*n* - 1)</sup> (mod *n*). How does the `pow` builtin function do it? When *a* and *n* are 24-digit numbers, if we naively tried to compute `a ** (n - 1)`, we'd have two problems: we'd need about a billion petabytes to store the result, and we'd need centuries to compute it. The way around these problems is to use [modular exponentiation](https://en.wikipedia.org/wiki/Modular_exponentiation) where we (1) apply the modulus to intermediate results, so we don't need petabytes of storage, and (2) cut the exponent in half each iteration, so we need only do *O*(log *n*) multiplications, not *O*(*n*). That's a big difference because log<sub>2</sub>(10<sup>24</sup>) is only 80. The basic idea is that *b*<sup>2*e*</sup> is equal to (*b* × *b*)<sup>*e*</sup>. Here is an implementation:"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "e71eb140-7e1a-4146-87a7-8c4eebf61486",
"metadata": {},
"outputs": [],
"source": [
"def modpow(b: int, e: int, m: int) -> int:\n",
" \"\"\"Compute b**e mod m, efficiently.\"\"\"\n",
" return (1 if e == 0 else\n",
" b if e == 1 else\n",
" modpow((b * b) % m, e // 2, m) if e % 2 == 0 else\n",
" modpow((b * b) % m, e // 2, m) * b % m)"
]
},
{
"cell_type": "markdown",
"id": "6baf40c3-db36-40ba-b2d7-45afa21f811f",
"metadata": {},
"source": [
"We can see this function is fast and that it correctly computes the remainder of 1 for the follwoing:"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "01838139-52dd-48a0-8f62-bcdef351345e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 24 μs, sys: 0 ns, total: 24 μs\n",
"Wall time: 24.8 μs\n"
]
},
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a = 123456789012345678901234\n",
"n = 357686312646216567629137\n",
"\n",
"%time modpow(a, n - 1, n)"
]
},
{
"cell_type": "markdown",
"id": "4be033d1-93a0-4ef5-853e-81da0782ed20",
"metadata": {},
"source": [
"I hope you have enjoyed this excursion into the world of primes!"
]
}
],