围绕Python的数字列表并保持其总和

时间:2013-04-02 17:01:04

标签: python numpy sum rounding

我在Python中有一个列表或十进制数组。我需要将它们舍入到最接近的2位小数,因为它们是货币金额。但是,我需要保持总和,即舍入到2位小数的原始数组的总和必须等于数组的舍入元素的总和。

到目前为止,这是我的代码:

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项,因为它们在舍入条目和原始条目之间具有最大差异。

5 个答案:

答案 0 :(得分:5)

第一步是计算所需结果与实际总和之间的误差:

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

答案 1 :(得分: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

答案 2 :(得分:1)

首先,你不应该使用浮动来存钱(而是使用Decimals)。但是下面我提供了一些非常通用的解决方案 - 你需要存储,累积和使用舍入中的差异总和。你的数字有些冗长(而不是非常pythonic ;-)的例子:

# 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

答案 3 :(得分:1)

answer by kettlehell中所述,请尝试使用PyPI软件包iteround。但是,它并未针对使用NumPy进行优化。

>>> from iteround import saferound
>>> saferound([1.0, 2.1, 3.6], places=0)
[1.0, 2.0, 4.0]

答案 4 :(得分:0)

如果你有一个长列表,上面的方法是低效的,因为它们是O(n * log(n))(n元素的排序)。如果机率很高,你应该只在几个(或一个)这些索引中进行更改,你可以使用堆(如果只有一个地方可以改变,则使用最小值/最大值)。

我不是一个python编码器,但考虑到上述情况,这是一个解决方案(但不考虑浮点表示不准确性(已经被其他人提及))。

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非常小,则可能意味着很多。

(保留重新考虑的权利(因为它是在午夜之后)。)