问题 Enumerable.Average和OverflowException


也许是一个无用的问题:

public static double Average<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, int> selector
)

上述方法抛出的异常之一也是 OverflowException:序列中元素的总和大于Int64.MaxValue。

我假设这个例外的原因是使用变量计算平均值的总和 小号 类型 long?但由于返回值属于类型 double,为什么没有设计师选择制作 小号 也是类型 double

谢谢


3772
2018-04-19 19:29


起源

一般的沉思,但你有可能遇到这样的例外吗?考虑一下 Func<TSource, int> 正在回归 int,它需要一个以上的序列 4,294,967,298项一切都在 int.MaxValue,超过的价值 long.MaxValue。 - Anthony Pegram
@Anthony:很好的观察。 Enumerable.Range(1, 1000000000).Select(i => int.MaxValue).Average() 我花了30多秒才在我的电脑上运行,最多只增加了约20亿。打击需要很长时间 long.MaxValue。 - StriplingWarrior


答案:


因为这个特殊的超载知道你正在开始 int 值,它知道你没有使用十进制值。将每个值转换为a double 然后添加 double 值一起可能效率较低,如果你有足够大的值集合,肯定会让你面临浮点不精确问题的可能性。

更新

我只是做了一个快速的基准测试,它需要 大约50%的时间  超过两倍 平均 double和平均值一样 int秒。


7
2018-04-19 19:43



当我对整个问题进行喋喋不休时,我正在正确地给你的回答。 - ChaosPandion
很好的回答,谢谢 - flockofcode


首先,我注意到,除非你超过一个长的边界,否则不会出现异常。你打算怎么做?每个int最多约为20亿,而long的顶部约为8亿亿,因此这意味着您必须至少采用超过40亿的平均值才能触发异常。这是你经常需要解决的那种问题吗?

假设是为了争论。在双精度数中进行数学运算会失去精度,因为双精度算术四舍五入到大约十五个小数位。看:

using System;
using System.Collections.Generic;
static class Extensions
{
    public static double DoubleAverage(this IEnumerable<int> sequence)
    {
        double sum = 0.0;
        long count = 0;
        foreach(int item in sequence) 
        {
            ++count;
            sum += item;
        }
        return sum / count;
    }
    public static IEnumerable<T> Concat<T>(this IEnumerable<T> seq1, IEnumerable<T> seq2)
    {
        foreach(T item in seq1) yield return item;
        foreach(T item in seq2) yield return item;
    }
}


class P
{
    public static IEnumerable<int> Repeat(int x, long count)
    {
        for (long i = 0; i < count; ++i) yield return x;
    }

    public static void Main()
    {
        System.Console.WriteLine(Repeat(1000000000, 10000000).Concat(Repeat(1, 90000000)).DoubleAverage()); 
        System.Console.WriteLine(Repeat(1, 90000000).Concat(Repeat(1000000000, 10000000)).DoubleAverage()); 
    }
}

在这里,我们用双算术平均两个系列:一个是{十亿,十亿,十亿......一千万......十亿,一,一......九千万次}和一个是相同的顺序与第一个和数十亿最后。如果运行代码,则会得到不同的结果。差别不大,但不同,序列越长,差异就越大。长算术是准确的;双重算术可能会结束 一切 计算,这意味着 大规模的 错误会随着时间的推移而累积。

单独执行操作会导致浮点舍入误差累积,这似乎是非常意外的。这是在对浮点数进行操作时所期望的那种事情,但在使用整数时进行操作时却没有。


7
2018-04-19 19:59