Zahlen auf die nächsten 0,05 oder andere Schritte in JavaScript runden

Erfahren Sie, wie Sie Währungen und Zahlen auf bestimmte Schritte wie 0,05 oder 0,10 für Länder ohne kleine Münzen runden

Einführung

Ein Kunde in Kanada legt Artikel im Wert von 11,23 $ in den Warenkorb, aber die endgültige Rechnung zeigt 11,25 $. Dies geschieht, weil Kanada den Penny im Jahr 2013 abgeschafft hat, sodass Bargeldtransaktionen auf die nächsten 0,05 $ gerundet werden. Ihre Anwendung muss Preise anzeigen, die dem entsprechen, was Kunden tatsächlich zahlen.

Das Runden auf bestimmte Schritte löst dieses Problem. Sie können Zahlen auf den nächsten Nickel (0,05), Dime (0,10) oder jeden anderen Schritt runden. Dies gilt für Währungsformatierung, Messsysteme und jedes Szenario, in dem Werte mit bestimmten Schritten übereinstimmen müssen.

Dieser Leitfaden zeigt Ihnen, wie Sie Zahlen mit JavaScript auf benutzerdefinierte Schritte runden, einschließlich sowohl manueller Implementierung als auch der modernen Intl.NumberFormat-API.

Warum auf bestimmte Schritte runden

Viele Länder haben kleine Münzstückelungen aus praktischen Gründen abgeschafft. Die Herstellung und Handhabung dieser Münzen kostet mehr als ihr Nennwert. Bargeldtransaktionen in diesen Ländern werden auf die nächste verfügbare Münze gerundet.

Länder, die 0,05-Rundung für Barzahlungen verwenden:

  • Kanada (Penny im Jahr 2013 abgeschafft)
  • Australien (1- und 2-Cent-Münzen abgeschafft)
  • Niederlande (rundet auf die nächsten 0,05 Euro)
  • Belgien (rundet auf die nächsten 0,05 Euro)
  • Irland (rundet auf die nächsten 0,05 Euro)
  • Italien (rundet auf die nächsten 0,05 Euro)
  • Finnland (rundet auf die nächsten 0,05 Euro)
  • Schweiz (kleinste Münze ist 0,05 CHF)

Länder, die 0,10-Rundung verwenden:

  • Neuseeland (5-Cent-Münze im Jahr 2006 abgeschafft)

Die Vereinigten Staaten planen, die Produktion von Pennies bis Anfang 2026 einzustellen, was eine 0,05-Rundung für Bargeldtransaktionen erforderlich machen wird.

Die Bargeldrundung gilt nur für die endgültige Transaktionssumme, nicht für einzelne Artikel. Elektronische Zahlungen bleiben exakt, da keine physischen Münzen den Besitzer wechseln.

Über Währungen hinaus benötigen Sie möglicherweise benutzerdefinierte Inkremente für:

  • Messsysteme mit spezifischen Präzisionsanforderungen
  • Wissenschaftliche Berechnungen, die auf Instrumentenpräzision gerundet werden
  • Benutzeroberflächensteuerelemente, die auf bestimmte Werte einrasten
  • Preisstrategien, die psychologische Preispunkte verwenden

Das mathematische Konzept hinter der Inkrementrundung

Die Rundung auf ein Inkrement bedeutet, das nächste Vielfache dieses Inkrements zu finden. Die Formel dividiert die Zahl durch das Inkrement, rundet auf die nächste Ganzzahl und multipliziert dann wieder mit dem Inkrement.

roundedValue = Math.round(value / increment) * increment

Beispielsweise die Rundung von 11,23 auf das nächste 0,05:

  1. Dividieren: 11,23 / 0,05 = 224,6
  2. Runden: Math.round(224,6) = 225
  3. Multiplizieren: 225 * 0,05 = 11,25

Die Division wandelt das Inkrement in ganzzahlige Schritte um. Die Rundung von 224,6 auf 225 findet den nächsten Schritt. Die Multiplikation wandelt zurück in die ursprüngliche Skala.

Für 11,27 gerundet auf das nächste 0,05:

  1. Dividieren: 11,27 / 0,05 = 225,4
  2. Runden: Math.round(225,4) = 225
  3. Multiplizieren: 225 * 0,05 = 11,25

Sowohl 11,23 als auch 11,27 runden auf 11,25, da sie innerhalb von 0,025 dieses Wertes liegen. Der Mittelpunkt 11,25 bleibt bei 11,25.

Manuelle Implementierung der Inkrementrundung

Erstellen Sie eine Funktion, die jede Zahl auf ein bestimmtes Inkrement rundet:

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

Dies funktioniert für jedes Inkrement, nicht nur für Währungen:

console.log(roundToIncrement(17, 5)); // 15
console.log(roundToIncrement(23, 5)); // 25
console.log(roundToIncrement(2.7, 0.5)); // 2.5

