본문 바로가기

[모던JS] 012. [기본] 기본 연산자와 수학(산술 연산자, 연산자 우선순위, 할당 연산자, 복합 할당 연산자, 증감 연산자, 전후위형 증감연산자, 비트연산자, 쉼표연산자)

codeConnection 2024. 3. 25.

원문

피연산자(operand), 단항(unary), 이항(binary)

피연산자 (operand)

연산자가 연산을 수행하는 대상.
인수(arguments)라고도 한다.

console.log( 5 * 2 ); // 10

위의 예제에서 5와 2가 피연산자이다.

단항 (unary), 이항 (binary)

피연산자의 개수로 단항이냐, 이항이냐 구분한다.
예를 들어 마이너스 연산자가 단항 연산자와 이항 연산자 둘 다 될 수 있다.

let x = 1;
x = -x;

console.log(x); // -1

위 예제에서 마이너스 연산자(-)는 x라는 하나의 피연산자, 즉 단항을 받아서 값을 반대로 뒤집었다. (양수 -> 음수)

let x = 3, y = 5;
console.log (y - x); // 2

위 예제에서 마이너스 연산자(-)는 좌항으로 y를, 우항으로 x를 받아 이항 연산자가 되었고, 좌항에서 우항을 빼는 연산을 하게 되었다.

수학 연산자

연산자 의미
+ 덧셈
= 뺄셈
* 곱셈
/ 나눗셈
% 나머지
** 거듭제곱

나머지 연산자 (%) (remainder operator)

비율을 나타내는 퍼센트와는 관련이 없고 나누고 나머지를 반환하는 연산자이다.

좌항에서 우항을 나누고 나머지를 반환한다.

console.log( 5 % 2 ); // 1
// 5 나누기 2를 한 나머지 값인 1 반환

나머지 연산자로 홀짝 구분하기

if ( 5 % 2 === 0) {
    console.log("짝수입니다");
} else {
    console.log("홀수입니다.");
} // 홀수입니다

if (5 % 2 !== 0) {
    console.log("홀수입니다");
} else {
    console.log("짝수입니다");
} // 홀수입니다

5를 2로 나눴을 때 나머지가 0이라면 그것은 2의 배수인 것이고, 2의 배수라는 것은 짝수를 의미한다.
=== 는 좌항과 우항이 같냐는 것이고, !==는 같지 않냐는 의미이다. 이는 비교 연산자 편에서 다룬다.
참고로 숫자 0도 짝수이다.

거듭제곱 연산자 (**) (exponentiation operator)

좌항을 우항'번' 만큼 곱한 값을 반환한다.

console.log( 2 ** 2 ); // 4 // 2 * 2
console.log( 2 ** 3 ); // 8 // 2 * 2 * 2
console.log( 2 ** 4 ); // 16 // 2 * 2 * 2 * 2

위처럼 2, 3, 4와 같은 정수만 계산하는 것은 아니고 모든 숫자를 계산할 수 있다. 1/2, 1/3을 넣으면 제곱근, 세제곱근을 구할 수 있다.

console.log ( 2 ** (1/2) ); // 2 // 1/2의 거듭제곱은 제곱근
console.log ( 2 ** (1/3) ); // 2 // 1/3의 거듭제곱은 세제곱근

이항 덧셈 연산자 (+)

문자열 잇기 (string + string)

문자열끼리 더하면 문자열을 하나로 이을 수 있다.

let text = "my" + "string";
console.log(text); // mystring

좌항과 우항 중 하나라도 string 타입이면 나머지 항도 string 타입으로 묵시적 형 변환이 발생한다.

console.log("I am " + 21 + " years old"); // I am 21 years old.
// 21은 number type이지만 string type으로 형 변환되었다.

console.log( 2 + 2 + "1"); // 41

두번째 예제를 살펴보면 위 식은 연산을 두 번 한다.

  1. 2 + 2
  2. 위의 결과 + "1"

즉 첫번째 계산에서 2 + 2은 둘 다 number type이니 실제 사칙연산을 해서 4라는 결과를 반환하고,
4 + "1"에서는 둘 중 하나가 string 타입으로 4도 string 타입으로 형 변환되어 사칙연산이 아니라 문자열을 잇는 연산을 하게 되었다.

