平均3个长整数

时间:2014-05-30 08:01:32

标签: c# .net long-integer

我有3个非常大的有符号整数。

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

我想计算他们的截断平均值。预期平均值为long.MaxValue - 1,即9223372036854775806

无法将其计算为:

long avg = (x + y + z) / 3; // 3074457345618258600

注意:我读了所有关于2个数字的平均值的问题,但我不知道该技术如何应用​​于平均3个数字。

使用BigInteger会非常容易,但我们假设我无法使用它。

BigInteger bx = new BigInteger(x);
BigInteger by = new BigInteger(y);
BigInteger bz = new BigInteger(z);
BigInteger bavg = (bx + by + bz) / 3; // 9223372036854775806

如果我转换为double,那么,当然,我会失去精确度:

double dx = x;
double dy = y;
double dz = z;
double davg = (dx + dy + dz) / 3; // 9223372036854780000

如果我转换为decimal,它会起作用,但我们也假设我不能使用它。

decimal mx = x;
decimal my = y;
decimal mz = z;
decimal mavg = (mx + my + mz) / 3; // 9223372036854775806

问题:有没有办法只使用long类型来计算3个非常大的整数的截断平均值?不要将该问题视为C#特定的,只是我更容易在C#中提供样本。

12 个答案:

答案 0 :(得分:140)

这段代码可行,但不是很好。

它首先划分所有三个值(它将值放在底层,这样你就失去了剩余部分),然后除去余数:

long n = x / 3
         + y / 3
         + z / 3
         + ( x % 3
             + y % 3
             + z % 3
           ) / 3

请注意,当具有一个或多个负值时,上述示例并不总是正常工作。

正如Ulugbek所讨论的,由于下面的评论数量正在爆炸式增长,以下是正面和负面价值的最佳解决方案。

感谢Ulugbek UmirovJames SKevinZMarc van Leeuwengnasher729的答案和评论,这是当前的解决方案:

static long CalculateAverage(long x, long y, long z)
{
    return (x % 3 + y % 3 + z % 3 + 6) / 3 - 2
            + x / 3 + y / 3 + z / 3;
}

static long CalculateAverage(params long[] arr)
{
    int count = arr.Length;
    return (arr.Sum(n => n % count) + count * (count - 1)) / count - (count - 1)
           + arr.Sum(n => n / count);
}

答案 1 :(得分:26)

NB - 帕特里克已经给了a great answer。对此进行扩展,您可以为任意数量的整数执行泛型版本,如下所示:

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;

答案 2 :(得分:7)

Patrick Hofman posted a great solution。但如果需要,它仍然可以通过其他几种方式实现。使用算法here我有另一个解决方案。如果仔细实施,它可能比具有慢硬件除数的系统中的多个分区更快。它可以通过使用来自黑客的喜悦的divide by constants技术进一步优化

public class int128_t {
    private int H;
    private long L;

    public int128_t(int h, long l)
    {
        H = h;
        L = l;
    }

    public int128_t add(int128_t a)
    {
        int128_t s;
        s.L = L + a.L;
        s.H = H + a.H + (s.L < a.L);
        return b;
    }

    private int128_t rshift2()  // right shift 2
    {
        int128_t r;
        r.H = H >> 2;
        r.L = (L >> 2) | ((H & 0x03) << 62);
        return r;
    }

    public int128_t divideby3()
    {
        int128_t sum = {0, 0}, num = new int128_t(H, L);
        while (num.H || num.L > 3)
        {
            int128_t n_sar2 = num.rshift2();
            sum = add(n_sar2, sum);
            num = add(n_sar2, new int128_t(0, num.L & 3));
        }

        if (num.H == 0 && num.L == 3)
        {
            // sum = add(sum, 1);
            sum.L++;
            if (sum.L == 0) sum.H++;
        }
        return sum; 
    }
};

int128_t t = new int128_t(0, x);
t = t.add(new int128_t(0, y));
t = t.add(new int128_t(0, z));
t = t.divideby3();
long average = t.L;

在64位平台上的C / C ++中__int128

更容易
int64_t average = ((__int128)x + y + z)/3;

答案 3 :(得分:7)

您可以根据数字之间的差异计算数字的平均值,而不是使用总和。

我们假设x是最大值,y是中位数,z是最小值(正如你所知)。我们将其称为最大值,中位数和最小值。

根据@ UlugbekUmirov评论添加条件检查器:

long tmp = median + ((min - median) / 2);            //Average of min 2 values
if (median > 0) tmp = median + ((max - median) / 2); //Average of max 2 values
long mean;
if (min > 0) {
    mean = min + ((tmp - min) * (2.0 / 3)); //Average of all 3 values
} else if (median > 0) {
    mean = min;
    while (mean != tmp) {
        mean += 2;
        tmp--;
    }
} else if (max > 0) {
    mean = max;
    while (mean != tmp) {
        mean--;
        tmp += 2;
    }
} else {
    mean = max + ((tmp - max) * (2.0 / 3));
}

