Java ProgramsNumbersPrint Perfect Numbers

Print Perfect Numbers in Java

intermediate·  Numbers  ·  Number Theory

Problem

A perfect number is a number that equals the sum of its own proper divisors — printing every one in a range means checking that condition for every candidate.

Given an upper limit, print every perfect number up to and including it.

Input
up to 500
Output
6 28 496

Java Program

Java
public class PrintPerfectNumbers { public static void main(String[] args) { int limit = 500; for (int num = 2; num <= limit; num++) { int sum = 0; for (int i = 1; i < num; i++) { if (num % i == 0) sum += i; // add every proper divisor of this candidate } if (sum == num) { System.out.println(num); // candidate matches its own divisor sum } } } }

Output

6 28 496

Core Logic

Checking every candidate number in the range for the perfect-number condition, one divisor-sum scan at a time, finds every match by brute force.

How It Works
  1. 1The outer loop tries every candidate num from 2 up to limit.
  2. 2For each candidate, an inner loop sums every number from 1 up to num - 1 that divides num evenly.
  3. 3If that divisor sum equals num, the candidate is a perfect number and gets printed.
  4. 4The scan continues through the whole range, since perfect numbers are rare and spread far apart.
Within 2 to 500, only 6, 28, and 496 have a divisor sum that equals themselves — every other candidate in between fails the check.
💡

Key Point: Perfect numbers get rare fast — after 6, 28, and 496, the next one is 8128, and the one after that is over 33 million, so even a full scan up to 500 only turns up three matches.

Complexity
Time Complexity: O(limit²)Space Complexity: O(1)

Why: Each candidate number gets its own O(candidate) divisor-sum scan, so the nested loops add up to roughly O(limit²) across the whole range.

Key Concepts

nested loopproper divisorstrial division

Approach 2: Optimized (Divisor Pairs)

Java
public class PrintPerfectNumbersOptimized { public static void main(String[] args) { int limit = 500; for (int num = 2; num <= limit; num++) { int sum = 1; // 1 is a proper divisor of every number greater than 1 for (int i = 2; (long) i * i <= num; i++) { if (num % i == 0) { sum += i; int pair = num / i; if (pair != i) sum += pair; // add the matching divisor pair, unless it's the same value } } if (sum == num) { System.out.println(num); // candidate matches its own divisor sum } } } }

Output

6 28 496

Core Logic

Applying the same divisor-pairs shortcut used to check a single perfect number to every candidate in the range cuts each inner scan down to the square root.

How It Works
  1. 1The outer loop is unchanged — it still tries every candidate num from 2 to limit.
  2. 2The inner divisor-sum scan now only tries candidates up to √num, adding both a divisor and its pair, num / i, whenever one is found.
  3. 3The pair != i check still guards against double-counting a perfect square's middle divisor.
  4. 4The same sum == num comparison decides whether to print the candidate.
For 28 within the range, the optimized inner scan only tries candidates up to 5 (since 6 * 6 &gt; 28), instead of all the way up to 27.
💡

Key Point: This is the same trade-off as checking a single perfect number — the inner scan drops from O(candidate) to O(√candidate), which adds up across the whole range.

Complexity
Time Complexity: O(limit × √limit)Space Complexity: O(1)

Why: Each candidate's divisor-sum check now only costs O(√candidate) instead of O(candidate), cutting the total work from roughly O(limit²) down to O(limit × √limit).

Key Concepts

divisor pairssquare-root bound

Approach 3: Java 8

Java
import java.util.stream.IntStream; public class PrintPerfectNumbersStream { public static void main(String[] args) { int limit = 500; // Keeps only the candidates whose proper-divisor sum equals themselves IntStream.rangeClosed(2, limit) .filter(num -> IntStream.range(1, num).filter(i -> num % i == 0).sum() == num) .forEach(System.out::println); } }

Output

6 28 496

Core Logic

The same brute-force scan can be expressed as nested streams — filter the range down to only the candidates whose divisor sum matches themselves.

How It Works
  1. 1IntStream.rangeClosed(2, limit) generates every candidate in the range.
  2. 2.filter(num -> ...) keeps only the candidates that pass the perfect-number condition.
  3. 3Inside that filter, a nested IntStream.range(1, num).filter(i -> num % i == 0).sum() computes each candidate's divisor sum, mirroring the primary approach's inner loop.
  4. 4.forEach(System.out::println) prints every surviving candidate.
Filtering 2 through 500 down to candidates whose divisor sum equals themselves keeps exactly 6, 28, and 496, printed in order.
💡

Key Point: This does the same O(limit²) work as the primary approach, just expressed as nested streams instead of nested loops — the divisor-pairs optimization still applies separately if performance matters.

Complexity
Time Complexity: O(limit²)Space Complexity: O(1)

Why: Each candidate still gets its own O(candidate) divisor-sum stream, so the total cost across the whole range is still roughly O(limit²), just expressed as nested streams instead of nested loops.

Key Concepts

Streamnested IntStreamfilter()

Related Programs