단항 덧셈 연산자 (+)

단항 피연산자가 숫자일 경우 아무런 영향을 끼치지 않는다.

// 단항이 양수일 때
let x = 1;
console.log( + x ); // 1

// 단항이 음수일 때
let x = -1;
console.log( + x ); // 1

단항 덧셈 연산자 = Number()

단항 피연산자가 숫자가 아닐 때는 숫자형으로 형 변환한다.
Number(...)과 동일하다.

console.log( + true ); // 1
console.log( + "" ); // 0

true는 숫자 값으로 1, ""는 값없음, 숫자형으로는 0이다. 이는 (011. 원시 타입 자료형의 형 변환)에서 다룬다.

사용 예시

form을 통해 사용자에게 데이터를 입력받으면 문자열로 저장된다. 이 때 숫자형으로 전환시킬 때 사용한다.

let apples = "2";
let oranges = "3";

console.log(apples + oranges); // 23

위의 결과는 apples라는 변수의 값이 "2"라는 string이고, orenage도 string이기 때문에 이상 덧셈 연산자는 문자열을 잇는 기능을 하여 2라는 문자와 3이라는 문자를 연결시켜 23이라는 결과를 반환한다. 이 때 문자열인 2와 3을 숫자형으로 전환시키면 된다.

let apples = "2";
let oranges = "3";

console.log( +apples + +oranges); // 5

각 변수 앞에 덧셈 단항 연산자를 넣어서 Number()와 동일하게 문자열을 숫자형으로 형 변환시켰다.

연산자의 우선순위 (precedence)

우선순위 테이블(precedence table | MDN) 참고

위의 표에는 너무 많으니 주요 연산자의 우선순위를 아래에 정리하였다.

순위 연산자 연산자 이름
3 = 할당
13 - 뺄셈
13 + 덧셈
15 / 나눗셈
15 * 곱셈
16 ** 지수(거듭제곰)
17 - 단항 부정
17 + 단항 덧셈

주의 숫자가 낮은 것이 우선순위가 높은 것이 아니라 낮은 것이 높다.

단항 부정은 마이너스 연산자를 단항 연산자로 사용하면 양수를 음수로, 음수를 양수로 바꾸는 것을 말한다.

위의 표를 보면 단항 덧셈 연산자가 이항 덧셈 연산자보다 순위가 높다. 따라서 위의 예제에서 +apples에 사용된 단항 덧셈 연산자(number type 형 변환)가 apples와 oranges를 더하는 이항 덧셈 연산자보다 먼저 연산된 것이다.

할당 연산자 (=) (assignment)

변수의 값을 할당할 때와 같이 값을 할당할 때 사용하는 연산자이다. 위의 표에서 보면 우선순위가 매우 낮다.

x = 2 * 2 + 1 과 같은 표현식에서 2 * 2가 먼저 계산이 이루어지고 그 값과 + 1이 계산이 이루어진 뒤 최종적인 값이 x라는 변수에 할당(=)되는 것이 할당 연산자의 우선순위가 낮아서이다.

할당 연산자 체이닝

할당 연산자를 여러번 사용하는 것을 말한다.

let a, b, c;

a = b = c = 2 + 2;

console.log(a); // 4
console.log(b); // 4
console.log(c); // 4

이렇게 할당 연산자를 여러개 중첩해서 사용하는 경우, 보통 평가는 좌측에서부터 이루어지지만 이 경우에는 우측에서부터 이루어진다.

즉 2+2가 먼저 계산된 후 그 값이 c에 할당되고, c가 b에 할당되며, b가 a에 할당된다. 그러나 위 형태는 가독성이 좋지 않으므로 아래와 같이 평가의 순서대로 여러 줄에 나누어서 할당하는 것이 권장된다.

let a, b, c;

c = 2 + 2;
b = c;
a = c; // a를 b로 할당하지 않고 어차피, 직관적으로 값이 보이는 c로 할당했다.

복합 할당 연산자 (+= -=) (Compound Assignment Operators)

