std.money — Geldbeträge

Beträge als skalierte Ganzzahlen: sieben Rundungsarten, Mehrwertsteuer vor- und rückwärts, Aufteilen ohne verlorene Cents, Formatieren nach Landesart.

Mathematik-Units · std.sat · std.rat · KassenSichV

import std.money;

Schicht 1, hängt an std.sat und std.alloc. 27 Funktionen, elf Konstanten. Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.


Geld ist keine Fließkommazahl

<WRAP alert> 0,10 + 0,20 ist in f64 nicht 0,30 (→ std.feq). Für eine Rechnung, die stimmen muss, ist das disqualifizierend.

std.money rechnet deshalb in Ganzzahlen — der Betrag ist ein int64 in der kleinsten Einheit, die Skala sagt, wo das Komma steht. 1050 bei Skala 2 sind 10,50. Alle Grundrechenarten sind damit exakt; gerundet wird nur dort, wo es unvermeidlich ist, und dann ausdrücklich. </WRAP>

MONEY_INVALID ist der kleinste int64-Wert und markiert ein ungültiges Ergebnis; MoneyIsValid(v) fragt danach. Die Grundrechenarten setzen auf std.sat auf und melden einen Überlauf, statt ihn umschlagen zu lassen.


Runden: sieben Arten, und warum das nötig ist

unit main;
import std.io;
import std.string;
import std.alloc;
import std.money;

fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }

fn main(): int64 {
    ZI("Add(1050, 299)              : ", MoneyAdd(1050, 299));
    ZI("DivRound(100, 3, HALF_UP)   : ", MoneyDivRound(100, 3, MONEY_HALF_UP));
    ZI("DivRound(250, 100, HALF_UP) : ", MoneyDivRound(250, 100, MONEY_HALF_UP));
    ZI("DivRound(250, 100, HALF_EVEN): ", MoneyDivRound(250, 100, MONEY_HALF_EVEN));
    ZI("DivRound(350, 100, HALF_EVEN): ", MoneyDivRound(350, 100, MONEY_HALF_EVEN));
    ZI("DivRound(-250, 100, FLOOR)  : ", MoneyDivRound(-250, 100, MONEY_FLOOR));
    return 0;
}

Add(1050, 299)              : 1349
DivRound(100, 3, HALF_UP)   : 33
DivRound(250, 100, HALF_UP) : 3
DivRound(250, 100, HALF_EVEN): 2
DivRound(350, 100, HALF_EVEN): 4
DivRound(-250, 100, FLOOR)  : -3

Bei 2,5 gehen die Verfahren auseinander, bei 3,5 nicht. HALF_EVEN rundet zur geraden Nachbarzahl — über viele Beträge hebt sich der Rundungsfehler damit auf, statt sich zu summieren. Das ist der Grund, warum es im Bankwesen und in der Statistik die Vorgabe ist.

