2013-04-02 5 views
5

Ich habe eine Liste oder ein Array von Dezimalzahlen in Python. Ich muss sie auf die nächsten 2 Dezimalstellen runden, da es sich um Geldbeträge handelt. Aber ich muss die Gesamtsumme beibehalten, d.h. die Summe der ursprünglichen Matrix, die auf 2 Dezimalstellen gerundet wurde, muss gleich der Summe der gerundeten Elemente der Matrix sein.Runden Sie eine Python-Liste von Zahlen und pflegen Sie die Summe

Hier ist mein Code so weit:

myOriginalList = [27226.94982, 193.0595233, 1764.3094, 12625.8607, 26714.67907, 18970.35388, 12725.41407, 23589.93271, 27948.40386, 23767.83261, 12449.81318] 
originalTotal = round(sum(myOriginalList), 2) 
# Answer = 187976.61 

# Using numpy 
myRoundedList = numpy.array(myOriginalList).round(2) 
# New Array = [ 27226.95 193.06 1764.31 12625.86 26714.68 18970.35 12725.41 23589.93 27948.4 23767.83 12449.81] 

newTotal = myRoundedList.sum() 
# Answer = 187976.59 

Ich brauche eine effiziente Art und Weise meine neue abgerundete Anordnung so die Änderung, dass die Summe ist auch 187.976,61. Die Differenz von 2 Pence muss auf die Elemente 7 und 6 angewendet werden, da diese den größten Unterschied zwischen den gerundeten Einträgen und den ursprünglichen Einträgen aufweisen.

Irgendwelche Ideen?

+2

Sie sollten wahrscheinlich nicht Gleitkommazahlen verwenden darstellen Geldbeträge. Es gibt viele Zahlen, wie zum Beispiel '0.10', die' float' nicht genau darstellen kann. – NPE

+0

Was ist los mit Ihrer numpigen Lösung? Wenn ich richtig verstehe, ist das die Antwort, die Sie suchen ... – mgilson

+0

@ NPE: Ist das nicht, was Runde ist? Ich meine, jede Unsicherheit in der Zahl 0,1 sollte mit Maschinengenauigkeit vergleichbar sein, oder? – BenDundee

Antwort

1

Mit allen Einsprüche über Gleitkommazahlen mit:

delta_pence = int(np.rint((originalTotal - np.sum(myRoundedList))*100)) 
if delta_pence > 0: 
    idx = np.argsort(myOriginalList - myRoundedList)[-delta_pence:] 
    myRoundedList[idx] += 0.01 
elif delta_pence < 0: 
    idx = np.argsort(myOriginalList - myRoundedList)[:delta_pence] 
    myRoundedList[idx] -= 0.01 

>>> myRoundedList.sum() 
187976.60999999999 
+0

In Ihrem Beispiel, wenn Sie '8 * [0.001] + [0.009]' haben, erhalten Sie '8 * [0.0] + [0.2]', was der gleiche Fehler ist, den Sie in meiner Lösung festgelegt haben. Rundung ist Rundung. – palooh

+0

@palooh Das war ein Fehler, danke, dass du ihn entdeckt hast! Wenn Sie es jetzt ausführen, ist das Ergebnis '7 * [0.00] + 2 * [0.01] ', so dass die zusätzlichen Pence hinzugefügt werden und die einzelnen Rundungsfehler minimiert werden. – Jaime

1

Zu allererst sollte man nicht schwimmt zum Speichern von Geld verwenden (Verwendung Dezimalzahlen statt). Aber unten biete ich eine ziemlich generische Lösung an - Sie müssen die Summe der Unterschiede in der Rundung speichern, akkumulieren und verwenden. Einige ausführliche (und nicht sehr pythonic ;-) Beispiel mit Zahlen:

# define your accuracy 
decimal_positions = 2 

numbers = [27226.94982, 193.0595233, 1764.3094, 12625.8607, 26714.67907, 18970.35388, 12725.41407, 23589.93271, 27948.40386, 23767.83261, 12449.81318] 
print round(sum(numbers),decimal_positions) 
>>> 187976.61 

new_numbers = list() 
rest = 0.0 
for n in numbers: 
    new_n = round(n + rest,decimal_positions) 
    rest += n - new_n 
    new_numbers.append(new_n) 

print sum(new_numbers) 
>>> 187976.61 
+0

Wenn Ihre ersten beiden Zahlen 'x.0049' und' y.0001' sind, wird Ihre Rundungsmethode in 'x.00' und' y.01' konvertiert, was nicht die beste Lösung zu sein scheint. – Jaime

