Здравствуйте, syrompe, Вы писали:
S>>А для Average всё прекрасно ложится — делаете Average<int, long>(t). S>мне бы хотелось таки int на входе, long в сумме и double на выходе.
Ну, если хочется, то почему нет?
public static TResult Average<T, TSum, TResult>(IEnumerable<T> values)
where T : INumber<T>
where TSum : INumber<TSum>
where TResult : INumber<TResult>
{
TSum sum = Sum<T, TSum>(values);
return TResult.CreateChecked(sum) / TResult.CreateChecked(values.Count());
}
Хотя при таких условиях проще уже написать необобщённую версию
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Но это всё же выглядит как закат солнца вручную.
И, как я уже говорил, на следующем этапе нам придётся сделать switch по конкретным типам, т.к. SIMD-интринсики не обобщаются.
Хотя, можно попробовать сделать генеричную обёртку вокруг интринсиков, с тем же type switch внутри. Но сходу неясно, получится ли результирующий код хоть сколько-то более понятным.
Ну, и для SIMD всё же придётся сделать рантайм-проверку наличия в процессоре соответствующих инструкций. Заинлайнить всё полностью уже не получится — лучшее, что можно сделать, так это при прогреве получить (неуправляемый) указатель на наиболее подходящую версию кода для текущей архитектуры, и диспетчеризовать в неё. Не уверен, что это будет лучше, чем честная рантайм-проверка типа.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, syrompe, Вы писали: S>Они среднеквадратическое за два прохода считают что ли?
По исходнику — да. Ну это ж пример на пальцах. S>Похоже и среднее тоже по методу "сложить и поделить" ...
В статье прямо над кодом StandardDeviation приведён код Average:
public static TResult Average<T, TResult>(IEnumerable<T> values)
where T : INumber<T>
where TResult : INumber<TResult>
{
TResult sum = Sum<T, TResult>(values);
return TResult.CreateChecked(sum) / TResult.CreateChecked(values.Count());
}
Ну, и код Sum тоже приведён:
public static TResult Sum<T, TResult>(IEnumerable<T> values)
where T : INumber<T>
where TResult : INumber<TResult>
{
TResult result = TResult.Zero;
foreach (var value in values)
{
result += TResult.CreateChecked(value);
}
return result;
}
Так что да — сложить и поделить. А вы бы что предложили? Меня в приведённых примерах беспокоит разве что потеря точности.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали: S>Теперь главное — вспомнить, зачем я их так долго ждал S>Ведь помню были какие-то мега-идеи того, для чего всё это мне было нужно
Ну вот есть время до выхода .Net 7 что бы вспомнить.
и солнце б утром не вставало, когда бы не было меня
S>Так что да — сложить и поделить. А вы бы что предложили? Меня в приведённых примерах беспокоит разве что потеря точности.
С интами будет переполнение в аккумуляторе. Можно аккумулятор long сделать это в принципе решит проблему, но не ложится на эти абстракции.
С float при суммировании будут складываться числа разных размерностей со всеми вытекающими.
S(0)=0
S(n+1)=(a_n+1-S(n))/(n+1)+S(n)
По этой методе недостатков округления сильно меньше (они есть, но результат устойчив). Вот забыл совсем как формула называется, если кто помнит подскажите плз.
Но что с дисперсией что со средним абстракции подтекают.
Здравствуйте, syrompe, Вы писали: S>С интами будет переполнение в аккумуляторе. Можно аккумулятор long сделать это в принципе решит проблему, но не ложится на эти абстракции.
Не будет. Вы не можете параметризовать приведённый метод StandardDeviation интами, т.к. ни int ни long не реализуют IFloatingPointIeee754<T>.
А для Average всё прекрасно ложится — делаете Average<int, long>(t).
S>С float при суммировании будут складываться числа разных размерностей со всеми вытекающими.
Да, это то, на что я намекал в
Меня в приведённых примерах беспокоит разве что потеря точности.
S>
S>S(0)=0
S>S(n+1)=(a_n+1-S(n))/(n+1)+S(n)
S>По этой методе недостатков округления сильно меньше (они есть, но результат устойчив). Вот забыл совсем как формула называется, если кто помнит подскажите плз.
Тут есть некоторая сложность. Если по этой методе усреднять целые числа, то получится пежня из-за округления промежуточных результатов.
Попробуйте посчитать Average<int, long>(new[] {1, 2, 3, 4, 5}).
Заставлять всегда считать среднее только в плавающей запятой — так себе идея.
Можно сделать две версии — IntAverage и FloatAverage, т.к. в дотнете нельзя сделать перегрузки методов, которые бы отличались только констреинтами на дженериках.
А можно просто взять для суммирования Алгоритм Кэхэна. Для плавающей запятой он минимизирует погрешности; для целых чисел его, скорее всего, JIT сведёт к тому же бинарному коду, что и простое суммирование.
Но для достижения счастья нам, конечно же, понадобится что-то вроде этого: http://blog.zachbjornson.com/2019/08/11/fast-float-summation.html
То есть вычисление суммы всё же придётся делать не-дженерик, а специфично для конкретного типа. В реальной боевой библиотеке код Sum<T, TResult> скорее всего будет выглядеть как набор if(typeof(T) == typeof(int64))..., т.к. в дотнете это единственный способ сделать специализацию дженерика без потери производительности.
Зато вот код Average и StandardDeviation опишется обобщённым способом.
Правда, потом нас не устроит вычисление суммы квадратов отдельным проходом. Мы выкинем нафиг функцию Average, и запилим алгоритмы в зависимости от используемых типов. То есть — берём за основу что-то отсюда: https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance, и ускоряем при помощи SIMD.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Можно сделать две версии — IntAverage и FloatAverage, т.к. в дотнете нельзя сделать перегрузки методов, которые бы отличались только констреинтами на дженериках.
Можно в рантайме выбирать алгоритм. Один дополнительный type check на более менее длинных последовательностях сильно не повлияет. В Enumerable именно так выбираются специализированные алгоритмы для ICollection и [].