答案 4 :(得分:5)

因为C使用浮动除法而不是欧几里德除法,所以可能更容易计算三个无符号值的正确舍入平均值而不是三个有符号值。只需在取无符号平均值之前为每个数字添加0x8000000000000000UL,在获取结果后减去它,然后使用未经检查的强制转换回Int64以获得有符号平均值。

要计算无符号平均值,请计算三个值的前32位之和。然后计算三个值的底部32位的总和,再加上上面的和,再加上一个[加一是产生舍入结果]。平均值将是第一笔金额的0x55555555倍,加上第二笔金额的三分之一。

32位处理器的性能可能会通过产生三个“sum”值来增强,每个值都是32位长,因此最终结果为((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3;将sumL/3替换为((sumL * 0x55555556UL) >> 32)可能会进一步增强,但后者将依赖于JIT优化器[它可能知道如何用乘法将除法替换为3,其代码可能实际上是比显式乘法运算更有效率。

答案 5 :(得分:4)

您可以使用以下事实:您可以将每个数字写为y = ax + b,其中x是常量。每个a都是y / x(该分区的整数部分)。每个b将是y % x(该部门的其余/模数)。如果您以智能方式选择此常量,例如通过选择最大数字的平方根作为常量,您可以获得x个数的平均值,而不会出现溢出问题。

通过查找以下内容可以找到任意数字列表的平均值:

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

其中%表示模数,/表示整数&#39;分工的一部分。

该程序看起来像:

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

答案 6 :(得分:4)

通过Patrick Hofman更正修补supercat的解决方案,我为您提供以下内容:

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

N元素案例:

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

这总是给出平均值的floor(),并消除所有可能的边缘情况。

答案 7 :(得分:4)

如果你知道你有N个值,你可以将每个值除以N并将它们加在一起吗?

long GetAverage(long* arrayVals, int n)
{
    long avg = 0;
    long rem = 0;

    for(int i=0; i<n; ++i)
    {
        avg += arrayVals[i] / n;
        rem += arrayVals[i] % n;
    }

    return avg + (rem / n);
}

答案 8 :(得分:2)

我也尝试了它并提出了一个更快的解决方案(尽管只有3/4左右)。它使用单一划分

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

其中smallDiv3使用乘法除以3并仅用于小参数

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

以下whole code包括测试和基准测试,results并不令人印象深刻。

答案 9 :(得分:1)

此函数以两个分区计算结果。它应该很好地概括为其他除数和字大小。

它的工作原理是计算双字加法结果,然后计算出除法。

Int64 average(Int64 a, Int64 b, Int64 c) {
    // constants: 0x10000000000000000 div/mod 3
    const Int64 hdiv3 = UInt64(-3) / 3 + 1;
    const Int64 hmod3 = UInt64(-3) % 3;

    // compute the signed double-word addition result in hi:lo
    UInt64 lo = a; Int64 hi = a>=0 ? 0 : -1;
    lo += b; hi += b>=0 ? lo<b : -(lo>=UInt64(b));
    lo += c; hi += c>=0 ? lo<c : -(lo>=UInt64(c));

    // divide, do a correction when high/low modulos add up
    return hi>=0 ? lo/3 + hi*hdiv3 + (lo%3 + hi*hmod3)/3
                 : lo/3+1 + hi*hdiv3 + Int64(lo%3-3 + hi*hmod3)/3;
}

答案 10 :(得分:0)

<强>数学

(x + y + z) / 3 = x/3 + y/3 + z/3

(a[1] + a[2] + .. + a[k]) / k = a[1]/k + a[2]/k + .. + a[k]/k

<强>代码

long calculateAverage (long a [])
{
    double average = 0;

    foreach (long x in a)
        average += (Convert.ToDouble(x)/Convert.ToDouble(a.Length));

    return Convert.ToInt64(Math.Round(average));
}

long calculateAverage_Safe (long a [])
{
    double average = 0;
    double b = 0;

    foreach (long x in a)
    {
        b = (Convert.ToDouble(x)/Convert.ToDouble(a.Length));

        if (b >= (Convert.ToDouble(long.MaxValue)-average))
            throw new OverflowException ();

        average += b;
    }

    return Convert.ToInt64(Math.Round(average));
}

答案 11 :(得分:0)

试试这个:

long n = Array.ConvertAll(new[]{x,y,z},v=>v/3).Sum()
     +  (Array.ConvertAll(new[]{x,y,z},v=>v%3).Sum() / 3);