无溢出异常的平均函数

20

.NET Framework 3.5。
我正在尝试计算一些非常大的数字的平均值。
例如:

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        var items = new long[]
                        {
                            long.MaxValue - 100, 
                            long.MaxValue - 200, 
                            long.MaxValue - 300
                        };
        try
        {
            var avg = items.Average();
            Console.WriteLine(avg);
        }
        catch (OverflowException ex)
        {
            Console.WriteLine("can't calculate that!");
        }
        Console.ReadLine();
    }
}

显然,数学结果是9223372036854775607(long.MaxValue - 200),但我在那里遇到了异常。这是因为(在我的机器上)Average扩展方法的实现,由.NET Reflector检查如下:
public static double Average(this IEnumerable<long> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    long num = 0L;
    long num2 = 0L;
    foreach (long num3 in source)
    {
        num += num3;
        num2 += 1L;
    }
    if (num2 <= 0L)
    {
        throw Error.NoElements();
    }
    return (((double) num) / ((double) num2));
}

我知道我可以使用一个BigInt库(是的,我知道它在.NET Framework 4.0中包含,但我只能使用3.5版本)。

但我仍然想知道是否有一种相当简单的整数平均值计算实现,而不需要外部库。你知道这样的实现吗?

谢谢!


更新:

之前的示例仅是为了说明溢出问题。该问题是关于计算任何一组数字的平均数,这些数字可能相加得到一个超过类型最大值的大数字。对此混淆感到抱歉。我还更改了问题的标题以避免进一步的混淆。

感谢大家!


1
你无论如何都要将你的总和转换为double,为什么不在总和累加器中使用double类型呢? 由于将long截断为尾数宽度可能会导致一些小错误。 - ony
@ony:感觉他没有访问Average函数代码的权限 - 否则他为什么要使用反编译工具呢? - ANeves
@ANeves:那只是一种实现的变体,作为对“我仍然想知道是否有”的回应。 - ony
@PauliL - 哎呀,我把它修复成原始值了。 - Ron Klein
18个回答

0
也许你可以通过计算调整后的值的平均值,然后乘以集合中元素的数量来减少每个项目。但是,你会发现在浮点数上有一些不同的操作数量。
var items = new long[] { long.MaxValue - 100, long.MaxValue - 200, long.MaxValue - 300 };
var avg = items.Average(i => i / items.Count()) * items.Count();

0

设Avg(n)为前n个数的平均值,data[n]为第n个数。

Avg(n)=(double)(n-1)/(double)n*Avg(n-1)+(double)data[n]/(double)n

当 n 很大时,可以避免值溢出但会损失精度。


0

这是我编写的一个扩展方法版本,可以帮助解决这个问题。

    public static long Average(this IEnumerable<long> longs)
    {
        long mean = 0;
        long count = longs.Count();
        foreach (var val in longs)
        {
            mean += val / count;
        }
        return mean;
    }

谢谢你发表了你的回答。然而,这并不是对所提问题的实际回答。在Stack Overflow上,我们期望回答与被问问题直接相关。经过一点编辑,它可能会变得合适。 - Andrew Barber

0
对于两个正数(或两个负数),我在这里找到了一个非常优雅的解决方案。
在这个方案中,平均计算 (a+b)/2 可以被替换为 a+((b-a)/2)

0

0
你可以保持一个滚动平均值,每次更新一次大数。

0

在 CodePlex 上使用 IntX 库。


0

下一个平均值 = 当前平均值 + (新数值 - 当前平均值) / (当前观测次数 + 1)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接