JavaScriptで数値を0.05やその他の単位に丸める方法
小額硬貨のない国向けに、通貨や数値を0.05や0.10などの特定の単位に丸める方法を学ぶ
はじめに
カナダの顧客がショッピングカートに商品を追加し、合計が$11.23になりましたが、最終的な請求額は$11.25と表示されます。これは、カナダが2013年にペニー硬貨を廃止したため、現金取引が最も近い0.05に丸められるためです。アプリケーションは、顧客が実際に支払う金額と一致する価格を表示する必要があります。
特定の単位への丸め処理がこの問題を解決します。数値を最も近いニッケル(0.05)、ダイム(0.10)、またはその他の単位に丸めることができます。これは、通貨フォーマット、測定システム、および値を特定の単位に合わせる必要があるあらゆるシナリオに適用されます。
このガイドでは、手動実装と最新のIntl.NumberFormat APIの両方を使用して、JavaScriptでカスタム単位に数値を丸める方法を説明します。
特定の単位に丸める理由
多くの国が実用的な理由から小額硬貨を廃止しました。これらの硬貨の製造と取り扱いには、額面以上のコストがかかります。これらの国での現金取引は、利用可能な最も近い硬貨に丸められます。
現金支払いで0.05への丸めを使用している国:
- カナダ(2013年にペニー硬貨を廃止)
- オーストラリア(1セントと2セント硬貨を廃止)
- オランダ(最も近い0.05ユーロに丸める)
- ベルギー(最も近い0.05ユーロに丸める)
- アイルランド(最も近い0.05ユーロに丸める)
- イタリア(最も近い0.05ユーロに丸める)
- フィンランド(最も近い0.05ユーロに丸める)
- スイス(最小硬貨は0.05 CHF)
0.10への丸めを使用している国:
- ニュージーランド(2006年に5セント硬貨を廃止)
米国は2026年初頭までにペニー硬貨の製造を停止する予定であり、現金取引には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に丸められます。これは、どちらもその値の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) {
// Find the number of decimal places in the increment
const decimals = (increment.toString().split('.')[1] || '').length;
const multiplier = Math.pow(10, decimals);
// Convert to integers
const valueInt = Math.round(value * multiplier);
const incrementInt = Math.round(increment * multiplier);
// Round and convert back
return Math.round(valueInt / incrementInt) * incrementInt / multiplier;
}
console.log(roundToIncrementPrecise(11.23, 0.05)); // 11.25
Math.round()は四捨五入を使用し、2つの増分のちょうど中間の値は切り上げられます。一部のアプリケーションでは異なる丸めモードが必要です。
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オプションをサポートしており、数値のフォーマット時に増分丸めを自動的に処理します。
カナダドルのニッケル丸めの基本的な使用方法:
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 (rounded up)
利用可能な丸めモード:
ceil: 正の無限大に向かって丸めるfloor: 負の無限大に向かって丸めるexpand: ゼロから離れる方向に丸めるtrunc: ゼロに向かって丸めるhalfCeil: 半分を正の無限大に向かって丸めるhalfFloor: 半分を負の無限大に向かって丸めるhalfExpand: 半分をゼロから離れる方向に丸める(デフォルト)halfTrunc: 半分をゼロに向かって丸める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()) {
// Use Intl.NumberFormat with roundingIncrement
} else {
// Fall back to manual rounding
}
実用的な実装例
ロケールに基づいて現金丸めを処理する通貨フォーマッタを作成する:
function createCashFormatter(locale, currency) {
// Define cash rounding rules by currency
const cashRoundingRules = {
'CAD': { increment: 5, digits: 2 },
'AUD': { increment: 5, digits: 2 },
'EUR': { increment: 5, digits: 2 }, // Some eurozone countries
'CHF': { increment: 5, digits: 2 },
'NZD': { increment: 10, digits: 2 }
};
const rule = cashRoundingRules[currency];
if (!rule) {
// No special rounding needed
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 (no rounding)
現金とカード決済の両方に対応します。
function formatPaymentAmount(amount, currency, locale, paymentMethod) {
if (paymentMethod === 'cash') {
const formatter = createCashFormatter(locale, currency);
return formatter.format(amount);
} else {
// Electronic payments use exact amounts
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);
// Apply cash rounding only to the final total
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
通貨以外の値には増分丸めを使用します。
// Temperature display rounded to nearest 0.5 degrees
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
// Slider values snapped to increments
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];
// Wrong: rounding each item
const wrongTotal = items
.map(price => roundToIncrement(price, 0.05))
.reduce((sum, price) => sum + price, 0);
console.log(wrongTotal); // 3.75
// Correct: rounding the total
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, // Store exact value
currency: 'CAD',
paymentMethod: 'cash'
};
// Calculate display amount
const displayAmount = roundToIncrement(transaction.subtotal, 0.05); // 11.25
同じ通貨を使用していても、国によって現金の丸めルールが異なる場合があります。ユーロは現金の丸め処理がある国とない国の両方で使用されています。通貨だけでなく、特定の国の規制を確認してください。
負の金額は絶対値をゼロに向かって丸めてから、負の符号を適用します。
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;
}
// Work in cents
const amountCents = 1123; // $11.23
const roundedCents = roundToIncrementCents(amountCents, 5); // 1125
const dollars = roundedCents / 100; // 11.25
エッジケースで丸め処理の実装をテストします。
// Test cases for 0.05 rounding
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
});
// Check if roundingIncrement was actually applied
if (formatter.resolvedOptions().roundingIncrement === increment) {
return formatter.format(amount);
}
} catch (e) {
// roundingIncrement not supported
}
// Fallback: manual rounding
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">Subtotal: $11.23</div>
<div class="line-item total">Total (cash): $11.25</div>
<div class="note">Cash payments rounded to nearest $0.05</div>
</div>
管轄区域によって、丸め処理の利益を誰が得るか(顧客または販売者)、エッジケースの処理方法に関する規制が異なります。特定のユースケースについては、現地の規制を確認してください。