Java 10
Java 계산식, 루나와 함께 읽어요!
연산자 기호는 익숙하지만 Java 코드에서 나눗셈, 나머지, 우선순위가 실제 계산 결과를 어떻게 바꾸는지 놓치기 쉽다.
근거 · 교안 p73-p77
1장: 루나, 산술 연산자 기호 너머의 진짜 규칙을 찾아줘!
솔라의 손가락이 노트북 키보드 위에서 잠시 멈췄다. 화면에는 갓 시작한 Java 학습 자료가 떠 있었다. 그중 한 문장이 눈에 들어왔다.
연산자는 값을 계산하거나 비교하거나 논리적으로 판단하는 기호다.
솔라는 피식 웃었다. 너무나 당연한 말이었기 때문이다. ‘더하기 기호는 더하기, 빼기 기호는 빼기. 이게 뭐 대단한 거라고.’ 그녀는 중얼거리며 간단한 계산기 프로그램을 만들어보면 재밌겠다는 생각에 사로잡혔다. 아이디어가 떠오르자 손가락이 다시 바쁘게 움직이기 시작했다.
“일단 사칙연산부터. 덧셈, 뺄셈, 곱셈… 그리고 나눗셈.”
솔라는 옆에 놓인 연습장에 자연스럽게 수식을 적었다. 10 / 3. 고민할 것도 없었다. 펜 끝에서 3.333... 이라는 숫자가 흘러나왔다. 이제 이걸 코드로 옮기기만 하면 된다. 그녀는 자신만만하게 키보드를 두드렸다.
System.out.println(10 / 3);
실행 버튼을 누르는 손길은 가벼웠다. 당연히 화면에 3.333333... 비슷한 숫자가 출력될 거라고 기대했다. 하지만 콘솔 창에 나타난 것은 전혀 다른 숫자였다.
3
“어?”
솔라는 눈을 비볐다. 잘못 본 것이라 생각했다. 다시 한번 실행 버튼을 눌렀다. 결과는 바뀌지 않았다. 여전히 화면에는 냉정하게 ‘3’이라는 숫자만 찍혀 있었다. 그녀는 자신의 연습장과 모니터 화면을 번갈아 보았다. 펜으로 쓴 정답과 컴퓨터가 내놓은 오답. 머릿속이 하얘졌다.
“이게 왜… 컴퓨터가 계산을 틀릴 리가 없는데.”
그때, 따뜻한 김이 오르는 찻잔 두 개를 든 루나가 조용히 다가왔다. 솔라의 책상에 찻잔 하나를 내려놓으며 잔뜩 찌푸린 동생의 미간을 발견했다.
“뭐가 잘 안 풀려?”
“언니, 이거 좀 봐. 내가 아는 수학이 이상한 거야, 아니면 내 컴퓨터가 이상한 거야?”
솔라는 억울하다는 듯 화면과 노트를 가리켰다. “10 나누기 3은 3.33… 이잖아. 그런데 자바한테 시키니까 3이래. 소수점이 그냥 사라졌어. 버그인가?”
루나는 솔라가 가리키는 화면과 노트를 차례로 들여다보았다. 잠시 생각에 잠긴 듯하던 그녀는 솔라의 연습장을 가볍게 톡, 두드렸다.
“솔라, 네가 이 종이에 쓴 10이랑,”
루나의 손가락이 이번에는 모니터 화면 속 코드의 10을 가리켰다.
“자바 코드에 쓴 10은, 정말 똑같은 10일까?”
“당연히 똑같은 10이지. 무슨 소리야?”
솔라는 이해할 수 없다는 표정을 지었다. 10은 그냥 10이었다. 수학에서도, 현실에서도, 당연히 컴퓨터 안에서도.
루나는 고개를 저었다. “우리가 수학 문제를 풀 때, 10은 그냥 ‘수’ 10이야. 하지만 자바는 좀 더 꼼꼼해. 자바에게 10은 그냥 숫자가 아니라 ‘소수점이 없는 정수’라는 종류의 값이야.”
‘정수.’ 그 단어가 솔라의 머릿속을 파고들었다. 루나가 말을 이었다.
“코드에 10과 3이라고 썼지? 둘 다 소수점이 없어. 그럼 자바는 이 둘을 ‘정수’ 타입으로 받아들여. 그리고 아주 중요한 규칙을 따르지. ‘정수와 정수를 계산한 결과는 반드시 정수여야 한다’는 규칙.”
순간 솔라의 머릿속에 흩어져 있던 조각들이 맞춰지는 느낌이 들었다. 아! 하는 작은 탄성이 터져 나왔다.
“그럼… 10 / 3을 계산할 때, 자바는 ‘어? 이거 정수 나누기 정수네? 그럼 결과도 정수여야지!’라고 생각해서… 실제 결과인 3.333…에서 소수점 아래를 그냥 버려버린 거구나!”
솔라는 방금 전까지 자신을 혼란에 빠뜨렸던 / 기호를 새로운 눈으로 바라보았다. 그건 더 이상 단순한 ‘나누기’ 기호가 아니었다.
“와… 그럼 이 기호는 그냥 ‘나누라’는 뜻이 아니었네. ‘주어진 값들의 종류를 보고, 그 규칙에 맞춰서 나누기를 실행하라’는 훨씬 구체적인 명령이었어.”
연산자는 그저 기호일 뿐이라는 처음의 생각이 얼마나 얕았는지 깨닫는 순간이었다. 기호 너머에, 언어가 정해놓은 명확한 규칙이 있었다. 그 규칙을 읽지 못하면 코드의 흐름을 전혀 예측할 수 없다는 사실을 온몸으로 체감했다. 골치 아픈 문제가 풀린 듯 후련해진 솔라는 의자에 등을 기댔다.
하지만 궁금증은 꼬리를 물고 이어졌다.
“잠깐만, 언니. 그럼 내가 만약 소수점까지 전부 계산된, 정확한 3.333… 결과를 꼭 얻고 싶으면 어떻게 해야 해?”
솔라는 다시 몸을 앞으로 기울이며 화면을 뚫어져라 쳐다보았다.
“자바한테 이게 ‘정수 10’이 아니라고 알려주면 되나? 예를 들면… 10.0 처럼 점을 찍어서?”
새로운 질문의 씨앗이 이미 싹을 틔우고 있었다. 루나의 입가에 옅은 미소가 번졌다.
2장: 루나, 정수와 실수의 나눗셈이 왜 달라?
솔라의 손가락은 이미 답을 찾아 움직이고 있었다. 지난번의 발견, 즉 자바의 연산자는 주어진 값의 ‘종류’에 따라 다르게 행동한다는 깨달음은 그녀에게 새로운 가설을 심어주었다. ‘정수’가 아닌 ‘실수’를 주면 되지 않을까? 그녀는 이전 코드를 지우는 대신, 그 아래에 새로운 실험용 코드들을 나란히 추가했다. 화면에는 그녀의 호기심이 남긴 흔적이 고스란히 찍혀 있었다.
System.out.println(10 / 3);
System.out.println(10.0 / 3);
System.out.println(10 / 3.0);
그리고 그 아래, 콘솔 창에는 세 줄의 결과가 명백하게 나타나 있었다. 마치 그녀의 예측이 맞았다는 듯이.
3
3.3333333333333335
3.3333333333333335
“됐어!” 솔라는 작은 탄성을 질렀다. 그녀의 가설이 맞았다. 숫자 뒤에 .0을 붙여 ‘이것은 정수가 아니야’라고 알려주자, 자바는 마침내 그녀가 원했던 소수점 결과를 돌려주었다. 하지만 기쁨도 잠시, 새로운 의문이 고개를 들었다. 두 번째 줄(10.0 / 3)의 성공은 예상했지만, 세 번째 줄(10 / 3.0)도 똑같은 결과를 내놓은 것은 조금 의외였다.
“언니, 이거 봐. 내 생각이 맞았어. 10.0이라고 하니까 자바가 드디어 소수점 계산을 해줬어.”
솔라는 의기양양하게 화면을 가리켰다. 잠시 동생의 발견을 지켜보던 루나는 고개를 끄덕이며 찻잔을 한 모금 마셨다.
“그런데 이상해. 10.0 / 3은 그렇다 쳐도, 10 / 3.0은 왜 똑같은 결과가 나오는 거지? 둘 중 하나만 실수여도 전체가 실수 계산이 되는 건가? 꼭 무슨 전염병 같네.”
루나는 솔라의 ‘전염병’이라는 표현에 옅은 미소를 지었다. 그녀는 키보드를 끌어당겨 솔라의 코드 아래에 주석으로 간단한 표를 만들기 시작했다.
// 식 (Expression) | 왼쪽 값 타입 | 오른쪽 값 타입 | 결과 타입
//------------------|--------------|----------------|-----------
// 10 / 3 | 정수 | 정수 | 정수
루나는 첫 번째 줄만 채워 넣고는 솔라를 쳐다보았다. 마치 빈칸을 채워보라는 무언의 신호 같았다. 솔라는 루나의 의도를 금방 알아차리고 표의 다음 줄을 머릿속으로 그리기 시작했다.
“두 번째 식 10.0 / 3에서 왼쪽 값은 10.0이니까… ‘실수’.” 솔라는 손가락으로 허공을 가리키며 말했다. “오른쪽 값은 3이니까 ‘정수’. 그런데 결과는 3.333...이 나왔으니까 ‘실수’가 됐네.”
그녀는 곧바로 세 번째 식으로 시선을 옮겼다.
“10 / 3.0은 반대로 왼쪽이 ‘정수’, 오른쪽이 ‘실수’인데… 결과는 역시 ‘실수’.”
솔라는 자신이 말한 내용을 표 아래에 타이핑해서 채워 넣었다. 완성된 표는 한 가지 규칙을 명확하게 보여주고 있었다.
// 식 (Expression) | 왼쪽 값 타입 | 오른쪽 값 타입 | 결과 타입
//------------------|--------------|----------------|-----------
// 10 / 3 | 정수 | 정수 | 정수
// 10.0 / 3 | 실수 | 정수 | 실수
// 10 / 3.0 | 정수 | 실수 | 실수
“아!” 솔라의 눈이 커졌다. “알겠다! 나눗셈 연산자 /는 양쪽에 있는 값의 종류를 보고 행동을 결정하는 거였어. 둘 다 정수일 때는 ‘정수 나누기’를 해서 소수점을 버리고, 둘 중 하나라도 실수가 있으면 ‘실수 나누기’ 모드로 바뀌어서 어떻게든 소수점까지 계산해내는 거구나.”
방금 전까지 ‘전염병’ 같다고 느꼈던 현상의 원리가 명확해졌다. 그것은 혼란스러운 예외가 아니라, 더 정확한 값을 지키기 위한 자바의 체계적인 규칙이었다. ‘정수’라는 좁은 세상에 ‘실수’라는 더 넓은 세상의 값이 들어오자, 자바는 정보 손실을 막기 위해 계산의 기준 자체를 더 넓은 세상으로 확장해버린 것이다.
솔라는 이제 나눗셈 기호를 볼 때, 단순히 ‘나눈다’고 생각하지 않게 되었다. 그녀는 이제 기호의 양쪽을 살피며 ‘어떤 종류의 값들로 나누는가?’를 먼저 확인하게 되었다. 값의 타입을 보고 결과의 타입을 예측하는 새로운 눈이 생긴 것이다.
“정확해. 프로그래밍에서는 그걸 ‘타입 승격(Type Promotion)’이라고 불러. 크기가 작은 타입과 큰 타입을 함께 계산할 때, 자바가 자동으로 작은 타입을 큰 타입으로 변환해서 계산하는 거지.”
루나의 차분한 설명이 솔라의 발견에 확신을 더해주었다. 이제 산술 연산은 어느 정도 자신감이 붙은 것 같았다. 덧셈, 뺄셈, 곱셈, 그리고 이제는 나눗셈까지.
“좋아, 그럼 더하기, 빼기, 곱하기, 나누기… 이 기본적인 것들은 이제 알 것 같아. 그럼 다른 기호는? 아, 이거.”
솔라는 키보드에서 퍼센트 기호(%)를 찾아 눌렀다. 10 % 3. 10을 3으로 나눈 나머지. 결과는 당연히 1이다. 이건 쉬웠다. 하지만 지난번의 경험은 그녀를 신중하게 만들었다.
“근데 만약… 숫자가 음수면 어떻게 되지? 예를 들어 -10 % 3 이라든가.”
솔라의 손가락이 키보드 위에서 잠시 멈칫했다. 수학적으로 생각하면 나머지는 항상 양수여야 할 것 같은데… 자바도 과연 그럴까? 방금 전까지 명쾌했던 머릿속에 다시 한번 작은 의문의 안개가 피어오르기 시작했다.
3장: 루나, 나머지 연산은 왜 부호가 바뀌는 거야?
솔라의 손가락이 키보드 위에서 멈칫했던 순간이 무색하게, 실행 결과는 순식간에 나타났다. 하지만 그 결과는 명쾌함 대신 더 짙은 안개를 몰고 왔다. 그녀의 화면 콘솔 창에는 방금 실행한 System.out.println(-10 % 3); 코드의 결과가 덩그러니 찍혀 있었다.
-1
솔라는 고개를 갸웃거렸다. 10 % 3의 결과가 1인 것은 쉽게 예상했다. 10을 3으로 나누면 몫이 3이고 나머지가 1이니까. 하지만 -10을 3으로 나눈 나머지가 -1이라는 것은 선뜻 받아들이기 어려웠다. 어릴 때부터 배운 수학에서 ‘나머지’는 항상 0이거나 양수였기 때문이다. 음수인 나머지는 낯설었다.
“언니, 이상해. 나머지가 어떻게 음수가 나와? 수학에서는 이럴 때 몫을 -4로 하고 나머지를 2라고 하지 않나? 자바는 왜 -1이라고 하는 거지?”
솔라의 질문에 루나는 화면을 잠시 들여다보았다. 그러고는 아무 말 없이 솔라의 연습장을 가져와 네 개의 칸으로 나뉜 표를 그리기 시작했다. 각 칸의 머리에는 솔라가 방금 떠올린 숫자 조합들이 적혀 있었다.
| 식 | 예상 결과 | Java 실행 결과 | 규칙 추측 |
|---|---|---|---|
10 % 3 | |||
-10 % 3 | |||
10 % -3 | |||
-10 % -3 |
루나는 펜을 내려놓고 솔라를 바라보았다. 직접 빈칸을 채워보라는 뜻이었다. 솔라는 루나의 의도를 알아채고 다시 키보드로 손을 옮겼다. 그녀는 방금 했던 실험에 두 가지 경우를 더 추가해 코드를 완성했다.
System.out.println(10 % 3);
System.out.println(-10 % 3);
System.out.println(10 % -3);
System.out.println(-10 % -3);
실행 버튼을 누르자, 네 줄의 결과가 나란히 출력되었다.
1
-1
1
-1
솔라는 출력된 결과를 멍하니 바라보다가, 연습장에 그려진 표에 하나씩 옮겨 적기 시작했다. 첫 번째 칸, 10 % 3의 예상 결과는 1, 실제 결과도 1. 두 번째 칸, -10 % 3의 예상 결과는 수학적으로 생각해서 2라고 적으려다, 방금 겪은 혼란 때문에 물음표를 쳤다. 실제 결과는 -1. 세 번째와 네 번째 칸은 더 혼란스러웠다. 피제수가 아닌 제수까지 음수가 되자 예상조차 하기 어려웠다. 그녀는 그저 실행 결과를 그대로 받아 적을 뿐이었다.
| 식 | 예상 결과 | Java 실행 결과 | 규칙 추측 |
|---|---|---|---|
10 % 3 | 1 | 1 | |
-10 % 3 | ? (2?) | -1 | |
10 % -3 | ? | 1 | |
-10 % -3 | ? | -1 |
표가 채워지자 기묘한 패턴이 보이기 시작했다. 결과값의 부호가 제수(오른쪽 숫자)의 부호와는 아무 상관이 없어 보였다. 3이든 -3이든 결과의 부호에 영향을 주지 않았다. 하지만 피제수(왼쪽 숫자)의 부호를 따라가는 것 같았다.
솔라의 눈이 반짝였다. 그녀는 마지막 칸을 채우기 위해 펜을 집어 들었다.
“아! 알겠다!”
솔라는 표의 첫 번째 줄과 세 번째 줄을 손가락으로 가리켰다. “10이 양수일 때는 결과가 1로 양수. 그리고… -10이 음수일 때는 결과가 -1로 음수야. 나누는 숫자가 양수든 음수든 상관없이!”
그녀는 발견한 규칙을 ‘규칙 추측’ 칸에 흥분하며 적어 내려갔다. ‘결과의 부호는 첫 번째 숫자의 부호를 따라간다.’
방금 전까지 뒤죽박죽으로 보였던 -1과 1의 나열이 이제는 명확한 질서를 가진 것처럼 보였다. Java의 나머지 연산자 %는 수학의 ‘나머지’와는 조금 다른, 자신만의 뚜렷한 규칙을 가진 연산이었던 것이다. 바로 ‘결과의 부호는 첫 번째 피연산자의 부호와 같다’는 규칙. 나눗셈 연산자가 피연산자의 ‘타입’을 보고 행동을 결정했다면, 나머지 연산자는 첫 번째 피연산자의 ‘부호’를 보고 결과의 부호를 결정했다.
“이제 연산자 기호만 보고 섣불리 판단하면 안 되겠어. 나누기도 그렇고, 나머지도 그렇고, 다 자기만의 규칙이 있네.”
골치 아픈 문제를 또 하나 해결한 솔라는 후련한 마음으로 의자에 등을 기댔다. 덧셈, 뺄셈, 곱셈, 나눗셈, 그리고 나머지 연산까지. 이제 기본적인 계산은 자신 있었다.
“좋아, 그럼 이런 건 어떨까? 2 + 3 * 4.”
솔라는 새로운 실험을 위해 키보드를 두드렸다. 이건 너무 쉬웠다. 왼쪽부터 차례대로 계산하면 되니까. 2 더하기 3은 5이고, 5 곱하기 4는 20. 그녀는 자신만만하게 코드를 실행했다. 하지만 콘솔 창에 나타난 숫자는 또다시 그녀의 예상을 배신했다.
14
“뭐지? 20이 아니라 14?”
솔라의 미간이 다시 좁혀졌다. 덧셈과 곱셈, 이렇게 간단한 연산자 두 개가 모였을 뿐인데 왜 계산 순서가 자신의 생각과 다른 걸까? 머릿속에 또 다른 물음표가 떠올랐다.
4장: 루나, 연산자 우선순위 규칙이 대체 뭐야?
솔라의 연습장에는 그녀의 혼란이 그대로 새겨져 있었다. 2 + 3 * 4라는 식 아래, 그녀가 자신만만하게 계산했던 과정이 적혀 있었다. 2 + 3 = 5, 그리고 5 * 4 = 20. 하지만 그 숫자 ‘20’ 위에는 검은 펜으로 거칠게 X 표시가 그어져 있었다. 그 옆에는 모니터 콘솔 창에서 본 정답, ‘14’가 커다란 물음표와 함께 쓰여 있었다.
컴퓨터는 틀리지 않았다. 그렇다면 틀린 것은 자신의 계산 방식이었다. 하지만 덧셈과 곱셈, 이 간단한 연산의 순서를 틀렸다는 사실을 받아들이기 어려웠다. 왼쪽에서 오른쪽으로, 차례대로 계산하는 것이 당연한 것 아니었나? 나눗셈과 나머지 연산처럼 특별한 규칙이 숨어있을 거라고는 상상도 못 했다. 솔라는 펜 끝으로 무의미하게 책상을 톡톡 두드렸다.
“언니, 이건 진짜 모르겠어.”
생각에 잠겨 있던 루나가 솔라의 중얼거림에 고개를 돌렸다. 그녀는 솔라의 연습장에 그려진 X 표시와 물음표를 잠시 응시했다.
“자바가 어떻게 14라는 답을 얻었는지, 그 과정을 한번 거꾸로 추적해 볼까?”
루나는 답을 알려주는 대신, 솔라가 스스로 길을 찾도록 유도했다. 솔라는 잠시 생각에 잠겼다. 14라는 숫자가 나오려면…
“음… 3 곱하기 4를 먼저 하면 12가 나오고, 거기에 2를 더하면 14가 돼. 아! 그럼 자바가 덧셈을 건너뛰고 곱셈부터 계산한 거네. 근데 왜?”
“왜 그랬을까? +랑 *랑 둘 다 똑같은 연산자 기호인데.”
루나의 되물음에 솔라는 다시 머리가 복잡해졌다. 나눗셈은 피연산자의 ‘타입’을 보고 행동을 바꿨다. 나머지 연산은 첫 번째 피연산자의 ‘부호’를 따랐다. 연산자들이 각자 다른 기준에 따라 행동한다는 건 이제 알겠다. 그렇다면 이번엔 뭘까?
루나는 솔라의 연습장에 새로운 식을 하나 더 적었다.
10 - 4 / 2
“이건 어때? 왼쪽부터 계산하면 뭐가 나올까?”
“10 빼기 4는 6이고… 6 나누기 2는 3.”
“그럼 자바는 뭐라고 할까?”
솔라는 반신반의하며 코드를 입력하고 실행했다. 화면에 나타난 숫자는 8이었다. 또 틀렸다. 그녀는 허탈하게 웃었다.
“8? 이것도 나누기를 먼저 했네. 4 나누기 2는 2고, 10에서 2를 빼면 8이니까. 곱셈도 그렇고, 나눗셈도 그렇고… 얘네가 덧셈, 뺄셈보다 힘이 더 센가?”
“힘이 더 세다…”
루나는 솔라의 표현을 곱씹으며 미소지었다. “재밌는 표현이네. 맞아, 그렇게 생각해도 좋아. 마치 줄을 서 있는데, VIP 티켓을 가진 사람이 먼저 들어가는 것처럼.”
‘VIP 티켓.’ 그 비유가 솔라의 머릿속에 박혔다.
“그럼 곱셈(*), 나눗셈(/), 그리고 나머지(%) 연산자는 VIP 티켓을 가지고 있는 거구나! 덧셈(+)과 뺄셈(-)은 일반 티켓이고. 그래서 한 줄에 같이 서 있으면, 자바가 먼저 VIP들을 찾아서 계산부터 해주는 거야.”
솔라는 방금 전까지 자신을 괴롭혔던 두 개의 식을 새로운 관점으로 다시 바라보았다.
2 + 3 * 4
- 자바가 식 전체를 훑는다.
- VIP 티켓을 가진
*를 발견한다. 3 * 4를 먼저 계산해서12로 만든다.- 이제 식은
2 + 12가 된다. - 남은 일반 티켓
+를 계산해서14를 얻는다.
10 - 4 / 2
- 자바가 식 전체를 훑는다.
- VIP 티켓을 가진
/를 발견한다. 4 / 2를 먼저 계산해서2로 만든다.- 이제 식은
10 - 2가 된다. - 남은 일반 티켓
-를 계산해서8을 얻는다.
퍼즐 조각이 딱 맞춰지는 기분이었다. 연산자들이 왼쪽부터 순서대로 처리되는 것이 아니었다. 식 전체에 보이지 않는 ‘우선순위’라는 계급이 존재했던 것이다. 연산자 기호의 정체는 변하지 않지만, 이 우선순위 규칙에 따라 행동하는 순서가 결정되었다.
이제 솔라는 복잡한 계산식을 봐도 당황하지 않고, 먼저 VIP 연산자가 어디에 있는지부터 찾는 ‘우선순위 스캐너’처럼 생각하게 되었다. 곱셈, 나눗셈, 나머지 연산을 먼저 찾아서 처리하고, 그 다음에 덧셈과 뺄셈을 처리하면 된다. Java의 계산 흐름을 정확하게 읽어낼 수 있는 새로운 눈을 얻은 것이다.
“알겠다! 이제 어떤 식이든 계산 순서를 알 수 있을 것 같아. 그냥 VIP 먼저 챙겨주면 되는 거였네.”
자신감이 붙은 솔라는 의자에 등을 기댔다. 하지만 그 순간, 한 가지 의문이 떠올랐다.
“그런데 언니, 만약에… 정말 만약에 내가 VIP를 무시하고 덧셈부터 꼭 먼저 계산하고 싶으면 어떡해? 2 + 3을 먼저 해서 5를 만들고, 거기에 4를 곱해서 20을 만들고 싶을 때 말이야. 자바한테 ‘이번 한 번만 VIP 무시해!’라고 말할 방법은 없어?”
솔라의 질문에 루나의 입가에 다시 옅은 미소가 번졌다. 그 질문에 대한 답은, 자바의 또 다른 강력한 규칙 안에 숨어 있었다.
5장: 루나, 괄호로 계산의 흐름을 조종하는 방법!
솔라의 손가락이 키보드 위를 잠시 떠다녔다. 그녀의 머릿속에는 방금 전 루나가 말해준 ‘VIP 티켓’ 비유가 맴돌았다. 곱셈과 나눗셈이 덧셈과 뺄셈보다 먼저 처리되는 우선순위 규칙. 그렇다면 그 규칙을 거스를 방법은 정말 없을까? 덧셈을 먼저 하라고, 자바에게 떼를 쓸 방법은?
‘수학에서는 이럴 때 괄호를 썼는데.’
그 생각이 떠오르자마자 솔라의 손가락이 움직였다. 그녀는 기존 코드 2 + 3 * 4 아래에, 자신의 의도를 담은 새로운 코드를 한 줄 추가했다. 반신반의하는 마음이었다.
System.out.println(2 + 3 * 4);
System.out.println((2 + 3) * 4);
실행 버튼을 누르자, 두 개의 숫자가 나란히 콘솔 창에 나타났다. 첫 번째 줄에는 익숙한 오답 ‘14’가, 그리고 두 번째 줄에는 그녀가 간절히 원했던 바로 그 숫자 ‘20’이 찍혀 있었다.
“됐다! 20이 나왔어!”
솔라는 저도 모르게 작은 환호성을 질렀다. 괄호가 통했다. 하지만 기쁨도 잠시, 그녀는 고개를 갸웃거렸다. 괄호는 그저 수학 식을 보기 좋게 묶어주거나, 사람이 읽기 편하게 구분하는 용도라고 막연히 생각해왔다. 그런데 이렇게 계산 결과 자체를 바꿔버릴 정도의 힘이 있었던가?
“언니, 괄호를 쓰니까 되긴 했는데… 이게 그냥 ‘여기부터 읽어줘’ 하고 알려주는 힌트 같은 거야? 아니면 더 강력한 규칙이 있는 거야?”
루나는 솔라의 질문에 대답하는 대신, 솔라가 방금 사용했던 ‘VIP 티켓’ 비유를 다시 가져왔다.
“솔라, 네가 쓴 그 괄호는 그냥 VIP 티켓이 아니야.”
루나는 화면의 (2 + 3) * 4 코드에서 괄호 부분을 손가락으로 가리켰다.
“저건 모든 VIP들을 무시하고 무조건 가장 먼저 입장할 수 있는, 최상위 등급의 ‘프리패스(Free Pass)’ 같은 거야. 자바는 코드를 읽다가 괄호를 만나면, 다른 어떤 VIP 연산자가 줄을 서 있든 상관없이 무조건 괄호 안의 세계부터 해결하고 나와.”
‘프리패스.’ 그 단어가 괄호의 역할을 명확하게 정의해 주었다. 괄호는 단순한 구분선이나 힌트가 아니었다. 연산자들의 우선순위라는 기존 질서를 완전히 무시하고, 자신만의 계산을 강제하는 절대적인 명령이었던 것이다.
솔라는 두 코드의 실행 과정을 머릿속으로 다시 그려보았다. 루나의 비유를 적용하자 모든 것이 명확해졌다.
2 + 3 * 4의 세계:
- 자바가 식을 훑는다.
+(일반)와*(VIP)를 발견. - VIP인
*를 먼저 처리한다.3 * 4는12가 된다. - 남은 식
2 + 12를 계산해14를 얻는다.
(2 + 3) * 4의 세계:
- 자바가 식을 훑는다.
()(프리패스)를 발견. - 다른 모든 것을 멈추고 괄호 안으로 들어간다.
2 + 3을 계산해5를 얻는다. - 괄호 안의 세계가
5라는 하나의 값으로 정리되었다. 이제 식은5 * 4가 된다. - 남은 계산을 마저 처리해
20을 얻는다.
“와… 괄호는 그냥 우선순위가 제일 높은 게 아니라, 아예 계산의 흐름을 조종하는 도구였구나. 내가 원하는 대로 계산 순서를 직접 만들 수 있는 거네.”
솔라는 감탄하며 중얼거렸다. 이제 그녀는 더 이상 자바가 정해놓은 순서에 끌려다니지 않아도 되었다. 연산자의 종류를 파악하고, 우선순위를 인지하며, 필요할 때는 괄호라는 ‘프리패스’를 사용해 계산의 흐름을 직접 지휘할 수 있게 된 것이다.
이제 정말로 간단한 계산기 프로그램을 만들 수 있을 것 같았다. 솔라는 맨 처음, 자신을 혼란에 빠뜨렸던 ‘계산기 만들기’라는 목표를 떠올렸다. 그녀는 지금까지 배운 모든 규칙을 시험해 볼 만한, 조금 더 복잡한 식을 자신 있게 키보드로 입력하기 시작했다.
System.out.println((150 / 2 + 5) * 2 - 10 % 3);
코드를 완성한 솔라는 실행 버튼을 누르는 대신, 펜을 들고 연습장에 직접 계산 순서를 써 내려갔다.
- 가장 먼저 프리패스!
(150 / 2 + 5)안으로. - 괄호 안에도 VIP가 있네.
150 / 2(정수 나누기)를 먼저 계산하면75. - 괄호 안은
75 + 5가 되고, 결과는80. - 이제 원래 식은
80 * 2 - 10 % 3이 됐다. - 다시 보니 VIP가 둘이나 있네.
*와%. 왼쪽부터 순서대로. 80 * 2는160. 이제 식은160 - 10 % 3.- 남은 VIP
10 % 3을 계산하면1. - 마지막으로
160 - 1을 계산하면…159.
“정답은 159!”
솔라는 자신의 예측을 큰 소리로 외치며, 비로소 실행 버튼을 눌렀다. 잠시 후, 콘솔 창에 그녀가 예측했던 숫자 159가 정확히 나타났다.
자신의 손으로 코드의 실행 흐름을 완벽하게 읽어내고 결과를 예측해낸 순간이었다. 더 이상 연산자는 그저 수학 기호가 아니었다. 값의 종류를 보고 행동을 바꾸는 나눗셈, 첫 번째 값의 부호를 따르는 나머지, 저마다 다른 힘을 가진 우선순위, 그리고 그 모든 질서를 지배하는 괄호까지. 이 모든 규칙이 모여 하나의 계산식을 살아 움직이게 만드는 설계도임을 깨달았다. 솔라는 이제 그 설계도를 읽는 법을 알게 되었다. 루나는 그런 동생을 보며 조용히 미소 지었다.