shift 연산으로 2의 제곱을 만들 수 있다.
그렇다면
int a = Math.pow(2, n);
int b = 1 << n;
a, b 두 변수는 일반적인 상황에서 같은 결과를 갖는다.
글의 제목을 키워드로 구글에 검색해보면 스택오버플로우 링크가 나온다. 이전에 이런 고민을 해본 사람들이 있었던 것 같아 확인 해보았다.
int x= ?;
int b = (int) Math.pow(2,x);
int c = 1<<x;
x의 값에 따라 결과가 어떻게 나오는지 확인하면
x=32: b=2147483647; c=1;
x=31: b=2147483647; c=-2147483648;
x=3: b=8 ; c=8
위와 같은 결과가 나온다.
같은 x=32인데 pow의 결과와 shift 연산의 결과가 다르다. 왜일까?
질문에 대한 답변으로 x=32일때 pow 함수는 double로 계산하여 int로 형변환할 때 round()를 하는데 이때 double은 8바이트, int는 4바이트라서 Integer.MAX_VALUE를 리턴한다. 반면 shift 연산은 1<<32는 1<<0과 같기때문에 1이 나온다.
oracel 공식 문서에서 left shift에 대한 정의가 나온다.
It is as if the right-hand operand were subjected to a bitwise logical AND operator & with the mask value 0x1f (0b11111). The shift distance actually used is therefore always in the range 0 to 31, inclusive.
shift할 때 오른쪽의 피연산자는 0x1f와 and 연산을 통해 나온 값을 shift에 이용하기 때문에 32는 0b100000과 같기 때문에 0이 된다. 오른쪽 피연산자의 하위 5비트만을 shift에 이용하고 있다. 그래서 결과가 다르게 나온다.
마찬가지로 x=31일 경우, 0b11111이고 shift 연산을 진행할 경우 최상위 비트가 1이기때문에 2의 보수표현에서 음수이다. 그래서 Integer.MIN_VALUE를 리턴한다.
따라서 range(0..30)을 벗어난 케이스에서 Math.pow(2,n)과 1<<n은 결과에서 차이가 난다.
물론 Math.pow()의 성능이 훨씬 느리다. shift 연산은 프로세서에서 바로 처리하는 연산이기 때문이다.
그렇지만 Math.pow()는 일반적인 목적으로 자주 이용되고 있고 모든 숫자에 대해서 power 연산을 해야하기 때문에 2의 제곱이라는 특정한 케이스를 위해 최적화를 진행하지 않았다.
일리가 있는 말이라 Math.pow()의 구현에 대해 납득이 갔다.
스택오버플로우 답변에 추신으로 흥미로운 내용이 있어 이후에 실험해보고 포스팅할 예정이다.
P.S. It is perhaps interesting to note that using long in place of int (and 1L in place of 1) would give yet another set of results different from the other two. This holds even if the final results are converted to int.
https://stackoverflow.com/questions/10416694/java-results-differ-for-intmath-pow2-x-and-1x
https://stackoverflow.com/questions/47416967/what-is-the-difference-between-1-x-and-pow2-x/47417103
https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.19