2013-04-02 19 views
5

我有一個列表或十進制數的在Python陣列。我需要將它們四捨五入到最接近的2位小數位,因爲這些是貨幣金額。但是,我需要維護總體和,即原始數組的四捨五入到小數點後2位的總和必須等於數組的四捨五入元素的總和。回合數的Python列表和維護的總和

這裏是我到目前爲止的代碼:

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 

我需要修改我的新的圓形陣列等的有效方法是總和還187976.61。 2便士差異需要應用於項目7和6,因爲這些在四捨五入的條目和原始條目之間具有最大的差異。

任何想法?

+2

您可能不應該使用浮點數來表示貨幣金額。有很多數字,比如'0.10','浮動'不能完全表示。 – NPE

+0

你的numpy解決方案有什麼問題?如果我理解正確,那就是你正在尋找的答案...... – mgilson

+0

@NPE:那不是什麼回合?我的意思是,數字0.1的任何不確定性都應該與機器精度相媲美,對嗎? – BenDundee

回答

1

與所有有關使用浮點數的告誡:

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

在你的例子中,當你有'8 * [0.001] + [0.009]'時,你得到'8 * [0.0] + [0.2]'這是你在我的解決方案中指定的錯誤類型。舍入是舍入。 – palooh

+0

@palooh這是一個錯誤,感謝您的發現!如果你現在運行它,結果是'7 * [0.00] + 2 * [0.01]',所以額外的便士被添加到最小化個別舍入誤差。 – Jaime

1

首先你不應該使用用於存儲貨幣浮動的(使用小數代替)。但下面我提供了一些非常通用的解決方案 - 您需要存儲,積累和使用舍入差異的總和。與你的數字有些冗長(而不是非常符合Python ;-)例如:

# 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

如果你的前兩個數字是'x.0049'和'y.0001',你的舍入方法會將它們轉換爲'x.00'和'y.01',這似乎不是最好的選擇。 – Jaime

+0

好吧,你總是可以用'abs(n%0.01)'降序排列列表,但考慮到_rounding_也許它並不重要...... – palooh

4

的第一步是計算期望的結果和實際總和之間的誤差:

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

這可以是正面或負面。由於myRoundedList中的每個項目都在實際值的0.005以內,因此每個原始陣列項目的該錯誤將小於0.01。您只需0.01和圓劃分得到了必須調整的項目數:

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

現在,所有剩下的就是選擇應該進行調整的項目。最佳結果來自調整那些最接近邊界的值。您可以通過按原始值與四捨五入值之間的差異進行排序來查找這些值。

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

如果你有一個長長的名單,因爲他們是爲O(n *的log(n))(排序n元素)上面的方法效率低。如果機會是很高,你應該只在這些指標,你可以使用堆(或最小/最大,如果只有一個地方改變)的少數(或一個)而改變。

我沒有太大的蟒蛇編碼器的,但這裏的考慮的解決方案上面的(但不考慮浮點表示誤差(已經被別人提及))。

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是圓形的總和輪的總和之間的數值差異,這告訴我們,我們應該有多少地方改變舍入。如果d爲零,我們顯然無所事事。如果d1-1最好的地方可以很容易地minmax被發現。對於任意數量的,我們可以用heapq.nlargest找到最好D=abs(d)地方。

那麼爲什麼會出現一個max,如果nlargest會怎麼做?因爲minmax比這更有效地實施。

這樣,算法是O(n + D * log(n))。

注意:有了堆,您還可以創建一個O(n + D^2 * log(D))算法,因爲頂部的D元素應該位於堆的頂部D級別上,您可以訂購在O(D^2 * log(D))步驟中列出該列表。如果n是巨大的,D非常小,這可能意味着很多。

(保留複議權利(因爲它在午夜之後)。)