+0

Nun, Sie können die Liste immer nach 'abs (n% 0,01)' absteigend sortieren, aber wenn man die _rounding_ betrachtet, ist es vielleicht nicht so wichtig ... – palooh

4

Der erste Schritt ist es, die Fehler zwischen dem gewünschten Ergebnis zu berechnen und die tatsächliche Summe:

>>> error = originalTotal - sum(myRoundedList) 
>>> error 
0.01999999996041879 

Dies kann entweder positiv oder negativ. Da jedes Element in myRoundedList innerhalb von 0,005 des tatsächlichen Werts liegt, beträgt dieser Fehler weniger als 0,01 pro Element des ursprünglichen Arrays. Sie können einfach von 0,01 und rund teilen die Anzahl der Elemente zu erhalten, die eingestellt werden müssen:

>>> n = int(round(error/0.01)) 
>>> n 
2 

Jetzt alles, was übrig bleibt, ist die Elemente auszuwählen, die eingestellt werden soll. Die optimalen Ergebnisse ergeben sich aus der Anpassung der Werte, die der Grenze am nächsten waren. Sie können diese finden, indem Sie nach der Differenz zwischen dem ursprünglichen Wert und dem gerundeten Wert sortieren.

>>> myNewList = myRoundedList[:] 
>>> for _,i in sorted(((myOriginalList[i] - myRoundedList[i], i) for i in range(len(myOriginalList))), reverse=n>0)[:abs(n)]: 
    myNewList[i] += math.copysign(0.01, n) 

>>> myRoundedList 
[27226.95, 193.06, 1764.31, 12625.86, 26714.68, 18970.35, 12725.41, 23589.93, 27948.4, 23767.83, 12449.81] 
>>> myNewList 
[27226.95, 193.06, 1764.31, 12625.86, 26714.68, 18970.359999999997, 12725.42, 23589.93, 27948.4, 23767.83, 12449.81] 
>>> sum(myNewList) 
187976.61 
0

Wenn Sie eine lange Liste haben, sind die oben genannten Methoden ineffizient, weil sie O (n * log (n)) (Sortieren von n Elemente). Wenn die Chancen hoch sind, dass Sie nur bei ein paar (oder einem) dieser Indizes ändern sollten, könnten Sie einen Heap verwenden (oder ein Minimum/Maximum, wenn nur ein Platz geändert werden soll).

Ich bin nicht viel von einem Python-Coder, aber hier ist eine Lösung in Anbetracht der oben genannten (aber nicht die Gleitkommadarstellung Ungenauigkeit (bereits von anderen erwähnt)).

import math 
import heapq 

def roundtosum(l, r): 
    q = 10**(-r) 
    d = int((round(sum(l),r) - sum([ round(x, r) for x in l ])) * (10**r)) 
    if d == 0: 
     return l 
    elif d in [ -1, 1 ]: 
     c, _ = max(enumerate(l), key=lambda x: math.copysign(1,d) * math.fmod(x[1] - 0.5*q, q)) 
     return [ round(x, r) + q * math.copysign(1,d) if i == c else round(x, r) for (i, x) in enumerate(l) ] 
    else: 
     c = [ i for i, _ in heapq.nlargest(abs(d), enumerate(l), key=lambda x: math.copysign(1,d) * math.fmod(x[1] - 0.5*q, q)) ] 
     return [ round(x, r) + q * math.copysign(1,d) if i in c else round(x, r) for (i, x) in enumerate(l) ] 

d ist die numerische Differenz zwischen der gerundeten Summe und der Summe der Runden, dies sagt uns, wie viele Orte, sollen wir die Rundung zu ändern. Wenn d Null ist, haben wir eindeutig nichts zu tun. Wenn d1 oder -1 ist, kann der beste Platz leicht mit min oder max gefunden werden. Für eine beliebige Nummer können wir heapq.nlargest verwenden, um die besten D=abs(d) Orte zu finden.

Also warum gibt es eine max, wenn nlargest würde tun ?!Weil min und max sind viel effizienter als das implementiert.

Auf diese Weise ist der Algorithmus O (n + D * log (n)).

Hinweis: Mit einem Heap können Sie auch einen O (n + D^2 * log (D)) - Algorithmus erstellen, da die obersten Elemente auf den obersten D-Ebenen des Heapspeichers stehen und Sie können bestellen diese Liste in O (D^2 * log (D)) Schritten. Wenn n ist riesig und ist sehr klein, das kann eine Menge bedeuten.

(Rechte für erneute Überprüfung vorbehalten (weil es nach Mitternacht ist).)