복합할당 연산자는 산술 연산(+ - 등)과 할당 연산(=)을 결합하여 변수의 값을 수정하는 방법인데, 이를 사용하면 코드가 간결해지기에 사용한다.

쉽게 말해 변수에 값을 초기화 해놓고, 그 값을 계속 수정할 때 사용한다는 의미이다.

let n = 2; // 최초 변수 선언 및 값 초기화

n = n + 5; // n의 값을 최초 값에 +5한 값으로 재할당
n = n * 2; // n의 값을 최초 값에 *2한 값으로 재할당

위 처럼 계속 변수를 수정할 때 할당 연산자(=)와 산술 연산자(+ - 등)을 하나로 합치면 한 번에 코드를 작성할 수 있다. 아래 예제는 위와 같은 식이다.

let n = 2; // 2

n += 5; // 7
n *= 2; // 14
복합 할당 연산자 기능
+= 덧셈 대입
-= 뺄셈 대입
*= 곱셈 대입
/= 나눗셈 대입
%= 나눗셈 대입

복합 할당 연산자의 우선순위는 할당 연산자와 같다. 따라서 거의 대부분의 연산이 종료된 후 값을 할당한다.

증가·감소 연산자 (++ --) (in/decrese)

연산자 기능
++ 1 증가
-- 1 감소
let counter = 2;
counter++; // conter + 1
console.log(counter); // 3

let counter = 2;
conter--; // counter - 1
console.log(counter); // 1

증가 감소 연산자는 위와 같이 변수에만 사용 가능하다.
직접적으로 숫자에 5++와 같이 사용할 수 없다.

전·후위형 증가·감소 연산자

증가 감소 연산자는 변수의 앞에 올 수도 있고, (++counter,)
뒤에 올 수도 있다. (counter++)

앞에 오는 경우를 전위형(prefix form) 뒤에 오는 경우를 후위형(postfix form)이라 한다.

연산자 값의 반환 시기 예시
전위 단항 연산자 값을 증감한 후 곧바로 증감된 값을 반환 ++i, --i
후위 단항 연산자 현재의 값을 반환한 후 값을 증감 시킴 i++, i--

위 표의 의미를 아래 예제에서 살펴보겠다.

let counter = 0;

console.log(++counter); // 1
console.log(counter); // 1

위의 예제는 전위 증가 연산자를 사용하여 값을 곧바로 1로 반환하였다.

let counter = 0;

console.log(counter++); // 0
console.log(counter); // 1

위의 예제는 후위 증가 연산자를 사용하여 그 연산자가 사용된 변수에 1이 증가한 것을 바로 반환하지 않고 그 후에 반환한다. 그래서 그 순간이 아닌, 다음 라인에서 counter 변수의 값이 1로 출력되었다.

바로 값을 사용하고 싶다면 전위 연산자를 사용하면 된다.

증가·감소 연산자의 우선순위

대부분의 산술 연산자보다 우선순위가 높다. 따라서 표현식 중간에 사용해도 무리가 없다.

// 표현식 중간에 전위 연산자를 사용하는 경우
let counter = 1;
console.log( 2 * ++counter ); // 4

// 표현식 중간에 후위 연산자를 이용하는 경우
let counter = 1;
console.log( 2 * counter++ ); // 1
console.log(counter); // 2

후위 연산자를 사용하는 경우에는 해당 표현식에서는 원래의 변수 값을 사용하고 그 이후에 반환하기 때문에 다음 라인에서 뒤늦게 2를 반환하는 것을 알 수 있다.

그런데 위와 같은 예제도 코드 한 줄에 2 * counter라는 연산과 counter를 1 증가시키는 연산이 함께 존재하다보니, 빠르게 코드를 읽어나갈 때 가독성이 떨어질 수 있다.

따라서 아래와 같이 코드 한 줄에 하나의 기능을 넣는 것이 가독성 측면에서 좋다.

let counter = 1;
counter++;
console.log( 2 * counter ); // 4

비트 연산자 (& | ^ ~ << >> >>>) (bitwise operator)

비트 연산자(bitwise operator)는 32비트 정수로 변환하여 이진 연산을 수행한다.

