对Python数字列表进行四舍五入并保持其总和
我在Python中有一个十进制数字列表或数组。我需要将它们四舍五入到小数点后两位,因为这些是货币金额。但是,我需要保持总的和,即原始数组四舍五入到小数点后2位的和必须等于数组中四舍五入元素的和 以下是我目前的代码:对Python数字列表进行四舍五入并保持其总和,python,numpy,sum,rounding,Python,Numpy,Sum,Rounding,我在Python中有一个十进制数字列表或数组。我需要将它们四舍五入到小数点后两位,因为这些是货币金额。但是,我需要保持总的和,即原始数组四舍五入到小数点后2位的和必须等于数组中四舍五入元素的和 以下是我目前的代码: myOriginalList = [27226.94982, 193.0595233, 1764.3094, 12625.8607, 26714.67907, 18970.35388, 12725.41407, 23589.93271, 27948.40386, 23767.8326
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项,因为它们在四舍五入条目和原始条目之间的差异最大。关于使用浮点数的所有注意事项:
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
delta_pence=int(np.rint((原始总数-np.sum(myRoundedList))*100))
如果增量便士>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
首先,你不应该使用浮点数来存储钱(而应该使用小数)。但下面我提供了一些非常通用的解决方案-您需要存储、累积和使用舍入中的差异总和。一些详细的(但不是很像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
第一步是计算期望结果和实际总和之间的误差:
>>> 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
如果您有一个很长的列表,那么上面的方法效率很低,因为它们是O(n*log(n))(对
n
元素进行排序)。如果您很有可能只更改其中的几个(或一个)索引,那么您可以使用堆(如果只有一个地方需要更改,则使用最小/最大值)
我不是一个python程序员,但这里有一个解决方案,考虑到了上述情况(但没有考虑浮点表示的不精确性(其他人已经提到过))
d
是四舍五入之和与四舍五入之和之间的数值差,它告诉我们应该更改四舍五入的位数。如果d
为零,我们显然无事可做。如果d
为1
或-1
则可以使用min
或max
轻松找到最佳位置。对于任意数字,我们可以使用heapq.nlargest
找到最佳D=abs(D)
位置
那么,如果nlargest
可以,为什么会有一个max
?!因为min
和max
的实现效率要高得多
这样,算法是O(n+D*log(n))
注意:对于堆,您也可以创建一个O(n+D^2*log(D))算法,因为顶级D
元素应该位于堆的顶级D级,您可以按O(D^2*log(D))步骤对该列表进行排序。如果n
很大而D
很小,这可能意味着很多
P>(保留的复议权(因为它是午夜后)) < P>如A所述,考虑PYPI包。但是,它在内部不使用NumPy
>>> from iteround import saferound
>>> saferound([1.0, 2.1, 3.6], places=0)
[1.0, 2.0, 4.0]
您可能不应该使用浮点数来表示货币金额。有很多数字,
0.10
,float
无法准确表示。您的numpy解决方案有什么问题?如果我理解正确的话,这就是你要寻找的答案…@NPE:这不就是回合的目的吗?我的意思是,数字0.1中的任何不确定性都应该与机器精度相当,对吗?@BenDundee:如果你只使用float
来存储货币金额,并且意识到这些问题,那么你可能没问题。然而,你开始计算的那一刻就是你打开一大罐蠕虫的那一刻。如果你的前两个数字是x.0049
和y.0001
,你的取整方法会将它们转换为x.00
和y.01
,这似乎不是最好的选择。好吧,你总是可以按照abs对列表进行排序(n%0.01)
递减,但考虑到舍入可能并不那么重要……在您的示例中,当您有8*[0.001]+[0.009]
时,您会得到8*[0.0]+[0.2]
这与您在我的解决方案中指定的错误类型相同。取整就是取整。@palooh这是一个错误,感谢您发现它!如果您现在运行它,结果是7*[0.00]+2*[0.01]
,这样就可以添加额外的便士,从而最大限度地减少单个取整错误。
>>> from iteround import saferound
>>> saferound([1.0, 2.1, 3.6], places=0)
[1.0, 2.0, 4.0]