Comment arrondir des nombres au 0,05 le plus proche ou à un autre incrément en JavaScript
Apprenez à arrondir les devises et les nombres à des incréments spécifiques comme 0,05 ou 0,10 pour les pays sans petites pièces
Introduction
Un client au Canada ajoute des articles à son panier pour un total de 11,23 $, mais le montant final affiché est de 11,25 $. Cela se produit parce que le Canada a éliminé le penny en 2013, donc les transactions en espèces sont arrondies au 0,05 le plus proche. Votre application doit afficher des prix qui correspondent à ce que les clients paient réellement.
L'arrondissement à des incréments spécifiques résout ce problème. Vous pouvez arrondir des nombres au nickel le plus proche (0,05), au dime le plus proche (0,10) ou à tout autre incrément. Cela s'applique au formatage des devises, aux systèmes de mesure et à tout scénario où les valeurs doivent s'aligner sur des incréments spécifiques.
Ce guide vous montre comment arrondir des nombres à des incréments personnalisés en utilisant JavaScript, incluant à la fois l'implémentation manuelle et l'API moderne Intl.NumberFormat.
Pourquoi arrondir à des incréments spécifiques
De nombreux pays ont éliminé les pièces de petite dénomination pour des raisons pratiques. La production et la manipulation de ces pièces coûtent plus que leur valeur nominale. Les transactions en espèces dans ces pays sont arrondies à la pièce disponible la plus proche.
Pays utilisant l'arrondissement à 0,05 pour les paiements en espèces :
- Canada (penny éliminé en 2013)
- Australie (pièces de 1 et 2 cents éliminées)
- Pays-Bas (arrondi au 0,05 euro le plus proche)
- Belgique (arrondi au 0,05 euro le plus proche)
- Irlande (arrondi au 0,05 euro le plus proche)
- Italie (arrondi au 0,05 euro le plus proche)
- Finlande (arrondi au 0,05 euro le plus proche)
- Suisse (la plus petite pièce est de 0,05 CHF)
Pays utilisant l'arrondissement à 0,10 :
- Nouvelle-Zélande (pièce de 5 cents éliminée en 2006)
Les États-Unis prévoient d'arrêter la production de pièces d'un cent d'ici début 2026, ce qui nécessitera un arrondi à 0,05 pour les transactions en espèces.
L'arrondi des espèces s'applique uniquement au total final de la transaction, pas aux articles individuels. Les paiements électroniques restent exacts car aucune pièce physique ne change de mains.
Au-delà de la monnaie, vous pourriez avoir besoin d'incréments personnalisés pour :
- Les systèmes de mesure avec des exigences de précision spécifiques
- Les calculs scientifiques arrondis à la précision de l'instrument
- Les contrôles d'interface utilisateur qui s'alignent sur des valeurs spécifiques
- Les stratégies de tarification qui utilisent des points de prix psychologiques
Le concept mathématique derrière l'arrondi par incrément
Arrondir à un incrément signifie trouver le multiple le plus proche de cet incrément. La formule divise le nombre par l'incrément, arrondit à l'entier le plus proche, puis multiplie à nouveau par l'incrément.
roundedValue = Math.round(value / increment) * increment
Par exemple, arrondir 11,23 au 0,05 le plus proche :
- Diviser : 11,23 / 0,05 = 224,6
- Arrondir : Math.round(224,6) = 225
- Multiplier : 225 * 0,05 = 11,25
La division convertit l'incrément en étapes de nombres entiers. Arrondir 224,6 à 225 trouve l'étape la plus proche. La multiplication reconvertit à l'échelle d'origine.
Pour 11,27 arrondi au 0,05 le plus proche :
- Diviser : 11,27 / 0,05 = 225,4
- Arrondir : Math.round(225,4) = 225
- Multiplier : 225 * 0,05 = 11,25
11,23 et 11,27 s'arrondissent tous deux à 11,25 car ils se situent à moins de 0,025 de cette valeur. Le point médian 11,25 reste à 11,25.
Implémenter l'arrondi par incrément manuellement
Créez une fonction qui arrondit n'importe quel nombre à un incrément spécifié :
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
Cela fonctionne pour n'importe quel incrément, pas seulement pour la monnaie :
console.log(roundToIncrement(17, 5)); // 15
console.log(roundToIncrement(23, 5)); // 25
console.log(roundToIncrement(2.7, 0.5)); // 2.5
L'arithmétique en virgule flottante de JavaScript peut introduire de petites erreurs de précision. Pour les calculs monétaires où la précision compte, multipliez les valeurs par une puissance de 10 pour travailler avec des entiers :
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() utilise l'arrondi au demi supérieur, où les valeurs exactement à mi-chemin entre deux incréments sont arrondies vers le haut. Certaines applications nécessitent différents modes d'arrondi :
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
Utilisation d'Intl.NumberFormat avec roundingIncrement
Les navigateurs modernes prennent en charge l'option roundingIncrement dans Intl.NumberFormat, qui gère automatiquement l'arrondi par incrément lors du formatage des nombres.
Utilisation de base pour l'arrondi au nickel du dollar canadien :
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
L'option roundingIncrement accepte uniquement des valeurs spécifiques :
- 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000
Pour un arrondi à 0,05, définissez maximumFractionDigits: 2 et roundingIncrement: 5. Pour un arrondi à 0,10, définissez maximumFractionDigits: 1 et roundingIncrement: 1, ou conservez maximumFractionDigits: 2 avec roundingIncrement: 10.
Arrondi du franc suisse à 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
Arrondi du dollar néo-zélandais à 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
Vous pouvez combiner roundingIncrement avec différents modes d'arrondi :
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)
Modes d'arrondi disponibles :
ceil: arrondir vers l'infini positiffloor: arrondir vers l'infini négatifexpand: arrondir en s'éloignant de zérotrunc: arrondir vers zérohalfCeil: arrondir le demi vers l'infini positifhalfFloor: arrondir le demi vers l'infini négatifhalfExpand: arrondir le demi en s'éloignant de zéro (par défaut)halfTrunc: arrondir le demi vers zérohalfEven: arrondir le demi au pair (arrondi bancaire)
Contraintes importantes pour roundingIncrement :
minimumFractionDigitsetmaximumFractionDigitsdoivent avoir la même valeur- Ne peut pas être combiné avec l'arrondi aux chiffres significatifs
- Fonctionne uniquement lorsque
roundingPriorityestauto(par défaut)
Vérifier si le navigateur prend en charge 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
}
Exemples d'implémentation pratiques
Créer un formateur de devise qui gère l'arrondi des espèces en fonction de la 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)
Gérer les paiements en espèces et par carte :
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
Calculer le montant réel à facturer :
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
Utiliser l'arrondi par incrément pour les valeurs non monétaires :
// 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
Considérations importantes et cas limites
Appliquer l'arrondi en espèces uniquement au total final de la transaction, pas aux articles individuels. Arrondir chaque article séparément produit un résultat différent de l'arrondi de la somme :
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
Afficher les montants arrondis aux utilisateurs, mais stocker les montants exacts dans votre base de données. Cela préserve la précision pour la comptabilité et vous permet de modifier les règles d'arrondi sans perdre d'informations :
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
Différents pays utilisant la même devise peuvent avoir des règles d'arrondi en espèces différentes. L'euro est utilisé dans des pays avec et sans arrondi en espèces. Vérifiez les réglementations spécifiques du pays, pas seulement la devise.
Les montants négatifs s'arrondissent vers zéro pour la valeur absolue, puis appliquent le signe négatif :
console.log(roundToIncrement(-11.23, 0.05)); // -11.25
console.log(roundToIncrement(-11.22, 0.05)); // -11.20
Les très grands nombres peuvent perdre en précision avec l'arithmétique en virgule flottante. Pour les applications financières traitant de gros montants, envisagez d'utiliser l'arithmétique entière avec la plus petite unité monétaire :
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
Tester votre implémentation d'arrondi avec des cas limites :
// 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})`);
});
L'option roundingIncrement dans Intl.NumberFormat est prise en charge dans les navigateurs modernes. Firefox a ajouté la prise en charge dans la version 93. Pour les applications prenant en charge les navigateurs plus anciens, implémentez une solution de secours :
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);
}
Documenter clairement votre comportement d'arrondi dans les interfaces utilisateur. Les utilisateurs doivent comprendre pourquoi les montants affichés diffèrent des totaux des articles :
<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>
Différentes juridictions ont des réglementations différentes concernant qui bénéficie de l'arrondi (client ou commerçant) et comment gérer les cas limites. Consultez les réglementations locales pour votre cas d'usage spécifique.