자바스크립트 뿐만 아니라 대부분의 프로그래밍 언어에서 지원한다.

비트 연산자 기능
& AND
  (원화표시키)
^ XOR
~ NOT
<< LEFT SHIFT
>> RIGHT SHIFT
>>> ZERO-FILL RIGHT SHIFT

일반적인 코딩 환경에서는 잘 사용하진 않는다. 저수준 언어(2진 표현)를 다룰 때 주로 사용된다.

하지만 암호를 다뤄야 하는 경우에는 유용하게 사용되니 MDN 문서를 참고하면 좋다.

쉼표 연산자 (,) (comma operator)

잘 사용하지 않는다.
사용 목적은 코드를 짧게 사용하기 위해 사용한다.
우리가 쓸 일은 없고 이것이 쓰인 코드를 만났을 때 코드를 읽기 위한 정도로 알고 넘어가면 좋다.

프레임워크에서는 이 연산자를 사용하는 경우가 많다.

쉼표 연산자로 연산을 구분한 경우 표현식에서 가장 마지막 표현식의 결과만 반환되고 앞의 연산은 버려진다는 특징이 있다.

let a = (1 + 2, 3 + 4);

console.log(a); // 7

a의 값이 7로 반환된 이유는 쉼표 이전의 1 + 2의 값은 버려졌고 3 + 4의 값만 반환됐기 때문이다.

쉼표 연산자의 우선순위는 할당 연산자가 3이었던 것에 비해 이것보다 더 낮다.

만약 위 식에서 괄호가 없었다면, 덧셈 연산자가 각각 먼저 수행되고 할당 연산자가 수행되고 그 다음 쉼표 연산자가 수행된다.

소스코드를 압축해야 하는 프레임워크에서는 한 줄에서 여러 기능을 수행하기 위해 아래처럼 사용하곤 한다.

for ( a = 1, b = 3, c = a * b; a < 10; a++) {}
...

연습문제

문제

  1. 아래에서 a, b, c, d의 값은?
  2. let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

2. 아래에서 a, x의 값은?
```javascript
let a = 2;

let x = 1 + (a *= 2);
  1. 아래에서 각 표현식의 값은?
  2. "" + 1 + 0 "" - 1 + 0 true + false 6 / "3" "2" * "3" 4 + 5 + "px" "$" + 4 + 5 "4" - 2 "4px" - 2 7 / 0 " -9 " + 5 " -9 " - 5 null + 1 undefined + 1 " \t \n" - 2
  3. 덧셈 고치기
    아래 코드에서는 사용자에게 form에서 값을 두 개를 입력받아 합산하는 코드를 작성한 것이다.
    promt 모달창을 이용해서 form의 기본값을 1, 2로 입력했고, 사용자가 기본값을 수정하지 않으면 값이 3이 나오도록 하려고 했는데 "12"라는 값이 출력된다.
    이런 상황에서 값이 3이 나오도록 수정하시오.
  4. let a = prompt("첫번째 숫자를 입력하세요", 1); let b = prompt("두번째 숫자를 입력하세요", 2);

alert(a + b); // 12


### 정답

1. 1번 문제
a = 2
b = 2
c = 2
d = 1
```javascript
let a = 1, b = 1;

alert( ++a ); // 2, 전위형은 증가 후의 값을 반환합니다.
alert( b++ ); // 1, 후위형은 증가 전의 값을 반환합니다.

alert( a ); // 2, 값이 1만큼 증가합니다.
alert( b ); // 2, 값이 1만큼 증가합니다.
  1. 2번 문제
    a = 4, x = 5
  2. 3번 문제
  3. "" + 1 + 0 = "10" // (1) "" - 1 + 0 = -1 // (2) true + false = 1 6 / "3" = 2 "2" * "3" = 6 4 + 5 + "px" = "9px" "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN 7 / 0 = Infinity " -9 " + 5 = " -9 5" // (3) " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7)

해답

"" + 1 + 0 = "10"
// "" + 1에서 문자열이 하나라도 있기 때문에 우항 1은 문자열로 형 변환 된다. 공백은 0이다.
// "1" + 0에서 좌항이 문자열이기 때문에 역시 우항 0도 문자열로 형 변환 된다. 따라서 "1" + "0" = "10"

