JavaScript에서 숫자를 가장 가까운 0.05 또는 다른 증분으로 반올림하는 방법
소액 동전이 없는 국가를 위해 통화 및 숫자를 0.05 또는 0.10과 같은 특정 증분으로 반올림하는 방법 알아보기
소개
캐나다의 고객이 쇼핑 카트에 총 $11.23 상당의 물품을 추가했지만, 최종 청구액은 $11.25로 표시됩니다. 이는 캐나다가 2013년에 1센트 동전을 폐지하여 현금 거래가 가장 가까운 0.05 단위로 반올림되기 때문입니다. 애플리케이션은 고객이 실제로 지불하는 금액과 일치하는 가격을 표시해야 합니다.
특정 단위로 반올림하면 이 문제를 해결할 수 있습니다. 숫자를 가장 가까운 5센트(0.05), 10센트(0.10) 또는 다른 단위로 반올림할 수 있습니다. 이는 통화 형식, 측정 시스템 및 값이 특정 단위와 일치해야 하는 모든 시나리오에 적용됩니다.
이 가이드에서는 JavaScript를 사용하여 수동 구현과 최신 Intl.NumberFormat API를 포함한 사용자 지정 단위로 숫자를 반올림하는 방법을 보여줍니다.
특정 단위로 반올림하는 이유
많은 국가들이 실용적인 이유로 소액 동전을 폐지했습니다. 이러한 동전을 생산하고 취급하는 비용이 액면가보다 더 많이 듭니다. 이러한 국가에서의 현금 거래는 가장 가까운 사용 가능한 동전 단위로 반올림됩니다.
현금 결제에 0.05 반올림을 사용하는 국가:
- 캐나다(2013년 1센트 동전 폐지)
- 호주(1센트 및 2센트 동전 폐지)
- 네덜란드(가장 가까운 0.05 유로로 반올림)
- 벨기에(가장 가까운 0.05 유로로 반올림)
- 아일랜드(가장 가까운 0.05 유로로 반올림)
- 이탈리아(가장 가까운 0.05 유로로 반올림)
- 핀란드(가장 가까운 0.05 유로로 반올림)
- 스위스(가장 작은 동전은 0.05 CHF)
0.10 반올림을 사용하는 국가:
- 뉴질랜드(2006년 5센트 동전 폐지)
미국은 2026년 초까지 1센트 동전 생산을 중단할 계획이며, 이로 인해 현금 거래에 0.05 반올림이 필요하게 됩니다.
현금 반올림은 개별 품목이 아닌 최종 거래 총액에만 적용됩니다. 전자 결제는 물리적 동전이 오가지 않기 때문에 정확한 금액으로 유지됩니다.
통화 외에도 다음과 같은 경우에 사용자 지정 단위가 필요할 수 있습니다:
- 특정 정밀도 요구 사항이 있는 측정 시스템
- 기기 정밀도에 맞게 반올림된 과학적 계산
- 특정 값에 맞춰지는 사용자 인터페이스 컨트롤
- 심리적 가격 포인트를 사용하는 가격 책정 전략
증분 반올림 뒤에 있는 수학적 개념
증분으로 반올림한다는 것은 해당 증분의 가장 가까운 배수를 찾는 것을 의미합니다. 이 공식은 숫자를 증분으로 나누고, 가장 가까운 정수로 반올림한 다음, 다시 증분을 곱합니다.
roundedValue = Math.round(value / increment) * increment
예를 들어, 11.23을 가장 가까운 0.05로 반올림하면:
- 나누기: 11.23 / 0.05 = 224.6
- 반올림: Math.round(224.6) = 225
- 곱하기: 225 * 0.05 = 11.25
나눗셈은 증분을 정수 단계로 변환합니다. 224.6을 225로 반올림하면 가장 가까운 단계를 찾습니다. 곱셈은 원래 스케일로 다시 변환합니다.
11.27을 가장 가까운 0.05로 반올림하면:
- 나누기: 11.27 / 0.05 = 225.4
- 반올림: Math.round(225.4) = 225
- 곱하기: 225 * 0.05 = 11.25
11.23과 11.27 모두 11.25로 반올림됩니다. 이는 두 값이 모두 11.25에서 0.025 이내에 있기 때문입니다. 중간점인 11.25는 11.25로 유지됩니다.
수동으로 증분 반올림 구현하기
지정된 증분으로 모든 숫자를 반올림하는 함수를 만들어 보겠습니다:
function roundToIncrement(value, increment) {
return Math.round(value / increment) * increment;
}
console.log(roundToIncrement(11.23, 0.05)); // 11.25
console.log(roundToIncrement(11.27, 0.05)); // 11.25
console.log(roundToIncrement(11.28, 0.05)); // 11.30
console.log(roundToIncrement(4.94, 0.10)); // 4.90
console.log(roundToIncrement(4.96, 0.10)); // 5.00
이 방법은 통화뿐만 아니라 모든 증분에 대해 작동합니다:
console.log(roundToIncrement(17, 5)); // 15
console.log(roundToIncrement(23, 5)); // 25
console.log(roundToIncrement(2.7, 0.5)); // 2.5
JavaScript 부동 소수점 연산은 작은 정밀도 오류를 발생시킬 수 있습니다. 정밀도가 중요한 통화 계산의 경우, 값에 10의 거듭제곱을 곱하여 정수로 작업하세요:
function roundToIncrementPrecise(value, increment) {
// 증분의 소수점 자릿수 찾기
const decimals = (increment.toString().split('.')[1] || '').length;
const multiplier = Math.pow(10, decimals);
// 정수로 변환
const valueInt = Math.round(value * multiplier);
const incrementInt = Math.round(increment * multiplier);
// 반올림하고 다시 변환
return Math.round(valueInt / incrementInt) * incrementInt / multiplier;
}
console.log(roundToIncrementPrecise(11.23, 0.05)); // 11.25
Math.round()는 두 증분 사이의 정확히 중간에 있는 값을 올림하는 반올림 방식을 사용합니다. 일부 애플리케이션에서는 다른 반올림 모드가 필요합니다:
function roundToIncrementWithMode(value, increment, mode = 'halfUp') {
const divided = value / increment;
let rounded;
switch (mode) {
case 'up':
rounded = Math.ceil(divided);
break;
case 'down':
rounded = Math.floor(divided);
break;
case 'halfDown':
rounded = divided % 1 === 0.5 ? Math.floor(divided) : Math.round(divided);
break;
case 'halfUp':
default:
rounded = Math.round(divided);
break;
}
return rounded * increment;
}
console.log(roundToIncrementWithMode(11.225, 0.05, 'halfUp')); // 11.25
console.log(roundToIncrementWithMode(11.225, 0.05, 'halfDown')); // 11.20
console.log(roundToIncrementWithMode(11.23, 0.05, 'up')); // 11.25
console.log(roundToIncrementWithMode(11.23, 0.05, 'down')); // 11.20
roundingIncrement와 함께 Intl.NumberFormat 사용하기
최신 브라우저는 Intl.NumberFormat에서 roundingIncrement 옵션을 지원하며, 이는 숫자 포맷팅 시 자동으로 증분 반올림을 처리합니다.
캐나다 달러 5센트 반올림의 기본 사용법:
const formatter = new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 5
});
console.log(formatter.format(11.23)); // CA$11.25
console.log(formatter.format(11.27)); // CA$11.25
console.log(formatter.format(11.28)); // CA$11.30
roundingIncrement 옵션은 특정 값만 허용합니다:
- 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000
0.05 반올림의 경우, maximumFractionDigits: 2와 roundingIncrement: 5로 설정하세요. 0.10 반올림의 경우, maximumFractionDigits: 1과 roundingIncrement: 1로 설정하거나, maximumFractionDigits: 2와 roundingIncrement: 10으로 유지하세요.
스위스 프랑 0.05 반올림:
const chfFormatter = new Intl.NumberFormat('de-CH', {
style: 'currency',
currency: 'CHF',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 5
});
console.log(chfFormatter.format(8.47)); // CHF 8.45
console.log(chfFormatter.format(8.48)); // CHF 8.50
뉴질랜드 달러 0.10 반올림:
const nzdFormatter = new Intl.NumberFormat('en-NZ', {
style: 'currency',
currency: 'NZD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 10
});
console.log(nzdFormatter.format(15.94)); // $15.90
console.log(nzdFormatter.format(15.96)); // $16.00
roundingIncrement를 다양한 반올림 모드와 함께 사용할 수 있습니다:
const formatterUp = new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 5,
roundingMode: 'ceil'
});
console.log(formatterUp.format(11.21)); // CA$11.25 (올림)
사용 가능한 반올림 모드:
ceil: 양의 무한대 방향으로 반올림floor: 음의 무한대 방향으로 반올림expand: 0에서 멀어지는 방향으로 반올림trunc: 0 방향으로 반올림halfCeil: 중간값을 양의 무한대 방향으로 반올림halfFloor: 중간값을 음의 무한대 방향으로 반올림halfExpand: 중간값을 0에서 멀어지는 방향으로 반올림(기본값)halfTrunc: 중간값을 0 방향으로 반올림halfEven: 중간값을 짝수로 반올림(은행가 반올림)
roundingIncrement에 대한 중요한 제약사항:
minimumFractionDigits와maximumFractionDigits는 동일한 값을 가져야 함- 유효 숫자 반올림과 함께 사용할 수 없음
roundingPriority가auto(기본값)일 때만 작동함
브라우저가 roundingIncrement를 지원하는지 확인:
function supportsRoundingIncrement() {
try {
const formatter = new Intl.NumberFormat('en-US', {
roundingIncrement: 5,
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const options = formatter.resolvedOptions();
return options.roundingIncrement === 5;
} catch (e) {
return false;
}
}
if (supportsRoundingIncrement()) {
// roundingIncrement와 함께 Intl.NumberFormat 사용
} else {
// 수동 반올림으로 대체
}
실용적인 구현 예시
로케일에 따른 현금 반올림을 처리하는 통화 포맷터 생성:
function createCashFormatter(locale, currency) {
// 통화별 현금 반올림 규칙 정의
const cashRoundingRules = {
'CAD': { increment: 5, digits: 2 },
'AUD': { increment: 5, digits: 2 },
'EUR': { increment: 5, digits: 2 }, // 일부 유로존 국가
'CHF': { increment: 5, digits: 2 },
'NZD': { increment: 10, digits: 2 }
};
const rule = cashRoundingRules[currency];
if (!rule) {
// 특별한 반올림이 필요 없음
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
});
}
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: rule.digits,
maximumFractionDigits: rule.digits,
roundingIncrement: rule.increment
});
}
const cadFormatter = createCashFormatter('en-CA', 'CAD');
console.log(cadFormatter.format(47.83)); // CA$47.85
const usdFormatter = createCashFormatter('en-US', 'USD');
console.log(usdFormatter.format(47.83)); // $47.83 (반올림 없음)
현금 및 카드 결제 모두 처리:
function formatPaymentAmount(amount, currency, locale, paymentMethod) {
if (paymentMethod === 'cash') {
const formatter = createCashFormatter(locale, currency);
return formatter.format(amount);
} else {
// 전자 결제는 정확한 금액 사용
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
}
}
console.log(formatPaymentAmount(11.23, 'CAD', 'en-CA', 'cash')); // CA$11.25
console.log(formatPaymentAmount(11.23, 'CAD', 'en-CA', 'card')); // CA$11.23
실제 청구할 금액 계산:
function calculateCashTotal(items, currency) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
// 최종 합계에만 현금 반올림 적용
const cashRoundingRules = {
'CAD': 0.05,
'AUD': 0.05,
'CHF': 0.05,
'NZD': 0.10
};
const increment = cashRoundingRules[currency];
if (!increment) {
return subtotal;
}
return roundToIncrement(subtotal, increment);
}
function roundToIncrement(value, increment) {
return Math.round(value / increment) * increment;
}
const items = [
{ price: 5.99 },
{ price: 3.49 },
{ price: 1.75 }
];
console.log(calculateCashTotal(items, 'CAD')); // 11.25
console.log(calculateCashTotal(items, 'USD')); // 11.23
비통화 값에 대한 증분 반올림 사용:
// 가장 가까운 0.5도로 반올림된 온도 표시
function formatTemperature(celsius) {
const rounded = roundToIncrement(celsius, 0.5);
return new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'celsius',
minimumFractionDigits: 1,
maximumFractionDigits: 1
}).format(rounded);
}
console.log(formatTemperature(22.3)); // 22.5°C
console.log(formatTemperature(22.6)); // 22.5°C
// 증분에 맞춰진 슬라이더 값
function snapToGrid(value, gridSize) {
return roundToIncrement(value, gridSize);
}
console.log(snapToGrid(47, 5)); // 45
console.log(snapToGrid(53, 5)); // 55
중요한 고려사항 및 엣지 케이스
현금 반올림은 개별 항목이 아닌 최종 거래 총액에만 적용하세요. 각 항목을 개별적으로 반올림하면 합계를 반올림하는 것과 다른 결과가 나옵니다:
const items = [1.23, 1.23, 1.23];
// 잘못된 방법: 각 항목 반올림
const wrongTotal = items
.map(price => roundToIncrement(price, 0.05))
.reduce((sum, price) => sum + price, 0);
console.log(wrongTotal); // 3.75
// 올바른 방법: 총액 반올림
const correctTotal = roundToIncrement(
items.reduce((sum, price) => sum + price, 0),
0.05
);
console.log(correctTotal); // 3.70
사용자에게는 반올림된 금액을 표시하되, 데이터베이스에는 정확한 금액을 저장하세요. 이렇게 하면 회계 정확성을 유지하고 정보 손실 없이 반올림 규칙을 변경할 수 있습니다:
const transaction = {
items: [
{ id: 1, price: 5.99 },
{ id: 2, price: 3.49 },
{ id: 3, price: 1.75 }
],
subtotal: 11.23, // 정확한 값 저장
currency: 'CAD',
paymentMethod: 'cash'
};
// 표시 금액 계산
const displayAmount = roundToIncrement(transaction.subtotal, 0.05); // 11.25
동일한 통화를 사용하는 다른 국가들은 서로 다른 현금 반올림 규칙을 가질 수 있습니다. 유로화는 현금 반올림을 사용하는 국가와 사용하지 않는 국가 모두에서 사용됩니다. 통화만이 아니라 특정 국가의 규정을 확인하세요.
음수 금액은 절대값에 대해 0을 향해 반올림한 다음 음수 부호를 적용합니다:
console.log(roundToIncrement(-11.23, 0.05)); // -11.25
console.log(roundToIncrement(-11.22, 0.05)); // -11.20
매우 큰 숫자는 부동 소수점 연산으로 정밀도가 떨어질 수 있습니다. 대규모 금액을 처리하는 금융 애플리케이션의 경우 가장 작은 통화 단위로 정수 연산을 사용하는 것이 좋습니다:
function roundToIncrementCents(cents, incrementCents) {
return Math.round(cents / incrementCents) * incrementCents;
}
// 센트 단위로 작업
const amountCents = 1123; // $11.23
const roundedCents = roundToIncrementCents(amountCents, 5); // 1125
const dollars = roundedCents / 100; // 11.25
엣지 케이스로 반올림 구현을 테스트하세요:
// 0.05 반올림에 대한 테스트 케이스
const testCases = [
{ input: 0.00, expected: 0.00 },
{ input: 0.01, expected: 0.00 },
{ input: 0.02, expected: 0.00 },
{ input: 0.025, expected: 0.05 },
{ input: 0.03, expected: 0.05 },
{ input: 0.99, expected: 1.00 },
{ input: -0.03, expected: -0.05 },
{ input: 11.225, expected: 11.25 }
];
testCases.forEach(({ input, expected }) => {
const result = roundToIncrement(input, 0.05);
console.log(`${input} -> ${result} (expected ${expected})`);
});
Intl.NumberFormat의 roundingIncrement 옵션은 최신 브라우저에서 지원됩니다. Firefox는 버전 93에서 지원을 추가했습니다. 이전 브라우저를 지원하는 애플리케이션의 경우 대체 방안을 구현하세요:
function formatCurrency(amount, locale, currency, increment) {
try {
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: increment
});
// roundingIncrement가 실제로 적용되었는지 확인
if (formatter.resolvedOptions().roundingIncrement === increment) {
return formatter.format(amount);
}
} catch (e) {
// roundingIncrement가 지원되지 않음
}
// 대체 방안: 수동 반올림
const rounded = roundToIncrement(amount, increment / 100);
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(rounded);
}
사용자 인터페이스에서 반올림 동작을 명확하게 문서화하세요. 사용자는 표시된 금액이 항목 합계와 다른 이유를 이해해야 합니다:
<div class="cart-summary">
<div class="line-item">소계: $11.23</div>
<div class="line-item total">총액 (현금): $11.25</div>
<div class="note">현금 결제는 가장 가까운 $0.05로 반올림됩니다</div>
</div>
다양한 관할 지역에는 반올림의 혜택(고객 또는 판매자)과 엣지 케이스 처리 방법에 대한 다양한 규정이 있습니다. 특정 사용 사례에 대해서는 현지 규정을 참조하세요.