How to round numbers to the nearest 0.05 or other increment in JavaScript
Learn how to round currency and numbers to specific increments like 0.05 or 0.10 for countries without small coins
Introduction
A customer in Canada adds items to a shopping cart totaling $11.23, but the final charge shows $11.25. This happens because Canada eliminated the penny in 2013, so cash transactions round to the nearest 0.05. Your application needs to display prices that match what customers actually pay.
Rounding to specific increments solves this problem. You can round numbers to the nearest nickel (0.05), dime (0.10), or any other increment. This applies to currency formatting, measurement systems, and any scenario where values need to align with specific increments.
This guide shows you how to round numbers to custom increments using JavaScript, including both manual implementation and the modern Intl.NumberFormat API.
Why round to specific increments
Many countries eliminated small denomination coins for practical reasons. Producing and handling these coins costs more than their face value. Cash transactions in these countries round to the nearest available coin.
Countries using 0.05 rounding for cash payments:
- Canada (eliminated penny in 2013)
- Australia (eliminated 1 and 2 cent coins)
- Netherlands (rounds to nearest 0.05 euros)
- Belgium (rounds to nearest 0.05 euros)
- Ireland (rounds to nearest 0.05 euros)
- Italy (rounds to nearest 0.05 euros)
- Finland (rounds to nearest 0.05 euros)
- Switzerland (smallest coin is 0.05 CHF)
Countries using 0.10 rounding:
- New Zealand (eliminated 5 cent coin in 2006)
The United States plans to stop producing pennies by early 2026, which will require 0.05 rounding for cash transactions.
Cash rounding only applies to the final transaction total, not to individual items. Electronic payments remain exact because no physical coins change hands.
Beyond currency, you might need custom increments for:
- Measurement systems with specific precision requirements
- Scientific calculations rounded to instrument precision
- User interface controls that snap to specific values
- Pricing strategies that use psychological price points
The mathematical concept behind increment rounding
Rounding to an increment means finding the nearest multiple of that increment. The formula divides the number by the increment, rounds to the nearest integer, then multiplies back by the increment.
roundedValue = Math.round(value / increment) * increment
For example, rounding 11.23 to the nearest 0.05:
- Divide: 11.23 / 0.05 = 224.6
- Round: Math.round(224.6) = 225
- Multiply: 225 * 0.05 = 11.25
The division converts the increment into whole number steps. Rounding 224.6 to 225 finds the nearest step. Multiplying converts back to the original scale.
For 11.27 rounded to the nearest 0.05:
- Divide: 11.27 / 0.05 = 225.4
- Round: Math.round(225.4) = 225
- Multiply: 225 * 0.05 = 11.25
Both 11.23 and 11.27 round to 11.25 because they fall within 0.025 of that value. The midpoint 11.25 stays at 11.25.
Implementing increment rounding manually
Create a function that rounds any number to a specified increment:
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
This works for any increment, not just currency:
console.log(roundToIncrement(17, 5)); // 15
console.log(roundToIncrement(23, 5)); // 25
console.log(roundToIncrement(2.7, 0.5)); // 2.5
JavaScript floating point arithmetic can introduce small precision errors. For currency calculations where precision matters, multiply values by a power of 10 to work with integers:
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() uses half-up rounding, where values exactly halfway between two increments round up. Some applications need different rounding modes:
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
Using Intl.NumberFormat with roundingIncrement
Modern browsers support the roundingIncrement option in Intl.NumberFormat, which handles increment rounding automatically when formatting numbers.
Basic usage for Canadian dollar nickel rounding:
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
The roundingIncrement option accepts specific values only:
- 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000
For 0.05 rounding, set maximumFractionDigits: 2 and roundingIncrement: 5. For 0.10 rounding, set maximumFractionDigits: 1 and roundingIncrement: 1, or keep maximumFractionDigits: 2 with roundingIncrement: 10.
Swiss franc rounding to 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
New Zealand dollar rounding to 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
You can combine roundingIncrement with different rounding modes:
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)
Available rounding modes:
ceil: round toward positive infinityfloor: round toward negative infinityexpand: round away from zerotrunc: round toward zerohalfCeil: round half toward positive infinityhalfFloor: round half toward negative infinityhalfExpand: round half away from zero (default)halfTrunc: round half toward zerohalfEven: round half to even (banker's rounding)
Important constraints for roundingIncrement:
- Both
minimumFractionDigitsandmaximumFractionDigitsmust have the same value - Cannot combine with significant digits rounding
- Only works when
roundingPriorityisauto(the default)
Check if the browser supports 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
}
Practical implementation examples
Create a currency formatter that handles cash rounding based on locale:
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)
Handle both cash and card payments:
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
Calculate the actual amount to charge:
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
Use increment rounding for non-currency values:
// 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
Important considerations and edge cases
Apply cash rounding only to the final transaction total, not to individual line items. Rounding each item separately produces a different result than rounding the sum:
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
Display rounded amounts to users, but store exact amounts in your database. This preserves precision for accounting and allows you to change rounding rules without losing information:
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
Different countries using the same currency can have different cash rounding rules. The euro is used in countries with and without cash rounding. Check the specific country's regulations, not just the currency.
Negative amounts round toward zero for the absolute value, then apply the negative sign:
console.log(roundToIncrement(-11.23, 0.05)); // -11.25
console.log(roundToIncrement(-11.22, 0.05)); // -11.20
Very large numbers can lose precision with floating point arithmetic. For financial applications handling large amounts, consider using integer arithmetic with the smallest currency unit:
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 your rounding implementation with edge cases:
// 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})`);
});
The roundingIncrement option in Intl.NumberFormat is supported in modern browsers. Firefox added support in version 93. For applications supporting older browsers, implement a fallback:
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);
}
Document your rounding behavior clearly in user-facing interfaces. Users need to understand why displayed amounts differ from item totals:
<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>
Different jurisdictions have different regulations about who benefits from rounding (customer or merchant) and how to handle edge cases. Consult local regulations for your specific use case.