Ein Kunde in Tokio kauft ein Abo für 19,99 $. Ihr Code multipliziert mit dem USD/JPY-Kurs, erhält 2942,82785, schreibt das in die Datenbank und schickt es an Ihren Payment-Provider. Der Provider lehnt es ab — oder schlimmer, er akzeptiert es und bucht das Hundertfache ab. Der Yen hat keine Nachkommastellen, und Ihr Code hat nie danach gefragt.
Währungsrundung gehört zu den Problemen, die trivial wirken, bis sie in Produktion landen. Es ist keine Formatierungsfrage, sondern eine Korrektheitsfrage. Jede Währung hat ihre eigene Anzahl Nachkommastellen, Fließkomma-Arithmetik beschädigt Geldbeträge stillschweigend, und in dem Moment, in dem Sie zwischen Währungen umrechnen, treffen Sie eine Rundungsentscheidung, die bewusst getroffen werden muss. Dieser Leitfaden behandelt die Regeln, die wirklich zählen: wie viele Nachkommastellen jede Währung hat, warum Sie Beträge als Ganzzahlen speichern sollten, welchen Rundungsmodus Sie wählen und wie Sie eine FX-Umrechnung so runden, dass Ihr Hauptbuch am Monatsende noch aufgeht.
Untereinheiten: Wie viele Nachkommastellen hat jede Währung?
Die Untereinheit einer Währung ist ihre kleinste handelbare Unterteilung. Beim US-Dollar ist das der Cent, also hat USD zwei Nachkommastellen und 19,99 $ sind 1999 Cent. Das ist die Darstellung, die Payment-Provider erwarten, und die, die Ihre Datenbank verwenden sollte.
ISO 4217 — derselbe Standard, der Ihnen dreibuchstabige Codes wie USD und JPY liefert — weist jeder Währung außerdem einen Untereinheiten-Exponenten zu. Die meisten Entwickler nehmen an, dieser Exponent sei immer 2. Ist er nicht, und diese Annahme ist der teuerste Bug dieser Kategorie.
Währungen ohne Nachkommastellen
Diese Währungen haben keine umlaufende Untereinheit, der gesendete Betrag ist also die ganze Anzahl Einheiten:
- JPY — japanischer Yen
- KRW — südkoreanischer Won
- VND — vietnamesischer Dong
- CLP — chilenischer Peso
- ISK — isländische Krone
- XAF / XOF / XPF — CFA- und CFP-Franc
- UGX — ugandischer Schilling
- PYG — paraguayischer Guaraní
- RWF, GNF, KMF, DJF, VUV — und mehrere weitere kleinstückelige Währungen
Wenn Sie JPY als zweistellige Währung behandeln und vor dem Senden an den Provider mit 100 multiplizieren, haben Sie Ihrem Kunden gerade das 100-Fache berechnet.
Währungen mit drei Nachkommastellen
Sieben Währungen unterteilen sich in Tausendstel statt in Hundertstel:
- KWD — Kuwait-Dinar (1000 Fils)
- BHD — Bahrain-Dinar (1000 Fils)
- OMR — Omanischer Rial (1000 Baisa)
- JOD — Jordanischer Dinar (1000 Fils)
- TND — Tunesischer Dinar (1000 Millimes)
- IQD — Irakischer Dinar (1000 Fils)
- LYD — Libyscher Dinar (1000 Dirham)
Hier läuft der Fehler andersherum: Behandeln Sie KWD als zweistellig, berechnen Sie ein Zehntel des Gewollten. Aus einer Rechnung über KWD 12,500 wird KWD 1,250.
Es gibt in ISO 4217 sogar Einträge mit vier Nachkommastellen — die chilenische Unidad de Fomento (CLF) und die uruguayische Unidad Previsional (UYW). Das sind indexierte Recheneinheiten und kein Bargeld, aber wenn Ihr System beliebige ISO-Codes akzeptiert, muss es sie überstehen.
Wenn Standard und Provider auseinandergehen
Das ist die Falle, in die Teams tappen, die sonst alles richtig gemacht haben. Payment-Provider weichen aus betrieblichen Gründen manchmal von ISO 4217 ab. Adyen dokumentiert zum Beispiel, dass CLP, CVE, IDR und ISK in seiner API eine andere Nachkommastellenzahl haben als im Standard — ISK hat nach ISO 4217 null Nachkommastellen, muss aber mit zwei an Adyen übermittelt werden.
Die Regel: Ihre Rundungstabelle ist eine Eigenschaft des Systems, mit dem Sie sprechen, keine universelle Konstante. Führen Sie eine Tabelle pro Integration, befüllen Sie sie aus ISO 4217 und überschreiben Sie sie pro Provider dort, wo dessen Doku es verlangt. Codieren Sie niemals 100 fest.
Speichern Sie Geld niemals als Float
Vor jeder Rundungsdiskussion das Fundament. Binäre Fließkommazahlen können die meisten Dezimalbrüche nicht exakt darstellen:
0.1 + 0.2 // 0.30000000000000004
1.005 * 100 // 100.49999999999999
19.99 * 147.2150 // 2942.8278499999997Diese Nachkommaziffern sind nicht kosmetisch. Schicken Sie sie zum falschen Zeitpunkt durch Math.round(), erhalten Sie einen Betrag, der um eine Untereinheit danebenliegt — genug, um den Abgleich scheitern zu lassen.
Zwei Regeln decken fast jeden Fall ab:
- Speichern Sie Beträge als Ganzzahlen in Untereinheiten. Eine Spalte
amount_minor BIGINTplus eine Spaltecurrency CHAR(3).{ amount_minor: 1999, currency: "USD" }ist eindeutig und entspricht dem, was Stripe, Adyen und die meisten Provider ohnehin erwarten. - Rechnen Sie mit Ganzzahlen oder einem Dezimaltyp. Pythons
decimal.Decimal, JavasBigDecimal, PostgreSQLsNUMERICoder eine JavaScript-Geldbibliothek, die Ganzzahlarithmetik kapselt. Floats bleiben dem Wechselkurs selbst vorbehalten — und auch nur bis zur Multiplikation.
Wenn Sie diese Schicht von Grund auf entwerfen, geht unser Leitfaden zum Multi-Currency-Ledger-Design tiefer auf die Schema-Entscheidungen ein.
Die Umrechnungs-Pipeline: Untereinheiten rein, Untereinheiten raus
Währungsumrechnung hat genau vier Schritte, und die Rundung gehört in Schritt drei — einmal, ganz am Ende.
- Rechnen Sie den Ausgangsbetrag von Untereinheiten in einen Dezimalwert um.
- Multiplizieren Sie mit dem Wechselkurs in voller Präzision.
- Runden Sie auf den Untereinheiten-Exponenten der Zielwährung.
- Rechnen Sie zurück in ganzzahlige Untereinheiten.
So sieht das in JavaScript aus, wobei die Währungstabelle die Arbeit macht:
// Minor unit exponents. Seed from ISO 4217, override per payment provider.
const MINOR_UNITS = {
USD: 2, EUR: 2, GBP: 2, CHF: 2, CAD: 2, AUD: 2, CNY: 2, INR: 2,
JPY: 0, KRW: 0, VND: 0, CLP: 0, ISK: 0, XAF: 0, XOF: 0, XPF: 0,
KWD: 3, BHD: 3, OMR: 3, JOD: 3, TND: 3, IQD: 3, LYD: 3,
};
function exponentFor(currency) {
const e = MINOR_UNITS[currency];
if (e === undefined) throw new Error(`Unknown minor unit for ${currency}`);
return e;
}
/**
* Convert an integer minor-unit amount from one currency to another.
* Returns an integer in the target currency's minor units.
*/
function convertMinor(amountMinor, from, to, rate) {
const fromExp = exponentFor(from);
const toExp = exponentFor(to);
const decimalAmount = amountMinor / 10 ** fromExp; // 1999 -> 19.99
const converted = decimalAmount * rate; // full precision, no rounding yet
return Math.round(converted * 10 ** toExp); // single rounding step
}
convertMinor(1999, "USD", "JPY", 147.2150); // 2943 (¥2,943)
convertMinor(1999, "USD", "KWD", 0.30590); // 6115 (KWD 6.115)
convertMinor(1999, "USD", "EUR", 0.9241); // 1847 (€18.47)Beachten Sie, was die Funktion nicht tut: Sie rundet nie den Kurs, nie einen Zwischenwert und nimmt nie zwei Nachkommastellen an. Math.round ist hier kaufmännisches Aufrunden bei positiven Zahlen — für einen Checkout in Ordnung, aber lesen Sie den nächsten Abschnitt, bevor Sie es für etwas Reguliertes verwenden.
Den Rundungsmodus wählen
„Auf zwei Nachkommastellen runden" ist keine Spezifikation. Es gibt mindestens fünf vertretbare Arten, einen Gleichstand aufzulösen, und Finanzsystemen ist nicht egal, welche Sie wählen.
| Modus | 2,5 → | 3,5 → | −2,5 → | Typische Verwendung |
|---|---|---|---|---|
| Half up (kaufmännisch) | 3 | 4 | −3 | Endkundenpreise, Checkout-Summen |
| Half even (Banker's Rounding) | 2 | 4 | −2 | Buchhaltung, Zinsen, Steuern, Reporting |
| Half down | 2 | 3 | −2 | Selten; gelegentlich in Legacy-Finanzcode |
| Ceiling (aufrunden) | 3 | 4 | −2 | Gebühren, die nie zu niedrig sein dürfen |
| Floor (abrunden / abschneiden) | 2 | 3 | −3 | Auszahlungen, die nie zu hoch sein dürfen |
Math.round() bei positiven Zahlen liefert. Es ist intuitiv und passend für einen Preis, den ein Kunde gleich sieht.Half even, auch Banker's Rounding genannt, schickt exakte Hälften zur nächsten geraden Ziffer. Über viele Transaktionen hinweg gleicht es die systematische Aufwärtsverzerrung von Half-up aus — deshalb ist es der Standard in Buchhaltungssystemen, in Pythons decimal-Modul und in IEEE 754 selbst. Wenn Sie Tausende umgerechneter Beträge zu einem Umsatzbericht aggregieren, bläht Half-up die Summe still auf; Half-even nicht.
Ceiling und Floor existieren für asymmetrische Risiken. Ein Marktplatz, der an Verkäufer auszahlt, kann jede Auszahlung abrunden, um nie mehr zu verteilen als er hält; die Differenz landet auf einem Rundungskonto.
Python macht die Wahl explizit, was die richtige Ergonomie ist:
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP
MINOR_UNITS = {"USD": 2, "EUR": 2, "JPY": 0, "KWD": 3}
def convert_minor(amount_minor: int, src: str, dst: str,
rate: str, mode=ROUND_HALF_EVEN) -> int:
"""Convert integer minor units to integer minor units, exactly once."""
src_exp, dst_exp = MINOR_UNITS[src], MINOR_UNITS[dst]
amount = Decimal(amount_minor) / (Decimal(10) ** src_exp)
converted = amount * Decimal(rate) # rate passed as a string, not a float
quantum = Decimal(1).scaleb(-dst_exp) # 0.01, 1, or 0.001
rounded = converted.quantize(quantum, rounding=mode)
return int(rounded.scaleb(dst_exp))
convert_minor(1999, "USD", "JPY", "147.2150") # 2943
convert_minor(1999, "USD", "KWD", "0.30590") # 6115Den Kurs als String an Decimal zu übergeben, ist wichtig. Decimal(0.9241) erbt den Float-Fehler; Decimal("0.9241") nicht.
Drei Rundungsfehler, die echtes Geld kosten
1. Den Kurs vor der Multiplikation runden
Wechselkurse tragen routinemäßig vier bis sechs signifikante Nachkommastellen, und sie abzuschneiden ist nicht harmlos. Nehmen Sie USD/JPY bei 147,2150 und eine Überweisung über 10.000 $:
- Voller Kurs:
10000 × 147.2150 = ¥1.472.150 - Auf zwei Stellen gerundeter Kurs (147,21):
10000 × 147.21 = ¥1.472.100
Eine Differenz von 50 ¥ in einer einzigen Transaktion, allein weil der Kurs vor der Verwendung formatiert wurde. Speichern Sie den Kurs in der Präzision, die Ihr Anbieter liefert, runden Sie nur den resultierenden Betrag, und persistieren Sie den exakt verwendeten Kurs zur Transaktion für die Prüfung. Unser Leitfaden dazu, woher Wechselkurs-APIs ihre Daten beziehen, erklärt, warum diese Präzision überhaupt Bedeutung hat.
2. Zweimal runden bei mehrstufiger Umrechnung
Wenn Sie USD → EUR → JPY routen und beim EUR-Schritt runden, werfen Sie Präzision weg, die die zweite Multiplikation dann verstärkt. Umrechnung von 12,34 $ mit USD/EUR bei 0,9241 und EUR/JPY bei 159,3063:
- Direkt:
12.34 × 147.2150 = 1816.63→ ¥1.817 - Über eine gerundete EUR-Stufe:
12.34 × 0.9241 = 11.4034→ gerundet auf 11,40 € →11.40 × 159.3063 = 1816.09→ ¥1.816
Ein Yen, wegen eines unnötigen Rundungsschritts. Bei einem Auszahlungslauf über fünfzigtausend Transaktionen ist das ein Abstimmungsticket. Wo ein direktes Paar verfügbar ist, nutzen Sie es; wenn Sie triangulieren müssen, halten Sie den Zwischenwert in voller Präzision. Die Mechanik finden Sie unter Cross-Rates erklärt.
3. Positionen, die nicht zur Summe passen
Runden Sie jede Rechnungsposition unabhängig, ergeben die Teile nicht immer die gerundete Gesamtsumme. Der Klassiker ist eine Aufteilung:
$10.00 split three ways
10.00 / 3 = 3.3333...
→ 3.33 + 3.33 + 3.33 = 9.99 ✗ one cent missingDie Lösung heißt Allokation, nicht Rundung. Runden Sie die Summe einmal und verteilen Sie sie dann auf die Teile, wobei der Rest untereinheitenweise vergeben wird:
/**
* Split an integer minor-unit total into `n` parts whose sum is exactly the total.
* Remainder units are distributed to the earliest parts (largest-remainder method).
*/
function allocate(totalMinor, ratios) {
const sum = ratios.reduce((a, b) => a + b, 0);
const shares = ratios.map(r => Math.floor((totalMinor * r) / sum));
let remainder = totalMinor - shares.reduce((a, b) => a + b, 0);
for (let i = 0; remainder > 0; i = (i + 1) % shares.length, remainder--) {
shares[i] += 1;
}
return shares;
}
allocate(1000, [1, 1, 1]); // [334, 333, 333] → sums to exactly 1000
allocate(9247, [3, 2, 1]); // [4624, 3082, 1541] → sums to exactly 9247Wenden Sie dasselbe Muster nach einer FX-Umrechnung an: Rechnen Sie die Rechnungssumme um und runden Sie sie, und verteilen Sie diese Summe dann auf die Positionen. Die Positionen stimmen dann immer, weil sie aus der Summe abgeleitet und nicht unabhängig berechnet wurden. Am wichtigsten ist das bei der Multi-Currency-Rechnungsstellung und beim SaaS-Billing mit anteiliger Abrechnung, wo eine Ein-Cent-Abweichung in einem kundenseitigen PDF auftaucht.
Bargeldrundung ist eine eigene Regel
Die Untereinheit einer Währung nennt Ihnen den kleinsten Betrag, der verbucht werden kann. Sie nennt nicht immer den kleinsten Betrag, der bar bezahlt werden kann. Mehrere Länder haben ihre kleinsten Münzen eingezogen und runden Barzahlungen an der Kasse:
- Schweiz — Bargeld wird auf 0,05 CHF gerundet
- Kanada — die Ein-Cent-Münze wurde 2013 eingezogen; Bargeld wird auf 5 Cent gerundet
- Schweden — Bargeld wird auf die nächste ganze Krone gerundet
- Niederlande — Bargeld wird auf 5 Cent gerundet
Entscheidend: Das gilt für die Barzahlung, nicht für die Rechnung. Eine Schweizer Rechnung über CHF 12,32 wird weiterhin als 12,32 verbucht; nur die Barzahlung rundet auf 12,30, und die Differenz von 0,02 wird als Rundungsdifferenz gebucht. Wenn Sie Kassensoftware bauen, modellieren Sie Bargeldrundung als separaten, späteren Schritt auf der Zahlung — backen Sie sie nie in den gespeicherten Betrag ein, sonst weichen Ihre elektronischen und Bartransaktionen voneinander ab.
Formatierung ist der letzte Schritt, nicht die Berechnung
Ist die Arithmetik erledigt, übergeben Sie die Darstellung an einen locale-bewussten Formatter. Intl.NumberFormat kennt bereits Nachkommastellen, Symbolposition und Trennzeichen jeder Währung:
function formatMinor(amountMinor, currency, locale = "en-US") {
const exp = exponentFor(currency);
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(amountMinor / 10 ** exp);
}
formatMinor(1999, "USD"); // "$19.99"
formatMinor(2943, "JPY", "ja-JP"); // "¥2,943"
formatMinor(6115, "KWD"); // "KWD 6.115"
formatMinor(1847, "EUR", "de-DE"); // "18,47 €"Zwei praktische Hinweise. Erstens sind Intl.NumberFormat-Instanzen teuer in der Erzeugung — cachen Sie eine pro Locale-Währungs-Paar, statt eine pro Zeile zu bauen. Zweitens ist die Division durch 10 ** exp in der letzten Zeile die einzige Stelle, an der ein Float einen Geldwert berühren sollte, und auch nur, weil das Ergebnis sofort zu einem String wird.
Alles zusammen mit der Finexly-API
Holen Sie den Kurs in voller Präzision, rechnen Sie einmal um, runden Sie einmal, und speichern Sie den verwendeten Kurs:
curl "https://api.finexly.com/v1/latest?base=USD&symbols=JPY,KWD,EUR" \
-H "Authorization: Bearer YOUR_API_KEY"{
"success": true,
"base": "USD",
"timestamp": 1755244800,
"rates": {
"JPY": 147.2150,
"KWD": 0.30590,
"EUR": 0.9241
}
}async function quote(amountMinor, from, to) {
const res = await fetch(
`https://api.finexly.com/v1/latest?base=${from}&symbols=${to}`,
{ headers: { Authorization: `Bearer ${process.env.FINEXLY_API_KEY}` } }
);
const data = await res.json();
const rate = data.rates[to];
return {
amount_minor: convertMinor(amountMinor, from, to, rate),
currency: to,
rate, // persist the exact rate used
rate_timestamp: data.timestamp, // and when it was captured
};
}
await quote(1999, "USD", "JPY");
// { amount_minor: 2943, currency: "JPY", rate: 147.215, rate_timestamp: 1755244800 }rate und rate_timestamp in der Transaktionszeile zu speichern, macht einen Streitfall sechs Monate später beantwortbar. Alle Endpunkt- und Parameterdetails stehen in der Finexly-API-Dokumentation, und wenn Sie Kurse zwischen Requests cachen, behandeln unsere Notizen zu Caching und Fehlerbehandlung die Abwägungen zur Aktualität.
Eine Test-Checkliste
Geldfehler verstecken sich in den Fällen, für die niemand Tests schreibt. Decken Sie mindestens ab:
- Ein Ziel ohne Nachkommastellen — rechnen Sie nach JPY oder KRW um und prüfen Sie, dass das Ergebnis keinen Bruchteil hat.
- Ein Ziel mit drei Nachkommastellen — rechnen Sie nach KWD oder BHD um und prüfen Sie, dass drei Stellen erhalten bleiben.
- Exakte Hälften — prüfen Sie Ihren gewählten Rundungsmodus in beide Richtungen, inklusive negativer Werte.
- Hin-und-zurück-Drift — rechnen Sie USD → EUR → USD und prüfen Sie, dass das Ergebnis innerhalb einer Untereinheit liegt, nicht dass es gleich ist.
- Allokations-Invarianz — prüfen Sie, dass aufgeteilte Teile immer exakt die Summe ergeben, für 1 bis 100 Teile.
- Unbekannte Währungscodes — prüfen Sie, dass der Code eine Ausnahme wirft, statt still auf zwei Nachkommastellen zurückzufallen.
- Sehr große Beträge — prüfen Sie, dass jenseits von
Number.MAX_SAFE_INTEGERin JavaScript keine Präzision verloren geht; nutzen SieBigInt, wenn Sie IDR oder VND in großem Umfang verarbeiten.
Häufige Fragen
Wie viele Nachkommastellen hat jede Währung?
Die meisten haben zwei. Rund zwei Dutzend haben keine — darunter JPY, KRW, VND, CLP und ISK — und sieben haben drei: KWD, BHD, OMR, JOD, TND, IQD und LYD. ISO 4217 ist die maßgebliche Quelle, aber prüfen Sie auch die Tabelle Ihres Payment-Providers, da einige aus betrieblichen Gründen abweichen.
Soll ich Wechselkurse oder umgerechnete Beträge runden?
Nur umgerechnete Beträge. Halten Sie den Kurs in der vollen Präzision, die Ihr Anbieter liefert, multiplizieren Sie, und runden Sie das Ergebnis dann genau einmal auf die Untereinheit der Zielwährung. Einen Kurs vor der Multiplikation zu runden, erzeugt einen Fehler proportional zur Transaktionsgröße.
Was ist der Unterschied zwischen Half-up und Banker's Rounding?
Half-up schickt eine exakte Hälfte immer von der Null weg (2,5 → 3). Banker's Rounding — Hälfte zur geraden Ziffer — schickt sie zur nächsten geraden Stelle (2,5 → 2, 3,5 → 4), was die systematische Aufwärtsverzerrung beim Aggregieren vieler Beträge beseitigt. Nutzen Sie Half-up für Kundenpreise, Half-even für Buchhaltung und Reporting.
Warum ergeben meine umgerechneten Positionen nicht die umgerechnete Summe?
Weil jede Position unabhängig gerundet wurde und sich die Fehler summieren. Runden Sie die Summe einmal und verteilen Sie sie dann per Größte-Reste-Verfahren auf die Positionen. Die Teile ergeben dann per Konstruktion das Ganze.
Kann ich Geld nicht einfach als Float mit zwei Nachkommastellen speichern?
Nein. Binäre Fließkommazahlen können Werte wie 0,1 nicht exakt darstellen, also summieren sich Fehler über Additionen und Multiplikationen und kippen irgendwann eine Rundungsentscheidung. Speichern Sie Ganzzahlen in Untereinheiten oder nutzen Sie einen exakten Dezimaltyp. Das ist keine theoretische Sorge — es ist die häufigste Ursache für Ein-Cent-Abstimmungsfehler.
Kurse in voller Präzision holen
Korrektes Runden beginnt mit einem Kurs, dem Sie trauen können, und mit Präzision, die Sie nicht weggeworfen haben. Holen Sie sich Ihren kostenlosen Finexly-API-Key — ohne Kreditkarte. Starten Sie mit 1.000 kostenlosen Requests pro Monat über 170+ Währungen, prüfen Sie Ihre Rechnung mit dem Währungsrechner, und sehen Sie sich die Preispläne an, wenn Ihr Volumen wächst.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →