Перечислимый.Исключение Average и OverflowException

#c# #linq

#c# #linq

Вопрос:

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

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

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

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

Спасибо

Комментарии:

1. Общие размышления, но насколько вероятно, что вы встретите такое исключение? Учтите, что это Func<TSource, int> возвращает int , и что потребовалась бы последовательность из более чем 4 294 967 298 элементов , все в int.MaxValue , чтобы превысить значение long.MaxValue .

2. @Anthony: Хорошее наблюдение. Enumerable.Range(1, 1000000000).Select(i => int.MaxValue).Average() запуск на моем компьютере занял более 30 секунд и составил всего около 2 миллиардов. Для этого потребовалось бы очень много времени long.MaxValue .

Ответ №1:

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

Обновить

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

Комментарии:

1. Я официально добавляю свой скромный вес в ваш ответ, поскольку я сформулировал весь вопрос.

Ответ №2:

Прежде всего, я отмечаю, что исключение не возникает до тех пор, пока вы не превысите границы long . Как ты собираешься это сделать? Каждое значение int может составлять не более двух миллиардов, а верхнее значение long — около восьми миллиардов миллиардов, так что это означает, что вам нужно было бы использовать среднее значение более четырех миллиардов целых чисел, чтобы вызвать исключение. Это та проблема, которую вам регулярно приходится решать?

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

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

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

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