计算大数的加权平均值

时间:2010-05-30 07:01:09

标签: java average weighted-average

我正在尝试获得一些数字的加权平均值。基本上我有:

Price    - 134.42
Quantity - 15236545

可能只有一两个或多达五十或六十对价格和数量。我需要弄清楚价格的加权平均值。基本上,加权平均值应该对像

这样的对给予非常小的权重
Price    - 100000000.00
Quantity - 3

以及上面的那对。

我目前的公式是:

((price)(quantity) + (price)(quantity) + ...)/totalQuantity

到目前为止,我已经完成了这项工作:

        double optimalPrice = 0;
        int totalQuantity = 0;
        double rolling = 0;
        System.out.println(rolling);

        Iterator it = orders.entrySet().iterator();
        while(it.hasNext()) {
            System.out.println("inside");
            Map.Entry order = (Map.Entry)it.next();
            double price = (Double)order.getKey();
            int quantity = (Integer)order.getValue();
            System.out.println(price + " " + quantity);

            rolling += price * quantity;
            totalQuantity += quantity;
            System.out.println(rolling);
        }
        System.out.println(rolling);
        return rolling/totalQuantity;

问题是我很快就将“滚动”变量最大化了。

如何实际获得加权平均值?

7 个答案:

答案 0 :(得分:3)

一种解决方案是对java.math.BigIntegerrolling使用totalQuantity,并且只在末尾划分它们。这具有更好的数值稳定性,因为最后只有一个浮点除法,其他一切都是整数运算。

BigInteger基本上是无限制的,所以你不应该遇到任何溢出。

编辑:抱歉,只有在重新阅读后我才注意到您的价格是double。也许值得通过将它乘以100然后转换为BigInteger来规避这一点 - 因为我在你的例子中看到它在小数点右边正好有两位数 - 然后在结尾处除以100,尽管它是一个有点黑客。

答案 1 :(得分:3)

double可以容纳相当大的数字(大约1.7 x 10 ^ 308,根据文档),但你可能不应该将它用于需要精确精度的值(例如货币值)。

请查看BigDecimal课程。 This question on SO更详细地讨论了它。

答案 2 :(得分:1)

为获得最大的灵活性,rolling使用BigDecimaltotalQuantity使用BigInteger。划分后(注意,你将它向后移动;它应该是滚动/ totalQuantity),你可以返回一个BigDecimal,或者以精度损失使用doubleValue

答案 3 :(得分:0)

在任何给定时间点,您都记录了总价值ax + by + cz + ... = pq 总重量a + b + c + ... = p。知道两者会得到平均值pq/p = q。问题是pqp是溢出的大笔金额,即使您只想要中等大小的q

下一步添加了r的权重和s的权重。您希望仅使用(pq + rs) / (p + r)的值来查找新的总和q,只有当ppq通过在分子中以某种方式“消灭”时才会发生(pq + rs) / (p + r) - q 相同分数的分母。这是不可能的,正如我将要展示的那样。

此迭代中需要添加的值自然是

p*q

无法将其简化为p(pq + rs) / q(p + r) 消失的点。你也可以找到

pq

为了获得下一个平均值而乘以q的因子;但同样,pp仍然存在。所以没有聪明的解决方案。

其他人提到了任意精度变量,这是一个很好的解决方案。 pqp的大小随条目数呈线性增长,整数/浮点数的内存使用和计算速度与值的大小呈对数增长。所以性能是O(log(n)),不像灾难那样{{1}}是多个数字的倍数。

答案 4 :(得分:0)

首先,我看不出你如何“最大化”rolling变量。正如@Ash指出的那样,它可以表示最多约1.7 x 10^308的值。我能想到的唯一可能性就是你的输入中有一些不好的值。 (也许真正的问题是你正在失去精确度......)

其次,您使用Map来表示订单很奇怪,可能会被破坏。您目前使用它的方式,不能代表涉及两个或更多具有相同价格的物品的订单。

答案 5 :(得分:0)

您的最终结果只是精确度的加权平均值,因此您可能不需要遵循计算帐户余额时使用的规则等。如果我对上述内容是正确的,那么您不需要使用{ {1}},BigDecimal就足够了。

溢出问题可以通过存储“运行平均值”并用每个新条目更新来解决。即,让

a_n =(sum_ {i = 1} ^ n x_i * w_i)/(sum_ {i = 1} ^ n w_i)

对于n = 1,...,N。您从a_n = x_n开始,然后添加

d_n:= a_ {n + 1} - a_n

到它。 d_n的公式是

d_n =(x_ {n + 1} - w_ {n + 1} * a_n)/ W_ {n + 1}

其中W_n:= sum_ {i = 1} ^ n w_n。你需要跟踪W_n,但是这个问题可以通过将它存储为double来解决(因为我们只对平均值感兴趣)。您还可以标准化权重,如果您知道所有权重都是1000的倍数,则将它们除以1000。

要获得更高的准确度,您可以使用compensated summation

抢先解释:这里可以使用浮点运算。 double的相对精度为2E-16。 OP平均为正数,因此不会出现取消错误。任意精度算术的支持者没有告诉你的是,除了舍入规则之外,在 给你比IEEE754浮点运算提供更多精度的情况下,这将是显着的记忆和性能成本。浮点算法是由非常聪明的人(Kahan教授等人)设计的,如果有一种方法可以比浮点数提供更低的算术精度,那么他们就可以做到。

免责声明:如果你的重量是完全疯狂的(一个是1,另一个是10000000),那么我不是百分百肯定你是否会得到令人满意的准确性,但你可以在一些例子中测试它,当你知道答案应该是什么是

答案 6 :(得分:0)

执行两个循环:在第一个循环中首先计算totalQuantity。然后在第二个循环累积价格*(数量/ totalQuantity)。