Перечисляемое и избыточное исключение

Возможно, бесполезный вопрос:

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

Одно из исключений, вызванных указанным выше методом, также является OverflowException: сумма элементов в последовательности больше, чем Int64.MaxValue.

Я предполагаю, что причиной этого исключения является то, что сумма усредненных значений вычисляется с использованием переменной S типа long? Но поскольку возвращаемое значение имеет тип double, почему дизайнеры не решили сделать S также типа double?

Спасибо

Ответ 1

Поскольку эта особая перегрузка знает, что вы начинаете с значений int, она знает, что вы не используете десятичные значения. Преобразование каждого из ваших значений в double, а затем добавление значений double вместе, вероятно, было бы менее эффективным и, несомненно, откроет вам возможность проблем с нечеткой точкой с плавающей запятой, если у вас будет достаточно большой набор значений.

Update

Я просто сделал быстрый тест, и он занимает примерно на 50% дольше в два раза дольше, чтобы усреднить double, как это делается для средних int s.

Ответ 2

Во-первых, я отмечаю, что исключение не возникает, пока вы не превысили границы длинного. Как вы это сделаете? Максимум около двух миллиардов, а верхняя часть длинной составляет около восьми миллиардов миллиардов, так что это означает, что вам нужно будет усреднить более четырех миллиардов ints минимум, чтобы вызвать исключение. Это проблема, которую вы регулярно решаете?

Предположим, что для аргумента. Выполнение математики в парном разряде теряет точность, потому что двойная арифметика округляется до пятнадцати знаков после запятой. Часы:

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()); 
    }
}

Здесь мы усредняем с двойной арифметикой две серии: один миллиард, миллиард... десять миллионов раз... миллиард, один, один... девяносто миллионов раз} и тот, который это одна и та же последовательность с первой и миллиарды. Если вы запустите код, вы получите разные результаты. Не сильно отличается, но отличается, и разница будет становиться все больше и больше, чем дольше получаются последовательности. Длинная арифметика точна; двойная арифметика потенциально округляется для каждого расчета, а это означает, что с течением времени может нарастать массивная ошибка.

Кажется очень неожиданным делать операцию исключительно на int, что приводит к накоплению ошибки округления с плавающей запятой. Это то, что ожидалось при выполнении операции над поплавками, но не при выполнении на ints.