Konstante Verhalten bei x,5
MONEY_HALF_UP von der Null weg (Schulregel)
MONEY_HALF_EVEN zur geraden Zahl (kaufmännisch, bankers' rounding)
MONEY_HALF_DOWN zur Null hin
MONEY_UP immer von der Null weg
MONEY_DOWN immer zur Null hin (abschneiden)
MONEY_FLOOR immer nach unten
MONEY_CEIL immer nach oben

MONEY_DOWN und MONEY_FLOOR unterscheiden sich nur bei negativen Beträgen: −2,5 wird zu −2 beziehungsweise −3.

MoneyRoundTo(v, scale, places, mode) rundet auf eine Stellenzahl, MoneyRoundToMultiple(v, mult, mode) auf ein Vielfaches — für die Fünf-Rappen-Rundung in der Schweiz oder Preisenden auf 0,99.


Mehrwertsteuer in beide Richtungen

var brutto: int64 := MoneyAddTax(10000, 1900, 4, MONEY_HALF_UP);
var steuer: int64 := MoneyTaxOfGross(11900, 1900, 4, MONEY_HALF_UP);
var netto:  int64 := MoneyNetOfGross(11900, 1900, 4, MONEY_HALF_UP);

AddTax(10000, 19%)    : 11900
TaxOfGross(11900, 19%): 1900
NetOfGross(11900, 19%): 10000

Der Satz wird als skalierte Ganzzahl übergeben: 1900 bei rateScale 4 sind 19 %. Damit lassen sich auch krumme Sätze wie 8,25 % exakt darstellen (825 bei Skala 4).

 
Rückwärts ist nicht dasselbe wie vorwärts. Aus dem Bruttobetrag den Steueranteil zu ziehen ist eine andere Rechnung als den Nettobetrag zu erhöhen — deshalb gibt es dafür eigene Funktionen. Wer brutto − brutto/1,19 selbst rechnet, bekommt bei manchen Beträgen einen Cent Abweichung gegenüber dem, was das Finanzamt erwartet.

MoneyMulRate und MoneyPercent sind die allgemeinen Formen für Kurse und Prozentsätze.


Aufteilen ohne Centverlust

var out: int64 := alloc(3 * 8);
MoneySplit(1000, 3, out);

Split(1000, 3) : 334 333 333   Summe=1000

Die Summe der Teile ist wieder der Ausgangsbetrag. 10,00 € durch drei geteilt ergibt nicht dreimal 3,33 (das wären 9,99), sondern 3,34 + 3,33 + 3,33. Der Rest wird auf die vorderen Teile verteilt, statt zu verschwinden.

MoneyAllocate(total, weights, n, out) macht dasselbe gewichtet — für Rabatte, Umlagen und Skonti, die anteilig auf Positionen verteilt werden müssen, ohne dass am Ende ein Cent fehlt.


Formatieren und Einlesen

var buf: int64 := alloc(64);
MoneyFormat(123456, 2, buf, 64, MONEY_FMT_DE);

Format(123456, DE) : 1.234,56
Format(123456, CH) : 1'234.56

Konstante Ergebnis
MONEY_FMT_PLAIN 1234.56
MONEY_FMT_DE 1.234,56
MONEY_FMT_EN 1,234.56
MONEY_FMT_CH 1'234.56

MoneyParse(s, scale, mode) liest zurück und liefert MONEY_INVALID, wenn die Zeichenkette nicht passt.


Währungen

MoneyCurrencyScale("EUR"c)    // 2
MoneyCurrencyScale("JPY"c)    // 0
MoneyCurrencyScale("KWD"c)    // 3

CurrencyScale(EUR) : 2
CurrencyScale(JPY) : 0
CurrencyScale(KWD) : 3

<WRAP alert> Der Parameter ist die Adresse einer Zeichenkette, kein Zahlencode. MoneyCurrencyScale(„EUR“c) — wer eine beliebige Zahl übergibt, liest an dieser Adresse und bekommt einen Speicherzugriffsfehler. Die Funktion fängt nur den Fall 0 ab. </WRAP>

Yen und Won haben keine Nachkommastellen, die Golfwährungen (KWD, BHD, OMR) drei. Genau diese Ausreißer sind der Grund, warum die Skala überhaupt ein Parameter ist — wer fest mit Cents rechnet, liegt bei jedem Yen-Betrag um den Faktor 100 daneben. Unbekannte Codes bekommen 2.

Erfasst sind derzeit nur die Ausreißer; eine vollständige ISO-4217-Tabelle gibt es nicht.


Funktionsübersicht

Gruppe Funktionen
Grundrechenarten MoneyAdd · MoneySub · MoneyNeg · MoneyAbs · MoneyMulInt · MoneyDivInt · MoneySum
Vergleichen MoneyCmp · MoneySign · MoneyMin · MoneyMax · MoneyIsValid
Runden MoneyDivRound · MoneyRoundTo · MoneyRoundToMultiple · MoneyPow10
Skala wechseln MoneyRescale
Sätze und Steuern MoneyMulRate · MoneyPercent · MoneyAddTax · MoneyTaxOfGross · MoneyNetOfGross
Verteilen MoneySplit · MoneyAllocate
Text MoneyParse · MoneyFormat · MoneyCurrencyScale

Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; Rundungsarten, Steuerrechnung in beide Richtungen und die Summenerhaltung beim Aufteilen einzeln nachgemessen.