FoxRain Logo
FoxRain
devlog

JS, 두 종류의 0

JS, 두 종류의 0 image

코딩 테스트 언어를 파이썬에서 JS로 바꾸기로 마음먹은 뒤 JS로 문제 해결에 익숙해지기 위해 전에 풀었던 알고리즘들을 다시 풀어보고 있다. 이미 파이썬으로 풀어본 문제여서 JS로 변환만 하는 느낌으로 풀었지만 파이썬에서는 정답으로 채점되던 코드가 JS에서 동작하지 않아 당황했다. JS로 작성된 코드가 분명 예제 입력에 대해서도 정상적으로 작동되었지만 제출 시 오답으로 채점되었다. 아무리 디버깅을 해도 문제가 될만한 부분을 찾지 못하고 다른 원인들을 찾아보던 중 결국 해당 코드의 문제를 찾을 수 있었다.

문제의 코드

아래의 코드는 연산자 끼워넣기에서 문제가 발생했던 코드다. N개의 수와 연산에 사용할 수 있는 연산자의 종류와 개수가 주어지면 N개의 수와 사용할 수 있는 연산자를 가지고 최솟값과 최댓값을 출력하면 되는 문제이다.

예를 들어 3, 4, 5와 한 개의 덧셈(+), 한 개의 곱셈(×) 이 주어지면 최댓값은 35이고 최솟값은 17이 된다.

// input 값 예시
// 3 (주어지는 수의 개수)
// 3 4 5 (3, 4, 5 가 주어짐)
// 1 0 1 0 (차례대로 덧셈, 뺄셈, 곱셈, 나눗셈의 개수 => 덧셈 1개 곱셈 1개)

const n = parseInt(input[0]); // 3
const num_arr = input[1].split(' ').map(Number); // [3, 4, 5]
const oper_cnt = input[2].split(' ').map(Number); // [1, 0, 1, 0]
let max_num = Number.MIN_SAFE_INTEGER;
let min_num = Number.MAX_SAFE_INTEGER;

function dfs(idx, result, oper_arr) {
  if (idx === n) {
    max_num = Math.max(result, max_num);
    min_num = Math.min(result, min_num);
    return;
  }
  let num = num_arr[idx];
  if (oper_arr[0] > 0) {
    oper_arr[0] -= 1;
    dfs(idx + 1, result + num, oper_arr);
    oper_arr[0] += 1;
  }
  if (oper_arr[1] > 0) {
    oper_arr[1] -= 1;
    dfs(idx + 1, result - num, oper_arr);
    oper_arr[1] += 1;
  }
  if (oper_arr[2] > 0) {
    oper_arr[2] -= 1;
    dfs(idx + 1, result * num, oper_arr);
    oper_arr[2] += 1;
  }
  if (oper_arr[3] > 0) {
    oper_arr[3] -= 1;
    dfs(idx + 1, parseInt(result / num), oper_arr); // 나눗셈은 정수 나눗셈으로 몫만 취함
    oper_arr[3] += 1;
  }
}

dfs(1, num_arr[0], oper_cnt);
console.log(max_num);
console.log(min_num);

위의 코드에서는 나눗셈을 수행할 때, 나누는 수가 음수이고 몫이 0일 때 결과는 -0이 되기 때문에 특정 예제에서는 오류를 발생시켰던 것이다.

+0과 -0?

자바스크립트에는 양의 영(+0)음의 영(-0) 이 존재한다. 음의 영은 표기만 -0으로 하는 것이 아니다. 특정 수식의 연산 결과 또한 -0으로 떨어진다. 0을 양수로 나누거나 곱한 경우 그냥 0이 출력되지만 0을 음수로 나누거나 곱한 경우는 -0이 출력된다.

console.log(0 / 3); // 0
console.log(0 * 3); // 0

console.log(0 / -3); // -0
console.log(0 * -3); // -0

-0은 출력하면 -0이 출력되고 문자열로 변경하면 '0'으로 출력된다. +0과 비교하면 true를 반환한다. 그러다면 왜 JS는 -0이라는 개념이 존재하는 걸까?

let minusZero = 0 / -3;
console.log(minusZero); // -0
console.log(minusZero.toString()); // '0'
console.log(JSON.stringify(minusZero)); // '0'
console.log(0 === minusZero); // true
console.log(0 > minusZero); // false

-0의 존재 이유

JavaScript에서 -0은 IEEE 754 표준에 따라 부동 소수점 숫자를 표현하는 방법으로 인해 발생한다.

IEEE 754는 부동 소수점 숫자를 저장하는 방법을 정의하는 표준이다. 이 표준은 숫자를 64비트 이진수 형태로 표현하는데, 이진수에서 첫 번째 비트는 부호를 나타내고, 그 뒤에 11비트는 지수를, 나머지 52비트는 유효숫자를 나타낸다. 부호 비트가 0인 경우는 양수, 1인 경우는 음수를 나타내는데 부호 비트만 다르고 나머지가 모두 0인 경우에는 +0과 -0으로 구분되는 것이다. +0과 -0을 구분하는 이유는 특정 수학적 또는 공학적인 계산에서 부호가 중요할 수 있기 때문이다.

이제 JS의 양의 0과 음의 0에 대해 알아봤으니 JS에서는 이 값들을 어떻게 다루는지 알아봐야 한다.

+0과 -0 구별하기

JS에서 비교 연산자(== 또는 ===)를 사용할 때 +0과 -0은 동일한 값으로 간주한다. +0과 -0을 구별하기 위해서는 +Infinity-Infinity를 사용하면 된다.

console.log(+0 === -0); // true

// 1/+0 = 양의 무한대
console.log(1 / +0); // Infinity
console.log(1 / +0 === +Infinity); // true

// 1/-0 = 음의 무한대
console.log(1 / -0); // -Infinity
console.log(1 / -0 === -Infinity); // true

var positiveZero = +0;
var negativeZero = -0;
console.log(positiveZero === negativeZero); // true
console.log(1 / positiveZero === 1 / negativeZero); // false

또는 ES6부터 지원하는 Object.is함수를 사용하면 된다.

console.log(+0 === -0); // true
console.log(Object.is(+0, -0)); // false

문제 해결

+0과 -0의 개념을 알고 차이을 알고나니 문제를 해결할 수 있었다. 해결한 코드는 아래와 같다. -0 === 0은 true, 즉 조건문에서는 -0이 0과 동일하게 falsy하게 취급받기 때문에 -0 또는 0인경우는 0을 출력하고 나머지의 경우는 저장한 변수 값을 출력하면 된다.

- console.log(max_num);
- console.log(min_num);

+ console.log(max_num ? max_num : 0);
+ console.log(min_num ? min_num : 0);