코테 언어를 파이썬에서 JS로 바꾸기로 마음먹은 뒤 전에 풀었던 알고리즘들을 다시 풀어보고 있다. 연산자 끼워넣기 문제를 풀던 중 문제가 발생했는데 작성한 코드가 분명 예제 입력에 대해서도 정상적으로 작동되었고 다른 사람들이 올린 예제 입력에서도 잘 작동되었는데 제출을 하면 정답이 아니라고 채점되었다. 이미 파이썬으로 풀어본 문제여서 JS로 변환만 하는 느낌으로 풀었지만 파이썬에서는 정답이였던 코드가 JS에서 동작하지 않아 당황했다. 아무리 디버깅을 해도 문제가 될만한 부분을 찾지 못하고 다른 원인들을 찾아보던 중 결국 해당 코드의 문제를 찾을 수 있었다.
const fs = require("fs");
const filePath = process.platform === "linux" ? "/dev/stdin" : "input.txt";
const input = fs.readFileSync(filePath).toString().trim().split("\n");
const n = parseInt(input[0])
const num_arr = input[1].split(" ").map(Number);
const oper_cnt = input[2].split(" ").map(Number);
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으로 떨어진다.
console.log(0 / 3); // 0
console.log(0 * 3); // 0
console.log(0 / -3); // -0
console.log(0 * -3); // -0
위의 코드를 보면 0을 양수로 나누거나 곱한 경우 그냥 0이 출력되지만 0을 음수로 나누거나 곱한 경우는 -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은 -0 그대로 출력하면 -0이 출력되고 문자열로 변경하면 '0'으로 출력된다. 또 0, 즉 +0과 비교하면 true를 반환한다...
그러다면 왜 JS는 -0이라는 개념이 존재하는 걸까?
JavaScript에서 -0은 IEEE 754 표준에 따라 부동 소수점 숫자를 표현하는 방법으로 인해 발생한다.
IEEE 754는 부동 소수점 숫자를 저장하는 방법을 정의하는 표준이다. 이 표준은 숫자를 64비트 이진수 형태로 표현하는데, 이진수에서 첫 번째 비트는 부호를 나타내고, 그 뒤에 11비트는 지수를, 나머지 52비트는 유효숫자를 나타낸다.
부동 소수점 수는 무한대, 양의 무한대, 음의 무한대, NaN(숫자가 아님)와 같은 특수한 값들을 가질 수 있는데 이 표준에서는 0을 나타내기 위해 +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함수를 사용하면 된다.
// NaN과 NaN을 비교할 때, Object.is() 함수는 true를 반환한다. 하지만 === 연산자는 false를 반환한다.
// +0과 -0을 비교할 때, Object.is() 함수는 false를 반환한다. 하지만 === 연산자는 true를 반환한다.
console.log(+0 === -0) // true
console.log(Object.is(+0,-0)) // false
+0과 -0의 개념을 알고 차이을 알고나니 문제를 해결할 수 있었다. 해결한 코드는 아래와 같다. 출력 부분만 간단히 바꿔 문제를 해결했다.
const fs = require("fs");
const filePath = process.platform === "linux" ? "/dev/stdin" : "input.txt";
const input = fs.readFileSync(filePath).toString().trim().split("\n");
const n = parseInt(input[0])
const num_arr = input[1].split(" ").map(Number);
const oper_cnt = input[2].split(" ").map(Number);
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);
// -0 === 0은 true, 즉 조건문에서는 -0이 0과 동일하게 false처럼 취급받기 때문에
// -0 또는 0인경우는 0을 출력하고 나머지의 경우는 저장한 변수 값을 출력하면 된다.
console.log(max_num ? max_num : 0);
console.log(min_num ? min_num : 0);