2013-04-02 13 views
5

Mam listę lub tablicę liczb dziesiętnych w języku Python. Muszę zaokrąglić je do najbliższych 2 miejsc po przecinku, ponieważ są to kwoty pieniężne. Potrzebuję jednak całkowitej sumy, tj. Suma oryginalnej tablicy zaokrąglonej do 2 miejsc dziesiętnych musi być równa sumie zaokrąglonych elementów tablicy.Okrągłą listę numerów w języku Python i utrzymywanie sumy

Oto mój kod do tej pory:

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 

muszę skutecznego sposobu zmieniająca moja nowa tablica zaokrąglone tak, że suma ta jest również 187.976,61. Różnica w wysokości 2 pensów musi być zastosowana do pozycji 7 i 6, ponieważ mają one największą różnicę między zaokrąglonymi wpisami i oryginalnymi wpisami.

Wszelkie pomysły?

+2

Prawdopodobnie nie powinno się za pomocą liczb zmiennoprzecinkowych do reprezentowania kwoty pieniężne. Istnieje wiele liczb, takich jak '0.10', których' float' nie może dokładnie reprezentować. – NPE

+0

Co jest nie tak z twoim numpy rozwiązaniem? Jeśli dobrze rozumiem, to jest odpowiedź, której szukasz ... – mgilson

+0

@NPE: Czy to nie jest runda? Mam na myśli, że każda niepewność w liczbie 0.1 powinna być porównywalna do precyzji maszyny, prawda? – BenDundee

Odpowiedz

1

Ze wszystkimi zastrzeżeniami o użyciu liczb zmiennoprzecinkowych:

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

W twoim przykładzie, gdy masz '8 * [0,001] + [0,009]', otrzymasz '8 * [0.0] + [0.2]', który jest tego samego typu błędem, który wyznaczyłeś w moim rozwiązaniu. Zaokrąglanie się zaokrągla. – palooh

+0

@palooh To był błąd, dzięki za spostrzeżenie! Jeśli uruchomisz go teraz, wynikiem jest '7 * [0,00] + 2 * [0.01]', więc dodatkowe pensy są dodawane, minimalizując poszczególne błędy zaokrąglania. – Jaime

1

Przede wszystkim nie należy używać pływaków do przechowywania pieniędzy (wykorzystanie miejsc po przecinku zamiast). Poniżej przedstawiam dość ogólne rozwiązanie - musisz przechowywać, gromadzić i wykorzystywać sumę różnic w zaokrągleniu. Niektóre rozwlekły (i nie bardzo pythonic ;-) przykład ze swoimi numerami:

# 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

Jeśli twoje pierwsze dwie liczby to 'x.0049' i' y.0001', twoja metoda zaokrąglania zamieni je na 'x.00' i' y.01', co nie wydaje się najlepszą rzeczą do zrobienia. – Jaime

+0

Cóż, zawsze możesz posortować listę według 'abs (n% 0.01)' malejąco, ale biorąc pod uwagę _rounding_ może to nie jest takie ważne ... – palooh

4

Pierwszym krokiem jest obliczenie błędu pomiędzy pożądanym a rzeczywistym wynikiem sumy:

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

To może być pozytywny lub negatywny. Ponieważ każdy element w myRoundedList mieści się w zakresie 0.005 rzeczywistej wartości, ten błąd będzie mniejszy niż 0,01 na element oryginalnej tablicy. Można po prostu podzielić przez 0,01 rundzie, aby uzyskać liczbę elementów, które muszą być dostosowane:

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

Teraz wszystko, co pozostało, to wybrać elementy, które powinny zostać skorygowane. Optymalne wyniki pochodzą z dostosowania wartości, które były najbliżej granicy w pierwszej kolejności. Możesz je znaleźć, sortując według różnicy między wartością początkową a wartością zaokrągloną.

>>> 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

Jeśli masz długą listę, powyższe metody są nieskuteczne, ponieważ są one O (n * log (n)) (sortowanie n elementów). Jeśli istnieje duża szansa, że ​​powinieneś zmienić tylko kilka (lub jeden) z tych indeksów, możesz użyć sterty (lub min/max, jeśli jest tylko jedno miejsce do zmiany).

Nie jestem zbytnim programistą z Pythona, ale tutaj jest rozwiązanie, biorąc pod uwagę powyższe (ale nie biorąc pod uwagę niedokładności reprezentacji zmiennoprzecinkowej (już wspomniano przez innych)).

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 jest różnica liczbowa między zaokrąglonej sumy i sumy rund, to mówi nam, ile miejsca trzeba zmienić zaokrąglenie. Jeśli d wynosi zero, nie mamy oczywiście nic do roboty. Jeśli d jest 1 lub -1 najlepsze miejsce można łatwo znaleźć z min lub max. Dla dowolnej liczby możemy użyć heapq.nlargest, aby znaleźć najlepsze miejsca D=abs(d).

Dlaczego więc istnieje max, jeśli zrobiłby to nlargest ?!Ponieważ min i max są znacznie wydajniej zaimplementowane.

W ten sposób algorytm ma postać O (n + D * log (n)).

Uwaga: Za pomocą sterty można również utworzyć algorytm O (n + D^2 * log (D)), ponieważ najlepsze elementy D powinny znajdować się na najwyższych poziomach D sterty i można zamówić tę listę w krokach O (D^2 * log (D)). Jeśli n jest ogromny i D jest bardzo mały, może to wiele znaczyć.

(prawa do ponownego zastrzeżone (bo już po północy).)