Java 에서 숫자가 2 의 거듭제곱인지 확인하는 방법

JavaBeginner
지금 연습하기

소개

이 랩에서는 Java 에서 주어진 숫자가 2 의 거듭제곱인지 효율적으로 판별하는 방법을 배우게 됩니다. 전통적인 방법보다 성능이 뛰어난 경우가 많은 영리한 비트 연산 접근 방식을 살펴볼 것입니다.

이 랩에서는 이 비트 연산 기술을 구현하고 테스트하는 과정을 안내하고, 루프 기반 접근 방식과 비교하며, 견고한 솔루션을 보장하기 위해 양수가 아닌 숫자와 같은 엣지 케이스 (edge case) 를 처리하는 방법을 다룹니다.

2 의 거듭제곱 확인을 위한 비트 연산 사용

이 단계에서는 Java 에서 비트 연산을 사용하여 숫자가 2 의 거듭제곱인지 확인하는 영리한 방법을 살펴봅니다. 이 방법은 전통적인 루프 기반 접근 방식보다 효율적인 경우가 많습니다.

먼저, 2 의 거듭제곱이 무엇인지 이해해 봅시다. 2 의 거듭제곱은 정수 지수로 표현될 수 있는 모든 숫자입니다 (예: 2^0 = 1, 2^1 = 2, 2^2 = 4, 2^3 = 8 등). 이진 표현에서 2 의 거듭제곱은 고유한 패턴을 갖습니다. 즉, 1 다음에 0 개 이상의 0이 오는 형식으로 표현됩니다 (예: 1 은 1, 2 는 10, 4 는 100, 8 은 1000).

이제 2 의 거듭제곱과 그 바로 앞 숫자의 이진 표현을 생각해 봅시다. 예를 들어:

  • 8 을 이진수로 표현하면 1000
  • 7 을 이진수로 표현하면 0111

2 의 거듭제곱과 그 바로 앞 숫자 사이에 비트 AND 연산 (&) 을 수행하면 결과는 항상 0 입니다. 이는 2 의 거듭제곱이 단일 1 비트를 가지고 있고, 그 앞의 숫자는 해당 위치에 0을, 오른쪽에 모든 위치에 1을 가지고 있기 때문입니다.

예를 들어, 8 (1000) & 7 (0111) = 0000 (즉, 0) 입니다.

이 속성은 모든 양의 2 의 거듭제곱에 적용됩니다. 2 의 거듭제곱이 아닌 모든 양의 숫자는 이진 표현에 최소 두 개의 1 비트를 갖습니다. 그 앞의 숫자와 비트 AND 를 수행하면, 해당 1 비트 중 적어도 하나가 앞의 숫자의 1 비트와 정렬되어 0 이 아닌 값을 생성합니다.

따라서 양수 n이 2 의 거듭제곱이 되기 위한 조건은 (n & (n - 1)) == 0입니다.

이 검사를 구현하기 위해 Java 프로그램을 만들어 보겠습니다.

  1. WebIDE 편집기에서 HelloJava.java 파일을 엽니다.

  2. 기존 코드를 다음으로 바꿉니다.

    public class HelloJava {
    
        // Function to check if a number is a power of two using bitwise AND
        public static boolean isPowerOfTwo(int n) {
            // A number is a power of two if it's positive and (n & (n - 1)) is 0
            return (n > 0) && ((n & (n - 1)) == 0);
        }
    
        public static void main(String[] args) {
            int number1 = 8;
            int number2 = 12;
            int number3 = 1;
    
            System.out.println(number1 + " is a power of two: " + isPowerOfTwo(number1));
            System.out.println(number2 + " is a power of two: " + isPowerOfTwo(number2));
            System.out.println(number3 + " is a power of two: " + isPowerOfTwo(number3));
        }
    }

    이 코드에서:

    • 정수 n을 입력으로 받는 정적 메서드 isPowerOfTwo를 정의합니다.
    • 메서드 내부에서 n이 0 보다 큰지, 그리고 nn-1의 비트 AND 가 0 과 같은지 확인합니다.
    • main 메서드는 몇 가지 예제 숫자를 사용하여 isPowerOfTwo 메서드를 사용하는 방법을 보여줍니다.
  3. 파일을 저장합니다 (Ctrl+S 또는 Cmd+S).

  4. 터미널에서 Java 프로그램을 컴파일합니다.

    javac HelloJava.java

    오류가 없으면 컴파일이 성공적으로 완료됩니다.

  5. 컴파일된 프로그램을 실행합니다.

    java HelloJava

    다음과 유사한 출력을 볼 수 있습니다.

    8 is a power of two: true
    12 is a power of two: false
    1 is a power of two: true

이 출력은 비트 연산 검사가 2 의 거듭제곱을 올바르게 식별함을 확인합니다.

루프 기반 접근 방식 테스트

이전 단계에서는 비트 연산을 사용하여 2 의 거듭제곱을 확인하는 간결한 방법을 배웠습니다. 효율적이긴 하지만 초보자에게는 즉시 직관적이지 않을 수 있습니다. 이 단계에서는 루프를 사용하여 더 간단한 접근 방식을 구현하여 개념에 대한 이해를 굳힐 수 있습니다.

