kaufmännisch runden in python 3

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
jack_321
User
Beiträge: 1
Registriert: Freitag 6. September 2019, 07:22

Hallo,
ich habe nun schon länger erfolglos nach einer korrekten implementierung des kaufmännischen Rundens in python 3/3.5 gesucht, allerdings scheinen die meisten Möglichkeiten nicht immer korrekt zu sein.

ich möchte auf 2 Nachkommastellen kaufmännisch runden.

aktuell arbeite ich mit dieser im netz gefundenen implementierung:

Code: Alles auswählen

def custom_round(n, decimals=0):
    expoN = n * 10 ** decimals
    if abs(expoN) - abs(math.floor(expoN)) < 0.5:
        return math.floor(expoN) / 10 ** decimals
    return math.ceil(expoN) / 10 ** decimals
    
allerdings liefert diese nun z.B. bei 70.585 ein falsches ERgebnis (70.58, korrekt wäre kaufmännisch 70,59)

ich Bitte hier um Unterstätzung für eine allgemein verwendbare Funktion dafür.

danke
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Problem ist, dass 70.585 nicht exakt dargestellt werden kann, sondern in Wirklichkeit etwas kleiner ist:

Code: Alles auswählen

>>> '%.15f' % 70.585
'70.584999999999994'
Exakt rechnen kann man mit dem Decimal-Modul:

Code: Alles auswählen

import decimal
num = decimal.Decimal('70.585')
rounded_num = num.quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_HALF_UP)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das hat leider mit der technischen Umsetzung von Floats zu tun. Der intern genutzte Wert weicht in einigen Fällen leicht von der "sichtbaren" Darstellung ab. Die 70.585 ist so ein Fall, sie ist in Wirklichkeit nur eine "sehr große" 70.58499999... und dieses Manko zeigt sich beim Runden.

Eine gängige Lösung ist die Nutzung des decimal-Moduls. Dann ist es aber auch ganz wichtig, dass die Zahl als String vorliegen muss, da es andernfalls wieder zum gleichen Problem kommt. Denn mit der 70.585 als Float wird ja auch wieder der "gefakte" Wert übergeben. Irgendwo wird ja hoffentlich die Zahl als String vorliegen, z.B. bei der Nutzereingabe oder beim Verarbeiten der Werte, mit denen gerechnet werden soll. Hier muss man halt die Strings direkt an die Decimal-Klasse übergeben.

Runden geht dann mittels quantize(). wobei wie in deinem Beispiel auch mit einem Exponenten gearbeitet wird. Komplett könnte das so aussehen:

Code: Alles auswählen

from decimal import Decimal, ROUND_HALF_UP

def get_decimal(number, precision=2):
    d = Decimal(number)
    if precision < 0:
        raise ValueError('precision must not be negative')
    exp = Decimal(10) ** -precision
    return d.quantize(exp, rounding=ROUND_HALF_UP)

print(get_decimal('70.585'))
EDIT: Da war jemand schneller, aber ich lass es mal wegen den zusätzlichen Hinweisen stehen...
Antworten