Das Konzept der Rundung kennt man grundsätzlich. Aber versuchen wir es etwas systematischer zu erfassen.
Man hat eine Menge von Zahlen und eine Teilmenge
davon, deren Elemente in der verwendeten Programmierumgebung dargestellt werden. Dazu hat man noch eine Metrik
. Man verlangt normalerweise noch gewisse Eigenschaften von
:
- Positive Definitheit:
- Symmetrie:
- Dreiecksungleichung:
Typischerweise sind die Zahlen, mit denen wir uns meistens beschäftigen, in der Welt der reellen und komplexen Zahlen gedacht, man kann also fast immer sicher sein, dass ist, meistens sogar
oder wenn wir ehrlich sind sogar
. Dann ist normalerweise
. Wer die komplexen Zahlen noch nicht kennt, denke einfach an reelle und rationale Zahlen, das ist das, womit wir normalerweise bewusst zu tun haben. Dabei sind natürlich Konzepte wie Rundung speziell in
-adischen Zahlen hochinteressant, aber dafür muss ich die erstmal erklären und das lasse ich heute…
Was stellen wir uns nun unter einer Rundung vor?
Vielleicht eine Abbildung
,
die mit gewissen Nebenbedingungen für jedes x jeweils so gewählt wird, dass minimal ist.
Die Nebenbedingungen brauchen wir einerseits, um es eindeutig zu machen, wenn mehrere Werte existieren, für die
minimal ist. Der klassische Fall ist das Runden auf ganzzahlige Werte und die Frage mit der
. Wenn
eine Teilmenge der reellen Zahlen ist, was ja „meistens“ der Fall ist, hat man eine Anordung. Da kommen dann die folgenden Constraints zum Tragen (Beispiel immer mit Rundung auf ganze Zahlen):
- ROUND_UP
Z.B.
und
und
- ROUND_DOWN
Z.B.
und
und
- ROUND_CEILING
Z.B.
und
und
- ROUND_FLOOR
Z.B.
und
und
- ROUND_HALF_UP
- Minimiere
, aber wenn es mehrere optimale Werte für
gibt, wähle den am weitesten von 0 entfernten, z.B.
und
und
und
- ROUND_HALF_DOWN
- Minimiere
, aber wenn es mehrere optimale Werte für
gibt, wähle den am nächsten an 0, z.B.
und
und
und
- ROUND_HALF_CEILING
- Minimiere
, aber wenn es mehrere optimale Werte für
gibt, wähle den größten, z.B.
und
und
und
- ROUND_HALF_FLOOR
- Minimiere
, aber wenn es mehrere optimale Werte für
gibt, wähle den kleinesten, z.B.
und
und
und
- ROUND_HALF_EVEN
- Minimiere
, aber wenn es mehrere optimale Werte für
gibt, wähle den mit gerader Endziffer. Achtung, dieser Constraint ist im „klassischen“ Fall anwendbar, aber nicht allgemeingültig. Z.B.:
und
und
und
und
- ROUND_UNNECESSARY
- Dieser Constraint ist im mathematischen Sinne nicht geeignet (oder nur mit hässlichen Verrenkungen), aber programmatisch können wir das: Wir nehmen
und schmeißen eine Exception wenn nicht
schon gilt.
Typischerweise denken wir im Dezimalsystem und dann wählen wir eine Zehnerpotenz mit
, also
. Nun ist einfach
,
also umgangsprachlich sind in alle Zahlen aus
mit maximal
Stellen nach dem Komma. Diese Rundung funktioniert ganz gut mit so etwas wie LongDecimal in Ruby oder BigDecimal in Scala oder Java, wobei BigDecimal weniger Rundungsmodi anbietet als LongDecimal für Ruby.
Nun kommen wir zur Restklassenrundung. Wir gehen wieder von dieser Zehnerpotenz aus. Dann brauchen wir noch eine natürlich Zahl
und eine Menge von Resten
. Nun ist
.
Das bedeutet, wenn wir mit Nullen auf die angegebene Anzahl von Nachkommastellen auffüllen, das „Komma“ (normalerweise als „.“ geschrieben) weglassen und dann diese Zahl mit Rest durch teilen, der dabei herauskommende Rest in
liegt.
In diesem Fall wird es mit dem ROUND_HALF_EVEN eventuell schwierig, da es undefiniert oder mehrdeutig werden kann. Aber wir müssen auch den Fall abdecken, dass die 0 nicht in ist und Regeln angeben, wohin die 0 gerundet werden soll. Die Kandidaten sind hier mit selbsterklärenden Namen versehen:
- ZERO_ROUND_TO_PLUS
- ZERO_ROUND_TO_MINUS
- ZERO_ROUND_TO_CLOSEST_PREFER_PLUS
- ZERO_ROUND_TO_CLOSEST_PREFER_MINUS
- ZERO_ROUND_UNNECESSARY
Ein wichtiger Anwendungsfall ist hier und
. So kann man in der Schweiz Geldbeträge auf Vielfache von 5 Rappen (0.05 CHF) runden. Dies wurde in Rundung bei Geldbeträgen bereits genauer beschrieben.