숫자 n은 1 에 2 를 반복적으로 곱하여 얻을 수 있는 경우 2 의 거듭제곱입니다. 예를 들어:

  • 1 = 1 * 2^0
  • 2 = 1 * 2^1
  • 4 = 1 * 2^2
  • 8 = 1 * 2^3

루프를 사용하여 1 부터 시작하여 2 를 계속 곱하고 주어진 숫자 n에 도달했는지 확인할 수 있습니다.

루프를 사용하여 2 의 거듭제곱을 확인하는 새로운 메서드를 HelloJava.java 파일에 추가해 보겠습니다.

  1. WebIDE 편집기에서 HelloJava.java 파일을 엽니다.

  2. isPowerOfTwo 메서드 아래의 HelloJava 클래스 내부에 다음 메서드를 추가합니다.

        // Function to check if a number is a power of two using a loop
        public static boolean isPowerOfTwoLoop(int n) {
            if (n <= 0) {
                return false; // Powers of two are positive
            }
            int power = 1;
            while (power < n) {
                power *= 2; // Multiply by 2 in each iteration
            }
            return power == n; // Check if we reached the original number
        }

    이 새로운 메서드에서:

    • 먼저 n이 양수가 아닌 경우를 처리하여 false를 반환합니다.
    • 변수 power를 1 로 초기화합니다.
    • powern보다 작은 동안 계속되는 while 루프를 사용합니다.
    • 루프 내부에서 각 반복마다 power의 값을 두 배로 늘립니다 (power *= 2power = power * 2의 약식입니다).
    • 루프가 완료된 후, power의 최종 값이 n과 같은지 확인합니다. 그렇다면 n은 2 의 거듭제곱입니다.
  3. 이제 이 새로운 루프 기반 메서드를 테스트하도록 main 메서드를 수정해 보겠습니다. 기존 main 메서드를 이 업데이트된 버전으로 바꿉니다.

        public static void main(String[] args) {
            int number1 = 8;
            int number2 = 12;
            int number3 = 1;
            int number4 = 0; // Test a non-positive number
    
            System.out.println("--- Using Bitwise Approach ---");
            System.out.println(number1 + " is a power of two: " + isPowerOfTwo(number1));
            System.out.println(number2 + " is a power of two: " + isPowerOfTwo(number2));
            System.out.println(number3 + " is a power of two: " + isPowerOfTwo(number3));
            System.out.println(number4 + " is a power of two: " + isPowerOfTwo(number4)); // Test with 0
    
            System.out.println("\n--- Using Loop Approach ---");
            System.out.println(number1 + " is a power of two: " + isPowerOfTwoLoop(number1));
            System.out.println(number2 + " is a power of two: " + isPowerOfTwoLoop(number2));
            System.out.println(number3 + " is a power of two: " + isPowerOfTwoLoop(number3));
            System.out.println(number4 + " is a power of two: " + isPowerOfTwoLoop(number4)); // Test with 0
        }

    0에 대한 테스트 케이스를 추가하고 비트 연산 및 루프 기반 메서드 모두의 결과를 명확하게 표시하기 위해 print 문을 포함했습니다.

  4. 파일을 저장합니다 (Ctrl+S 또는 Cmd+S).

  5. 터미널에서 업데이트된 Java 프로그램을 컴파일합니다.

    javac HelloJava.java
  6. 컴파일된 프로그램을 실행합니다.

    java HelloJava

    다음과 유사한 출력을 볼 수 있습니다.

    --- Using Bitwise Approach ---
    8 is a power of two: true
    12 is a power of two: false
    1 is a power of two: true
    0 is a power of two: false
    
    --- Using Loop Approach ---
    8 is a power of two: true
    12 is a power of two: false
    1 is a power of two: true
    0 is a power of two: false

두 메서드 모두 이러한 테스트 케이스에 대해 동일한 결과를 생성합니다. 루프 기반 접근 방식은 처음에 이해하기 쉬울 수 있지만, 비트 연산 접근 방식은 일반적으로 더 큰 숫자의 경우 특히 더 성능이 좋습니다.

0 과 음수 처리

이전 단계에서 숫자가 2 의 거듭제곱인지 확인하는 두 가지 방법을 구현했습니다. 루프 기반 접근 방식에서 음수가 아닌 숫자 (0 과 음수) 를 처리하는 방법에 대해 간략하게 언급했습니다. 이 단계에서는 이러한 경우를 처리하는 것이 왜 중요한지 명시적으로 살펴보고 두 메서드 모두 음수가 아닌 입력에 대해 올바르게 false를 반환하는지 확인합니다.

정의에 따르면 2 의 거듭제곱은 양의 정수입니다. 따라서 0 과 모든 음수는 2 의 거듭제곱이 될 수 없습니다. 우리의 메서드는 이를 반영해야 합니다.

