So I am supposed to write a Python program that will identify and print all the perfect numbers in some closed interval [ 2, n ], one per line. We only have to use nested while loops/ if-else statements. I did it somehow using a for loop, but can't figure out the same using a while loop. I'd appreciate your help if you could show me how to translate my code into a while loop. Thanks guys. Here's what I have:
limit = int(input("enter upper limit for perfect number search: "))for n in range(2, limit + 1):sum = 0for divisor in range(1, n):if not n % divisor:sum += divisorif sum == n:print(n, "is a perfect number")
Here is a (somewhat more efficient) sieve version:
# search all numbers in [2..limit] for perfect numbers
# (ones whose proper divisors sum to the number)
limit = int(input("enter upper limit for perfect number search: "))# initialize - all entries are multiples of 1
# (ignore sieve[0] and sieve[1])
sieve = [1] * (limit + 1)n = 2
while n <= limit:# check nif sieve[n] == n:print(n, "is a perfect number")# add n to all k * n where k > 1kn = 2 * nwhile kn <= limit:sieve[kn] += nkn += nn += 1
Running it to 10000 finds
6 is a perfect number
28 is a perfect number
496 is a perfect number
8128 is a perfect number
and factorizing those shows an interesting pattern:
6 3 * 2 ( 4 - 1) * ( 4 / 2)
28 7 * 2 * 2 ( 8 - 1) * ( 8 / 2)
496 31 * 2 * 2 * 2 * 2 ( 32 - 1) * ( 32 / 2)
8128 127 * 2 * 2 * 2 * 2 * 2 * 2 (128 - 1) * (128 / 2)
where the first factor (3, 7, 31, 127) is a prime which is one less than a power of two, and it is multiplied by half that same power of two. Also, the powers involved are prime (2**2
, 2**3
, 2**5
, 2**7
).
In fact Euclid proved that (2**p - 1) * 2**(p - 1)
is a perfect number if 2**p - 1
is prime, which is only possible (though not assured) if p
is prime. Euler went further, proving that all even perfect numbers must be of this form.
This suggests an incredibly more efficient version - I am going to go ahead and use for loops, feel free to rewrite it without. First, we need a source of primes and an is_prime test:
def primes(known_primes=[7, 11, 13, 17, 19, 23, 29]):"""Generate every prime number in ascending order"""# 2, 3, 5 wheelyield from (2, 3, 5)yield from known_primes# The first time the generator runs, known_primes# contains all primes such that 5 < p < 2 * 3 * 5# After each wheel cycle the list of known primes# will be added to.# We need to figure out where to continue from,# which is the next multiple of 30 higher than# the last known_prime:base = 30 * (known_primes[-1] // 30 + 1)new_primes = []while True:# offs is chosen so 30*i + offs cannot be a multiple of 2, 3, or 5for offs in (1, 7, 11, 13, 17, 19, 23, 29):k = base + offs # next prime candidatefor p in known_primes:if not k % p:# found a factor - not primebreakelif p*p > k:# no smaller prime factors - found a new primenew_primes.append(k)breakif new_primes:yield from new_primesknown_primes.extend(new_primes)new_primes = []base += 30def is_prime(n):for p in primes():if not n % p:# found a factor - not primereturn Falseelif p * p > n:# no factors found - is primereturn True
then the search looks like
# search all numbers in [2..limit] for perfect numbers
# (ones whose proper divisors sum to the number)
limit = int(input("enter upper limit for perfect number search: "))for p in primes():pp = 2**pperfect = (pp - 1) * (pp // 2)if perfect > limit:breakelif is_prime(pp - 1):print(perfect, "is a perfect number")
which finds
enter upper limit for perfect number search: 2500000000000000000
6 is a perfect number
28 is a perfect number
496 is a perfect number
8128 is a perfect number
33550336 is a perfect number
8589869056 is a perfect number
137438691328 is a perfect number
2305843008139952128 is a perfect number
in under a second ;-)