按比例分配(按比例分配)一组值的值
我需要编写代码,根据列表中“基础”值的相对权重,在列表中按比例分配值。只需将“基准”值除以“基准”值之和,然后将系数乘以原始值,即可在一定程度上按比例计算:
proratedValue = (basis / basisTotal) * prorationAmount;
但是,计算结果必须四舍五入为整数值。四舍五入的效果意味着列表中所有项目的按比例值总和可能与原始按比例金额不同。
有人能解释一下如何应用一种“无损”按比例分配算法,在不出现舍入误差的情况下,尽可能准确地在列表中分配值吗?
最佳答案:
简单的算法草图…
有一个从零开始的连续总数。
对于第一个项目,请执行您的标准“将基础除以总基础,然后乘以比例金额”。
将运行总数的原始值存储在其他地方,然后将刚才计算的金额加在2中。
将运行合计的旧值和新值都舍入为整数(不要修改现有值,将其舍入为单独的变量),并取其差额。
步骤4中计算的数字是分配给当前基础的值。
对每个基础重复步骤2-5。
这保证了总金额按比例分配等于输入的按比例分配的金额,因为您从未实际修改运行的总金额本身(您只将其四舍五入值用于其他计算,而不将其写回)。整数舍入以前可能存在的问题现在已经解决了,因为舍入误差将随着时间的推移累积在运行的总计中,并最终在另一个方向上推过舍入阈值。
基本示例:
Input basis: [0.2, 0.3, 0.3, 0.2]
Total prorate: 47
----
R used to indicate running total here:
R = 0
First basis:
oldR = R [0]
R += (0.2 / 1.0 * 47) [= 9.4]
results[0] = int(R) - int(oldR) [= 9]
Second basis:
oldR = R [9.4]
R += (0.3 / 1.0 * 47) [+ 14.1, = 23.5 total]
results[1] = int(R) - int(oldR) [23-9, = 14]
Third basis:
oldR = R [23.5]
R += (0.3 / 1.0 * 47) [+ 14.1, = 37.6 total]
results[1] = int(R) - int(oldR) [38-23, = 15]
Fourth basis:
oldR = R [37.6]
R += (0.2 / 1.0 * 47) [+ 9.4, = 47 total]
results[1] = int(R) - int(oldR) [47-38, = 9]
9+14+15+9 = 47
文章内容 来源于:https://mlog.club/article/334541
golang实现并支持自定义小数位:
func CalculateAmountDecimal(amount float64, percentages map[uint]float64, decimalNum int32) map[uint]float64 {
var allPercentage decimal.Decimal
for _, v := range percentages {
allPercentage = allPercentage.Add(decimal.NewFromFloat(v))
}
result := make(map[uint]float64)
var oldR, R decimal.Decimal
keys := []int{}
for k, _ := range percentages {
keys = append(keys, int(k))
}
// 处理所有比例都为 0 的情况,全部为0即将所有比例重置为1,并按相同比例继续计算
if allPercentage.Equal(decimal.NewFromFloat(0)) {
l := len(percentages)
if l > 0 {
for k, _ := range percentages {
percentages[k] = 1
allPercentage = allPercentage.Add(decimal.NewFromFloat(1))
}
}
}
sort.Ints(keys)
for _, k := range keys {
if v, ok := percentages[uint(k)]; ok {
oldR = R
tmpR := decimal.NewFromFloat(v).Div(allPercentage).Mul(decimal.NewFromFloat(amount))
R = R.Add(tmpR)
if R.Equal(decimal.NewFromFloat(amount)) {
tmpAmount := decimal.NewFromFloat(0)
for _, val := range result {
tmpAmount = tmpAmount.Add(decimal.NewFromFloat(val))
}
result[uint(k)], _ = decimal.NewFromFloat(amount).Sub(tmpAmount).Float64()
break
}
itemAmount := R.Round(decimalNum).Sub(oldR.Round(decimalNum)).Round(decimalNum)
result[uint(k)], _ = itemAmount.Float64()
}
}
return result
}