HelloJava.java에서 기존 메서드를 다시 검토하여 음수가 아닌 숫자를 올바르게 처리하는지 확인해 보겠습니다.

  1. WebIDE 편집기에서 HelloJava.java 파일을 엽니다.

  2. isPowerOfTwo 메서드 (비트 연산 방식) 를 살펴봅니다.

        public static boolean isPowerOfTwo(int n) {
            // A number is a power of two if it's positive and (n & (n - 1)) is 0
            return (n > 0) && ((n & (n - 1)) == 0);
        }

    이 메서드는 이미 (n > 0) 검사를 포함하고 있습니다. 이렇게 하면 n이 0 또는 음수인 경우 (n > 0) 조건이 false가 되고, && 연산자로 인해 전체 표현식이 false로 평가됩니다. 따라서 비트 연산 방식은 음수가 아닌 숫자를 올바르게 처리합니다.

  3. 이제 isPowerOfTwoLoop 메서드를 살펴봅니다.

        public static boolean isPowerOfTwoLoop(int n) {
            if (n <= 0) {
                return false; // Powers of two are positive
            }
            int power = 1;
            while (power < n) {
                power *= 2;
            }
            return power == n;
        }

    이 메서드는 처음에 if (n <= 0)을 명시적으로 확인하고 조건이 참이면 false를 반환합니다. 이것 또한 음수가 아닌 숫자를 올바르게 처리합니다.

  4. 0 과 음수를 특별히 확인하기 위해 main 메서드에 더 많은 테스트 케이스를 추가해 보겠습니다. 음수를 포함하도록 main 메서드를 수정합니다.

        public static void main(String[] args) {
            int number1 = 8;
            int number2 = 12;
            int number3 = 1;
            int number4 = 0;
            int number5 = -4; // Test a negative number
            int number6 = -1; // Test another negative number
    
            System.out.println("--- Using Bitwise Approach ---");
            System.out.println(number1 + " is a power of two: " + isPowerOfTwo(number1));
            System.out.println(number2 + " is a power of two: " + isPowerOfTwo(number2));
            System.out.println(number3 + " is a power of two: " + isPowerOfTwo(number3));
            System.out.println(number4 + " is a power of two: " + isPowerOfTwo(number4));
            System.out.println(number5 + " is a power of two: " + isPowerOfTwo(number5));
            System.out.println(number6 + " is a power of two: " + isPowerOfTwo(number6));
    
            System.out.println("\n--- Using Loop Approach ---");
            System.out.println(number1 + " is a power of two: " + isPowerOfTwoLoop(number1));
            System.out.println(number2 + " is a power of two: " + isPowerOfTwoLoop(number2));
            System.out.println(number3 + " is a power of two: " + isPowerOfTwoLoop(number3));
            System.out.println(number4 + " is a power of two: " + isPowerOfTwoLoop(number4));
            System.out.println(number5 + " is a power of two: " + isPowerOfTwoLoop(number5));
            System.out.println(number6 + " is a power of two: " + isPowerOfTwoLoop(number6));
        }
  5. 파일을 저장합니다 (Ctrl+S 또는 Cmd+S).

  6. 업데이트된 Java 프로그램을 컴파일합니다.

    javac HelloJava.java
  7. 컴파일된 프로그램을 실행합니다.

    java HelloJava

    다음과 유사한 출력을 볼 수 있습니다.

    --- Using Bitwise Approach ---
    8 is a power of two: true
    12 is a power of two: false
    1 is a power of two: true
    0 is a power of two: false
    -4 is a power of two: false
    -1 is a power of two: false
    
    --- Using Loop Approach ---
    8 is a power of two: true
    12 is a power of two: false
    1 is a power of two: true
    0 is a power of two: false
    -4 is a power of two: false
    -1 is a power of two: false

보시다시피 두 메서드 모두 0 과 음수가 2 의 거듭제곱이 아님을 올바르게 식별합니다. 이는 우리의 구현이 모든 정수 입력에 대해 견고함을 확인합니다.

음수가 아닌 숫자와 같은 가장자리 사례를 이해하는 것은 코드가 모든 유효한 입력에서 올바르게 작동하도록 하기 위해 프로그래밍에서 매우 중요합니다.

요약

이 Lab 에서는 비트 연산을 사용하여 Java 에서 숫자가 2 의 거듭제곱인지 효율적으로 확인하는 방법을 배웠습니다. 양수 n이 2 의 거듭제곱인 것은 nn-1 사이의 비트 AND 연산의 결과가 0 인 경우에만 해당한다는 것을 알게 되었습니다. 이는 2 의 거듭제곱의 고유한 이진 표현 때문입니다. 우리는 이 비트 연산 방식을 활용하는 Java 함수 isPowerOfTwo를 구현했으며, 이는 일반적으로 루프 기반 메서드보다 성능이 뛰어납니다.

또한 이 비트 연산 방식을 테스트하고 음수가 아닌 숫자를 처리하는 방법을 고려하여 다양한 입력에 대해 검사가 견고하도록 했습니다. 이 실습 경험은 일반적인 프로그래밍 문제를 해결하기 위해 비트 연산을 적용하는 데 대한 실질적인 통찰력을 제공했습니다.