Average of 3 long integers

前端 未结 12 1727
我寻月下人不归
我寻月下人不归 2020-12-07 10:05

I have 3 very large signed integers.

long x = long.MaxValue;
long y = long.MaxValue - 1;
long z = long.MaxValue - 2;

I want to calculate th

相关标签:
12条回答
  • 2020-12-07 10:42

    Try this:

    long n = Array.ConvertAll(new[]{x,y,z},v=>v/3).Sum()
         +  (Array.ConvertAll(new[]{x,y,z},v=>v%3).Sum() / 3);
    
    0 讨论(0)
  • 2020-12-07 10:48

    Patching Patrick Hofman's solution with supercat's correction, I give you the following:

    static Int64 Avg3 ( Int64 x, Int64 y, Int64 z )
    {
        UInt64 flag = 1ul << 63;
        UInt64 x_ = flag ^ (UInt64) x;
        UInt64 y_ = flag ^ (UInt64) y;
        UInt64 z_ = flag ^ (UInt64) z;
        UInt64 quotient = x_ / 3ul + y_ / 3ul + z_ / 3ul
            + ( x_ % 3ul + y_ % 3ul + z_ % 3ul ) / 3ul;
        return (Int64) (quotient ^ flag);
    }
    

    And the N element case:

    static Int64 AvgN ( params Int64 [ ] args )
    {
        UInt64 length = (UInt64) args.Length;
        UInt64 flag = 1ul << 63;
        UInt64 quotient_sum = 0;
        UInt64 remainder_sum = 0;
        foreach ( Int64 item in args )
        {
            UInt64 uitem = flag ^ (UInt64) item;
            quotient_sum += uitem / length;
            remainder_sum += uitem % length;
        }
    
        return (Int64) ( flag ^ ( quotient_sum + remainder_sum / length ) );
    }
    

    This always gives the floor() of the mean, and eliminates every possible edge case.

    0 讨论(0)
  • 2020-12-07 10:52

    NB - Patrick has already given a great answer. Expanding on this you could do a generic version for any number of integers like so:

    long x = long.MaxValue;
    long y = long.MaxValue - 1;
    long z = long.MaxValue - 2;
    
    long[] arr = { x, y, z };
    var avg = arr.Select(i => i / arr.Length).Sum() 
            + arr.Select(i => i % arr.Length).Sum() / arr.Length;
    
    0 讨论(0)
  • 2020-12-07 10:53

    I also tried it and come up with a faster solution (although only by a factor about 3/4). It uses a single division

    public static long avg(long a, long b, long c) {
        final long quarterSum = (a>>2) + (b>>2) + (c>>2);
        final long lowSum = (a&3) + (b&3) + (c&3);
        final long twelfth = quarterSum / 3;
        final long quarterRemainder = quarterSum - 3*twelfth;
        final long adjustment = smallDiv3(lowSum + 4*quarterRemainder);
        return 4*twelfth + adjustment;
    }
    

    where smallDiv3 is division by 3 using multipliation and working only for small arguments

    private static long smallDiv3(long n) {
        assert -30 <= n && n <= 30;
        // Constants found rather experimentally.
        return (64/3*n + 10) >> 6;
    }
    

    Here is the whole code including a test and a benchmark, the results are not that impressive.

    0 讨论(0)
  • 2020-12-07 10:57

    You could use the fact that you can write each of the numbers as y = ax + b, where x is a constant. Each a would be y / x (the integer part of that division). Each b would be y % x (the rest/modulo of that division). If you choose this constant in an intelligent way, for example by choosing the square root of the maximum number as a constant, you can get the average of x numbers without having problems with overflow.

    The average of an arbitrary list of numbers can be found by finding:

    ( ( sum( all A's ) / length ) * constant ) + 
    ( ( sum( all A's ) % length ) * constant / length) +
    ( ( sum( all B's ) / length )
    

    where % denotes modulo and / denotes the 'whole' part of division.

    The program would look something like:

    class Program
    {
        static void Main()
        {
            List<long> list = new List<long>();
            list.Add( long.MaxValue );
            list.Add( long.MaxValue - 1 );
            list.Add( long.MaxValue - 2 );
    
            long sumA = 0, sumB = 0;
            long res1, res2, res3;
            //You should calculate the following dynamically
            long constant = 1753413056;
    
            foreach (long num in list)
            {
                sumA += num / constant;
                sumB += num % constant;
            }
    
            res1 = (sumA / list.Count) * constant;
            res2 = ((sumA % list.Count) * constant) / list.Count;
            res3 = sumB / list.Count;
    
            Console.WriteLine( res1 + res2 + res3 );
        }
    }
    
    0 讨论(0)
  • 2020-12-07 10:58

    Because C uses floored division rather than Euclidian division, it may easier to compute a properly-rounded average of three unsigned values than three signed ones. Simply add 0x8000000000000000UL to each number before taking the unsigned average, subtract it after taking the result, and use an unchecked cast back to Int64 to get a signed average.

    To compute the unsigned average, compute the sum of the top 32 bits of the three values. Then compute the sum of the bottom 32 bits of the three values, plus the sum from above, plus one [the plus one is to yield a rounded result]. The average will be 0x55555555 times the first sum, plus one third of the second.

    Performance on 32-bit processors might be enhanced by producing three "sum" values each of which is 32 bits long, so that the final result is ((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3; it might possibly be further enhanced by replacing sumL/3 with ((sumL * 0x55555556UL) >> 32), though the latter would depend upon the JIT optimizer [it might know how to replace a division by 3 with a multiply, and its code might actually be more efficient than an explicit multiply operation].

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