아래는 자바스크립트에서 부동소수점 연산으로 인해 발생하는 미세 오차 문제와, 이를 해결하기 위한 여러 방법을 이론과 함께 설명한 예시입니다.
자바스크립트는 IEEE 754 방식의 부동소수점 숫자 표현을 사용합니다. 이로 인해 0.1 + 0.2와 같이 간단해 보이는 연산에서도 미세한 오차가 발생할 수 있습니다. 예를 들어, 여러 소수점 값을 누적하여 계산하면 100.00000000000006과 같이 100과 정확히 일치하지 않는 결과가 나타날 수 있습니다. 이번 글에서는 이러한 부동소수점 오차 문제의 원인과 함께, 이를 해결하기 위한 다양한 자바스크립트 코드 예제 및 외부 라이브러리를 활용한 해결 방법을 소개합니다.
부동소수점 방식은 컴퓨터 내부에서 이진수로 실수를 표현하는 방식입니다. 이 과정에서 일부 소수값은 정확하게 표현되지 않으며, 계산 결과에 아주 미세한 오차가 발생하게 됩니다. 예를 들어,
console.log(0.1 + 0.2); // 0.30000000000000004
와 같이 예상과 다른 결과가 나타날 수 있습니다. 이러한 문제는 특히 합산이나 누적 연산에서 누적되어 의도치 않은 결과를 초래할 수 있습니다.
실무에서는 여러 소수점 값을 합산하여 목표값(예: 100)과 비교할 때, 부동소수점 연산으로 인한 미세 오차를 감안하여 비교하는 방법을 사용합니다.
아래에서는 네 가지 방법을 소개합니다.
자바스크립트에서는 Number.EPSILON을 사용하여 두 수의 차이가 아주 작은 경우(예: 1e-16 정도)에는 동일하다고 판단할 수 있습니다.
원리: 두 값의 차이가 Number.EPSILON보다 작으면 오차 범위 내에서 동일하다고 간주합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>소수점 합계 비교 (Epsilon 활용)</title>
<style>
input { margin: 5px; }
</style>
</head>
<body>
<h2>소수점 값 입력 후 합계가 100인지 확인</h2>
<input type="text" id="val1" placeholder="값 입력 (예: 24.913)">
<input type="text" id="val2" placeholder="값 입력 (예: 19.380)">
<input type="text" id="val3" placeholder="값 입력 (예: 28.971)">
<input type="text" id="val4" placeholder="값 입력 (예: 18.088)">
<input type="text" id="val5" placeholder="값 입력 (예: 8.648)">
<br>
<button onclick="checkSum()">합계 확인</button>
<p id="result"></p>
<script>
function getValues() {
const inputs = [
document.getElementById('val1').value,
document.getElementById('val2').value,
document.getElementById('val3').value,
document.getElementById('val4').value,
document.getElementById('val5').value
];
// 입력값을 Number로 변환 (빈 값은 0으로 처리)
return inputs.map(val => parseFloat(val) || 0);
}
// Epsilon을 이용한 비교 함수
function isApproximatelyEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON;
}
function checkSum() {
const values = getValues();
const sum = values.reduce((acc, num) => acc + num, 0);
const target = 100.0;
const resultEl = document.getElementById('result');
const epsilonEqual = isApproximatelyEqual(sum, target);
resultEl.innerHTML = `<strong>입력 값 합계:</strong> ${sum}<br>
<strong>Epsilon 비교:</strong> ${epsilonEqual ? '100과 동일함' : '100과 동일하지 않음'}`;
}
</script>
</body>
</html>
위 코드에서는 사용자가 입력한 값들을 parseFloat()로 숫자로 변환한 후 reduce()를 이용하여 합산합니다. 그 후 Number.EPSILON을 이용하여 미세한 차이를 무시하고 100과 동일한지 비교합니다.
소수점 값을 일정 배수로 확대하여 정수로 변환한 후 반올림하여 비교하는 방식입니다.
원리: 예를 들어 소수점 6자리까지 비교하고 싶다면 모든 값을 1,000,000배하여 정수로 만든 뒤 반올림하여 비교합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>소수점 합계 비교 (정수 변환)</title>
<style>
input { margin: 5px; }
</style>
</head>
<body>
<h2>소수점 값 입력 후 합계가 100인지 확인</h2>
<input type="text" id="val1" placeholder="값 입력 (예: 24.913)">
<input type="text" id="val2" placeholder="값 입력 (예: 19.380)">
<input type="text" id="val3" placeholder="값 입력 (예: 28.971)">
<input type="text" id="val4" placeholder="값 입력 (예: 18.088)">
<input type="text" id="val5" placeholder="값 입력 (예: 8.648)">
<br>
<button onclick="checkSumByRounding()">합계 확인</button>
<p id="result"></p>
<script>
function getValues() {
const inputs = [
document.getElementById('val1').value,
document.getElementById('val2').value,
document.getElementById('val3').value,
document.getElementById('val4').value,
document.getElementById('val5').value
];
return inputs.map(val => parseFloat(val) || 0);
}
function isEqualByRounding(a, b, multiplier = 1000000) {
return Math.round(a * multiplier) === Math.round(b * multiplier);
}
function checkSumByRounding() {
const values = getValues();
const sum = values.reduce((acc, num) => acc + num, 0);
const target = 100.0;
const resultEl = document.getElementById('result');
const roundedEqual = isEqualByRounding(sum, target);
resultEl.innerHTML = `<strong>입력 값 합계:</strong> ${sum}<br>
<strong>정수 변환 비교:</strong> ${roundedEqual ? '100과 동일함' : '100과 동일하지 않음'}`;
}
</script>
</body>
</html>
이 방식은 소수점 이하의 정밀도를 보장하기 위해 입력된 숫자에 일정 배수를 곱한 후 반올림하여 정수끼리 비교하는 방법입니다.
toFixed() 메서드는 소수점 이하를 지정한 자리수만큼 고정된 문자열로 반환합니다. 이 문자열을 비교하면 미세한 오차를 무시할 수 있습니다.
원리: 지정한 자릿수까지의 값을 문자열로 만든 후 비교하여, 부동소수점 오차를 제거합니다.
예제 코드:
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>소수점 합계 비교 (toFixed 사용)</title>
<style>
input { margin: 5px; }
</style>
</head>
<body>
<h2>소수점 값 입력 후 합계가 100인지 확인</h2>
<input type="text" id="val1" placeholder="값 입력 (예: 24.913)">
<input type="text" id="val2" placeholder="값 입력 (예: 19.380)">
<input type="text" id="val3" placeholder="값 입력 (예: 28.971)">
<input type="text" id="val4" placeholder="값 입력 (예: 18.088)">
<input type="text" id="val5" placeholder="값 입력 (예: 8.648)">
<br>
<button onclick="checkSumByFixed()">합계 확인</button>
<p id="result"></p>
<script>
function getValues() {
const inputs = [
document.getElementById('val1').value,
document.getElementById('val2').value,
document.getElementById('val3').value,
document.getElementById('val4').value,
document.getElementById('val5').value
];
return inputs.map(val => parseFloat(val) || 0);
}
function isEqualByFixed(a, b, decimals = 10) {
return a.toFixed(decimals) === b.toFixed(decimals);
}
function checkSumByFixed() {
const values = getValues();
const sum = values.reduce((acc, num) => acc + num, 0);
const target = 100.0;
const resultEl = document.getElementById('result');
const fixedEqual = isEqualByFixed(sum, target);
resultEl.innerHTML = `<strong>입력 값 합계:</strong> ${sum}<br>
<strong>toFixed 비교:</strong> ${fixedEqual ? '100과 동일함' : '100과 동일하지 않음'}`;
}
</script>
</body>
</html>
위 코드는 지정한 소수점 이하 자릿수(여기서는 10자리)로 문자열 변환 후 비교하여 오차 문제를 완화합니다.
부동소수점 오차 문제를 근본적으로 해결하려면, 외부 라이브러리를 활용하는 방법도 있습니다.
Big.js는 고정 소수점 산술을 지원하여, 아주 정밀한 계산을 가능하게 합니다. CDN을 이용해 쉽게 적용할 수 있습니다.
예제 코드:
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>소수점 합계 비교 (Big.js 사용)</title>
<style>
input { margin: 5px; }
</style>
<!-- Big.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/big.js/6.1.1/big.min.js"></script>
</head>
<body>
<h2>소수점 값 입력 후 합계가 100인지 확인 (Big.js)</h2>
<input type="text" id="bval1" placeholder="값 입력 (예: 24.913)">
<input type="text" id="bval2" placeholder="값 입력 (예: 19.380)">
<input type="text" id="bval3" placeholder="값 입력 (예: 28.971)">
<input type="text" id="bval4" placeholder="값 입력 (예: 18.088)">
<input type="text" id="bval5" placeholder="값 입력 (예: 8.648)">
<br>
<button onclick="checkSumBig()">합계 확인</button>
<p id="bresult"></p>
<script>
function getBigValues() {
const inputs = [
document.getElementById('bval1').value,
document.getElementById('bval2').value,
document.getElementById('bval3').value,
document.getElementById('bval4').value,
document.getElementById('bval5').value
];
// 빈 값은 0으로 처리
return inputs.map(val => new Big(val || 0));
}
function checkSumBig() {
const values = getBigValues();
let sum = new Big(0);
values.forEach(num => {
sum = sum.plus(num);
});
const target = new Big(100);
const resultEl = document.getElementById('bresult');
resultEl.innerHTML = `<strong>입력 값 합계:</strong> ${sum.toString()}<br>
<strong>Big.js 비교:</strong> ${sum.eq(target) ? '100과 동일함' : '100과 동일하지 않음'}`;
}
</script>
</body>
</html>
이 예제에서는 CDN을 통해 Big.js를 불러오고, 사용자가 입력한 값을 Big 객체로 변환하여 정확하게 합산합니다. Big.js의 eq() 메서드를 이용해 100과 동일한지를 확인합니다.
자바스크립트의 부동소수점 연산 특성 때문에 발생하는 미세 오차는 실제 계산에서 큰 문제를 일으킬 수 있습니다.
따라서,
위와 같은 방법들을 상황에 맞게 적용하면, 사용자로부터 입력받은 소수점 값들을 누적하여 100과 같은 결과를 비교할 때 발생하는 부동소수점 오차 문제를 효과적으로 해결할 수 있습니다.
이 글이 자바스크립트 부동소수점 오차 문제에 대한 이해와, 실무에서 이를 다루는 방법에 대해 도움이 되기를 바랍니다.
실무적용
var persentObj = $("#SelectDeptList_PopupSearchFinancialDept").find("input[name=Persent_PopupSearchFinancialDept]");
let persentSum = 0.0;
const epsilon = 0.0001; // 0.01% 오차 허용
for (let i = 0; i < persentObj.length; i++) {
const currentInput = $(persentObj).eq(i);
const inputVal = currentInput.val().trim();
if (!inputVal || isNaN(inputVal)) {
alert("비율을 확인하시기 바랍니다.");
currentInput.focus();
return false;
}
if (inputVal === "0") {
alert("비율이 0%인 부서가 존재 합니다. 해당 부서를 삭제해 주시기 바랍니다.");
currentInput.focus();
return false;
}
persentSum += parseFloat(inputVal) * 100;
}
// 부동소수점 오차 고려한 비교
if (Math.abs(persentSum - 10000) > epsilon) {
const displayedSum = (persentSum / 100).toFixed(2);
alert(`비율의 합은 100%여야 합니다.\n현재 입력된 총비율은 ${displayedSum}% 입니다.`);
return false;
}
return true;
Bootstrap v5.1.0 : AJAX 에서 modal 창 사용 방법 (0) | 2025.03.11 |
---|---|
JavaScript : Hoisting(호이스팅) 최상단으로 끌어올려지는 현상 알아보자 (0) | 2025.03.05 |
사용자 이미지 업로드(JavaScript) -> Google Cloud Vision API 로 이미지 분석 (1) | 2024.11.20 |
JS : 오늘, 내일 날씨 및 온도 구현하기 (openweathermap 활용) (1) | 2024.10.31 |
Javascript : Promise 객체 : 최신 개념과 실전 활용법 (0) | 2024.10.22 |