我在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项,因为它们在舍入条目和原始条目之间具有最大差异。
答案 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
为零,我们显然无所事事。如果d
为1
或-1
,则可以使用min
或max
轻松找到最佳位置。对于任意数字,我们可以使用heapq.nlargest
来找到最佳D=abs(d)
个地方。
那么为什么会有max
,如果nlargest
会这样做?!因为min
和max
的实施效率要高得多。
这样,算法就是O(n + D * log(n))。
注意:对于堆,您也可以创建O(n + D ^ 2 * log(D))算法,因为顶部D
元素应位于堆的前D级,并且您可以在O(D ^ 2 * log(D))步骤中订购该列表。如果n
很大且D
非常小,则可能意味着很多。
(保留重新考虑的权利(因为它是在午夜之后)。)