Average function without overflow exception

后端 未结 18 1718
一生所求
一生所求 2021-02-12 14:58

.NET Framework 3.5.
I\'m trying to calculate the average of some pretty large numbers.
For instance:

using System;
using System.Linq;

class Program
{
           


        
相关标签:
18条回答
  • 2021-02-12 15:42

    Here is how I would do if given this problem. First let's define very simple RationalNumber class, which contains two properties - Dividend and Divisor and an operator for adding two complex numbers. Here is how it looks:

    public sealed class RationalNumber
    {
        public RationalNumber()
        {
            this.Divisor = 1;
        }
    
    
        public static RationalNumberoperator +( RationalNumberc1, RationalNumber c2 )
        {
            RationalNumber result = new RationalNumber();
    
            Int64 nDividend = ( c1.Dividend * c2.Divisor ) + ( c2.Dividend * c1.Divisor );
            Int64 nDivisor = c1.Divisor * c2.Divisor;
            Int64 nReminder = nDividend % nDivisor;
    
            if ( nReminder == 0 )
            {
                // The number is whole
                result.Dividend = nDividend / nDivisor;
            }
            else
            {
                Int64 nGreatestCommonDivisor = FindGreatestCommonDivisor( nDividend, nDivisor );
    
                if ( nGreatestCommonDivisor != 0 )
                {
                    nDividend = nDividend / nGreatestCommonDivisor;
                    nDivisor = nDivisor / nGreatestCommonDivisor;
                }
    
                result.Dividend = nDividend;
                result.Divisor = nDivisor;
            }
    
                return result;
        }
    
    
        private static Int64 FindGreatestCommonDivisor( Int64 a, Int64 b)
        {
            Int64 nRemainder;
    
            while ( b != 0 )
            {
                nRemainder = a% b;
                a = b;
                b = nRemainder;
            }
    
            return a;
        }
    
    
        // a / b = a is devidend, b is devisor
        public Int64 Dividend   { get; set; }
        public Int64 Divisor    { get; set; }
    }
    

    Second part is really easy. Let's say we have an array of numbers. Their average is estimated by Sum(Numbers)/Length(Numbers), which is the same as Number[ 0 ] / Length + Number[ 1 ] / Length + ... + Number[ n ] / Length. For to be able to calculate this we will represent each Number[ i ] / Length as a whole number and a rational part ( reminder ). Here is how it looks:

    Int64[] aValues = new Int64[] { long.MaxValue - 100, long.MaxValue - 200, long.MaxValue - 300 };
    
    List<RationalNumber> list = new List<RationalNumber>();
    Int64 nAverage = 0;
    
    for ( Int32 i = 0; i < aValues.Length; ++i )
    {
        Int64 nReminder = aValues[ i ] % aValues.Length;
        Int64 nWhole = aValues[ i ] / aValues.Length;
    
        nAverage += nWhole;
    
        if ( nReminder != 0 )
        {
            list.Add( new RationalNumber() { Dividend = nReminder, Divisor = aValues.Length } );
        }
    }
    
    RationalNumber rationalTotal = new RationalNumber();
    
    foreach ( var rational in list )
    {
        rationalTotal += rational;
    }
    
    nAverage = nAverage + ( rationalTotal.Dividend / rationalTotal.Divisor );
    

    At the end we have a list of rational numbers, and a whole number which we sum together and get the average of the sequence without an overflow. Same approach can be taken for any type without an overflow for it, and there is no lost of precision.

    EDIT:

    Why this works:

    Define: A set of numbers.

    if Average( A ) = SUM( A ) / LEN( A ) =>

    Average( A ) = A[ 0 ] / LEN( A ) + A[ 1 ] / LEN( A ) + A[ 2 ] / LEN( A ) + ..... + A[ N ] / LEN( 2 ) =>

    if we define An to be a number that satisfies this: An = X + ( Y / LEN( A ) ), which is essentially so because if you divide A by B we get X with a reminder a rational number ( Y / B ).

    => so

    Average( A ) = A1 + A2 + A3 + ... + AN = X1 + X2 + X3 + X4 + ... + Reminder1 + Reminder2 + ...;

    Sum the whole parts, and sum the reminders by keeping them in rational number form. In the end we get one whole number and one rational, which summed together gives Average( A ). Depending on what precision you'd like, you apply this only to the rational number at the end.

    0 讨论(0)
  • 2021-02-12 15:43

    Perhaps you can reduce every item by calculating average of adjusted values and then multiply it by the number of elements in collection. However, you'll find a bit different number of of operations on floating point.

    var items = new long[] { long.MaxValue - 100, long.MaxValue - 200, long.MaxValue - 300 };
    var avg = items.Average(i => i / items.Count()) * items.Count();
    
    0 讨论(0)
  • 2021-02-12 15:44

    Use the IntX library on CodePlex.

    0 讨论(0)
  • 2021-02-12 15:45

    Let Avg(n) be the average in first n number, and data[n] is the nth number.

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

    Can avoid value overflow however loss precision when n is very large.

    0 讨论(0)
  • 2021-02-12 15:46

    If you're just looking for an arithmetic mean, you can perform the calculation like this:

    public static double Mean(this IEnumerable<long> source)
    {
        if (source == null)
        {
            throw Error.ArgumentNull("source");
        }
    
        double count = (double)source.Count();
        double mean = 0D;
    
        foreach(long x in source)
        {
            mean += (double)x/count;
        }
    
        return mean;
    }
    

    Edit:

    In response to comments, there definitely is a loss of precision this way, due to performing numerous divisions and additions. For the values indicated by the question, this should not be a problem, but it should be a consideration.

    0 讨论(0)
  • 2021-02-12 15:46

    You could keep a rolling average which you update once for each large number.

    0 讨论(0)
提交回复
热议问题