Die JavaScript-Gleitkommaarithmetik kann kleine Präzisionsfehler einführen. Für Währungsberechnungen, bei denen Präzision wichtig ist, multiplizieren Sie Werte mit einer Zehnerpotenz, um mit Ganzzahlen zu arbeiten:

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() verwendet kaufmännisches Runden (half-up), bei dem Werte genau in der Mitte zwischen zwei Schritten aufgerundet werden. Einige Anwendungen benötigen andere Rundungsmodi:

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

Verwendung von Intl.NumberFormat mit roundingIncrement

Moderne Browser unterstützen die Option roundingIncrement in Intl.NumberFormat, die beim Formatieren von Zahlen automatisch Schrittweisen-Rundung durchführt.

Grundlegende Verwendung für kanadische Dollar-Nickel-Rundung:

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

Die Option roundingIncrement akzeptiert nur bestimmte Werte:

  • 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000

Für 0,05-Rundung setzen Sie maximumFractionDigits: 2 und roundingIncrement: 5. Für 0,10-Rundung setzen Sie maximumFractionDigits: 1 und roundingIncrement: 1 oder behalten maximumFractionDigits: 2 mit roundingIncrement: 10.

Schweizer Franken-Rundung auf 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

Neuseeland-Dollar-Rundung auf 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

Sie können roundingIncrement mit verschiedenen Rundungsmodi kombinieren:

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)

Verfügbare Rundungsmodi:

  • ceil: Runden in Richtung positiver Unendlichkeit
  • floor: Runden in Richtung negativer Unendlichkeit
  • expand: Runden weg von Null
  • trunc: Runden in Richtung Null
  • halfCeil: Halbe Werte in Richtung positiver Unendlichkeit runden
  • halfFloor: Halbe Werte in Richtung negativer Unendlichkeit runden
  • halfExpand: Halbe Werte weg von Null runden (Standard)
  • halfTrunc: Halbe Werte in Richtung Null runden
  • halfEven: Halbe Werte auf gerade Zahlen runden (kaufmännisches Runden)

Wichtige Einschränkungen für roundingIncrement:

  • Sowohl minimumFractionDigits als auch maximumFractionDigits müssen denselben Wert haben
  • Kann nicht mit Rundung auf signifikante Stellen kombiniert werden
  • Funktioniert nur, wenn roundingPriority auf auto gesetzt ist (Standard)

Prüfen Sie, ob der Browser roundingIncrement unterstützt:

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
}

Praktische Implementierungsbeispiele

Erstellen Sie einen Währungsformatierer, der Bargeld-Rundung basierend auf dem Gebietsschema durchführt:

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)

Verarbeiten Sie sowohl Bar- als auch Kartenzahlungen:

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

Berechnen Sie den tatsächlich zu belastenden Betrag:

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

Verwenden Sie Inkrement-Rundung für Nicht-Währungswerte:

// 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

Wichtige Überlegungen und Sonderfälle

Wenden Sie die Bargeldrundung nur auf den endgültigen Transaktionsbetrag an, nicht auf einzelne Positionen. Die separate Rundung jeder Position führt zu einem anderen Ergebnis als die Rundung der Summe:

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

Zeigen Sie gerundete Beträge den Benutzern an, speichern Sie jedoch exakte Beträge in Ihrer Datenbank. Dies bewahrt die Präzision für die Buchhaltung und ermöglicht es Ihnen, Rundungsregeln zu ändern, ohne Informationen zu verlieren:

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

Verschiedene Länder, die dieselbe Währung verwenden, können unterschiedliche Bargeldrundungsregeln haben. Der Euro wird in Ländern mit und ohne Bargeldrundung verwendet. Prüfen Sie die spezifischen Vorschriften des jeweiligen Landes, nicht nur die Währung.

Negative Beträge runden zum Absolutwert hin zur Null, dann wird das negative Vorzeichen angewendet:

console.log(roundToIncrement(-11.23, 0.05)); // -11.25
console.log(roundToIncrement(-11.22, 0.05)); // -11.20

Sehr große Zahlen können bei Gleitkommaarithmetik an Präzision verlieren. Für Finanzanwendungen, die große Beträge verarbeiten, sollten Sie die Verwendung von Ganzzahlarithmetik mit der kleinsten Währungseinheit in Betracht ziehen:

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

Testen Sie Ihre Rundungsimplementierung mit Sonderfällen:

// 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})`);
});

Die Option roundingIncrement in Intl.NumberFormat wird in modernen Browsern unterstützt. Firefox hat die Unterstützung in Version 93 hinzugefügt. Für Anwendungen, die ältere Browser unterstützen, implementieren Sie einen 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);
}

Dokumentieren Sie Ihr Rundungsverhalten klar in benutzerseitigen Schnittstellen. Benutzer müssen verstehen, warum angezeigte Beträge von Positionssummen abweichen:

<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>

Verschiedene Rechtsordnungen haben unterschiedliche Vorschriften darüber, wer von der Rundung profitiert (Kunde oder Händler) und wie Sonderfälle zu behandeln sind. Konsultieren Sie die lokalen Vorschriften für Ihren spezifischen Anwendungsfall.