"" - 1 + 0 = 
// 위는 덧셈 연산자의 경우였고, 뺄셈 연산자는 문자열로 형 변환 되지 않는다. 따라서 0 - 1 = -1 숫자가 된다.
// -1 + 0 = -1

true + false = 1
// true는 산술연산자를 만나면 1, false는 0이다.

6 / "3" = 2
// 나눗셈도 문자열을 형 변환 시킨다.

"2" * "3" = 6
// 곱셈 연산자는 산술 연산자를 만나면 숫자형으로 형 변환 된다.

4 + 5 + "px" = "9px"
// 4 + 5 = 9
// "9" + "px" = 문자열 "px"이 숫자 9를 문자열로 형 변환 시킨다.

"$" + 4 + 5 = "$45"
// "$" + "4" = "$4" 문자 $가 숫자 4를 문자열로 형 변환 시킨다.
// "$4" + "5" = "$45" 마찬가지로 숫자 5가 문자열로 형 변환 되었다.

"4" - 2 = 2
// 덧셈 연산자를 제외 하고 나머지 산술 연산자는 문자열을 숫자형으로 형 변환한다.

"4px" - 2 = NaN
// 덧셈을 제외한 모든 산술 연산자는 문자열을 숫자로 형 변환 하는데, "4px"는 진짜 문자가 있기 때문에
// 형 변환이 되지 않아 NaN이 반환된다.

7 / 0 = Infinity
// 모든 숫자에 0을 나누면 Infinity의 값이 반환된다.

" -9 " + 5 = " -9 5"
// 덧셈 연산자는 산술 연산자를 만나면 숫자형을 문자열로 형 변환한다. 공백도 문자이므로 공백도 포함되었다.

" -9 " - 5 = -14
// 뺄셈 연산자가 문자열 " -9 "를 숫자형으로 형 변환 하였는데
// 공백은 모두 제거된다.

null + 1 = 1
// null은 숫자로 형 변환 시 0이 된다.
// 덧셈 연산자는 문자열이 있으면 나머지 항의 숫자도 문자열로 바꾸지만
// null은 문자 타입이 아니다.

undefined + 1 = NaN
// null은 숫자로 0이지만, undefined는 NaN이다.

" \t \n" - 2 = -2
// 덧셈 연산자를 제외한 모든 연산자는 모든 문자열을 숫자로 형 변환한다.
// " \t \n"에는 \t와 \n 앞에 진짜 공백이 두 개가 있다.
// 그리고 \t, \n은 공백을 넣는 문자다. 즉 이 문자열에는 공백만 4개다.
// 문자열을 숫자형으로 형 변환할 때 공백은 전부 제거된다.
// 모두 제거하고 나니 "" 값이 없다. 값이 없음은 숫자로 형 변환 시 0이다.

산술 연산자별 문자열의 묵시적 형 변환 여부
|산술 연산자|문자열 묵시적->숫자형 형 변환|
|:-:|:-:|
|+|X|
|-|O|
|*|O|
|/|O|
|%|O|

덧셈은 문자열이 있으면 반대로 숫자형을 문자열로 형 변환 시킨 뒤 string + string으로 만든다.

즉 덧셈 연산자를 제외한 모든 산술 연산자는 문자열을 숫자열로 형 변환 시킨다.

  1. 4번 문제
  2. let a = prompt("첫번째 숫자를 입력하세요", 1); let b = prompt("두번째 숫자를 입력하세요", 2);

alert(+a + +b); // 3
// 또는
alert(Number(a) + Number(b)); // 3

```javascript
let a = +prompt("첫번째 숫자를 입력하세요", 1);
let b = +prompt("두번째 숫자를 입력하세요", 2);

alert(a + b); // 3

입력 폼에서 받은 값은 string 타입으로 저장된다. 따라서 숫자형으로 형 변환 해주어야 한다. + 단항 연산자를 사용해도 되고, Number() 함수를 사용해도 된다.
alert 코드 자체에서 형 변환 하는 방법도 있고, prompt에서 값을 받을 때부터 형 변환 하는 방법이 있다.

댓글