3つの長整数の平均


103

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#でサンプルを提供する方が簡単です。


1
全体の平均差分を計算して、最大値から差し引いてみませんか?
Andreas Niedermair 2014年

6
@AndreasNiedermairでしょうが、私が持っている場合場合には動作しないlong.MinValuelong.MaxValue値の間。
Ulugbek Umirov 2014年

グッドキャッチ、確かに:)
Andreas Niedermair 2014年

これについて心配する必要がありますが、これはフレームワークによって処理されるべきではありませんか?
ボル

11
実際の理由があるBigIntegerか、decimal除外されているが、あるいはそれが、これはハード作りのためだけのでしょうか?
jpmc26 2014年

回答:


142

このコードは機能しますが、それほどきれいではありません。

最初に3つの値すべてを除算し(値をフロアーするため、残りを「失う」)、次に残りを除算します。

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

上記のサンプルは、1つ以上の負の値がある場合、常に正しく機能するとは限りません。

Ulugbekで説明したように、コメントの数は以下で爆発的に増加しているため、正と負の両方の値に対する現在のBESTソリューションを次に示します。

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

3
@DavidG号に数学、(x + y + z) / 3 = x / 3 + y / 3 + z / 3
Kris Vandermotten、2014年

4
Z3を使用して、1から5までのすべての変数カウントに対してこれが正しいことを証明しました
usr

5
もちろん、これは機能するように見えますが、整数の切り捨てが動作する方法はあなたを台無しにします。 f(1,1,2) == 1whilef(-2,-2,8) == 2
KevinZ

11
モジュロ演算の脳に損傷のあるセマンティクスのため、変数の負の値が許可されている場合、これは1だけずれる、つまり切り捨てではなく切り上げられる結果をもたらす可能性があることに注意してください。たとえば、x、yが3の正の倍数で、zが-2の場合、(x+y)/3どちらが多すぎるかがわかります。
Marc van Leeuwen、2014年

6
@KevinZ:...その効果は、そもそもその特殊なケースの動作を望まなかったプログラマーによって取り消される必要があります。コンパイラーが係数から導出した可能性のある剰余からそれを導出する必要はなく、プログラマーが係数を指定できるようにすると便利だと思われます。
スーパーキャット2014年

26

NB-パトリックはすでに素晴らしい答えを出しています。これを拡張すると、次のように任意の数の整数に対して汎用バージョンを実行できます。

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;

1
これはに対しては発生しませんlongが、小さいタイプの場合、2番目の合計がオーバーフローする可能性があることに注意してください。
user541686 2014年

7

Patrick Hofmanが優れたソリューション投稿しました。ただし、必要に応じて、他のいくつかの方法で実装できます。ここでアルゴリズムを使用すると、別の解決策があります。注意深く実装すると、ハードウェア除数が遅いシステムの複数の分割よりも高速になる場合があります。ハッカーの喜びから定数で除算テクニックを使用することにより、さらに最適化できます

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;

2
32ビットの符号なし値を3で除算する良い方法は、0x55555555Lを乗算し、0x55555555を加算し、32だけ右シフトすることです。比較すると、divideby3メソッドは、多くの個別のステップを必要とするように見えます。
スーパーキャット2014年

@supercatはい、私はその方法を知っています。ハッカーの喜びによる方法はさらに正確ですが、私は別の時間の実装を行います
phuclv

「より正確」の意味がわかりません。多くの場合、相互乗算は正確な値を直接生成するか、1つまたは2つのステップで調整できる値を生成します。ところで、私は0x55555556を掛けることを提案するべきだったと思います。そうすれば、「追加」を必要とせずに正確な結果が得られます。また、ループの状態は正しいですか?ループでHとLを変更するものは何ですか?
スーパーキャット2014年

ちなみに、ハードウェア乗算器がなくても、x=y/3を介して符号なしの近似値をすぐに求めることができx=y>>2; x+=x>>2; x+=x>>4; x+=x>>8; x+=x>>16; x+=x>>32;ます。結果はxに非常に近く、計算しdelta=y-x-x-x;x必要に応じて調整を使用することで正確にすることができます。
スーパーキャット2014年

1
@ gnasher729 64ビット64→128ビットの乗算を実行できないことが多いため、32ビットコンピュータでその最適化を使用できるかどうか疑問に思います
phuclv

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

2
@UlugbekUmirovのコメントを参照してください:値の中にlong.MinValueとlong.MaxValueがある場合、機能しません
ボル

@Boluコメントはlong.MinValueにのみ適用されます。したがって、この条件を追加して、私たちのケースで機能させるようにしました。
La-comadreja 2014年

初期化されていないときに中央値をどのように使用できますか?
phuclv 2014年

@LưuVĩnhPhúc、中央値は最小値と最大値の間の値です。
La-comadreja 2014年

1
(double)(2 / 3)0.0に等しくないですか?
phuclv 2014年

5

Cはユークリッド除算ではなくフロア除算を使用するため、3つの符号なしの値よりも3つの符号なしの値の適切に丸められた平均を計算する方が簡単な場合があります。符号なし平均を取る前に各数値に0x8000000000000000ULを追加し、結果を受け取った後にそれを減算し、チェックされていないキャストを使用してInt64を取得します。

符号なし平均を計算するには、3つの値の上位32ビットの合計を計算します。次に、3つの値の下位32ビットの合計と、上からの合計に1を加えたものを計算します(1を加えると、丸められた結果が得られます)。平均は、最初の合計の0x55555555倍に2番目の3分の1を加えたものになります。

32ビットプロセッサでのパフォーマンスは、それぞれが32ビット長の3つの「合計」値を生成することにより向上する可能性があり、最終結果はになり((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3ます。それはおそらくさらに置換することによって増強されるかもしれないsumL/3((sumL * 0x55555556UL) >> 32)後者がJITオプティマイザに依存するであろうが、[それは乗算と3による除算を交換する方法を知っているかもしれません、そのコードは、実際に、明示的な乗算演算よりも効率的であるかもしれません]。


0x8000000000000000ULを追加した後、オーバーフローは結果に影響しませんか?
phuclv 2014年

@LưuVĩnhPhúcオーバーフローはありません。行く私の答え実装のために。ただし、2 32ビット整数への分割は不要でした。
KevinZ 2014年

@KevinZ:各値を32ビットの上位部分と下位部分に分割する方が、3で割る商と剰余に分割するよりも高速です。
スーパーキャット2014年

1
@LưuVĩnhPhúc:意味的に数値で動作し、正当なCプログラムでオーバーフローすることが許可されていない符号付きの値とは異なり、符号なしの値は通常、ラッピング抽象代数リングのメンバーのように動作するため、ラッピングセマンティクスは明確に定義されています。
スーパーキャット2014年

1
タプルは-3、-2、-1を表します。各値に0x8000Uを追加した後、値を7F + FF 7F + FE 7F + FDのように半分に分割する必要があります。上半分と下半分を加算すると、17D + 2FAになります。上半分の合計を下半分の合計に加算すると、477が得られます。17Dに55を掛けると、7E81が得られます。477を3で割ると、17Dになります。7Dに7E81を追加すると、7FFEになります。それから8000を引いて、-2を取得します。
スーパーキャット2014年

5

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()が得られ、考えられるすべてのエッジケースが排除されます。


1
私はAvgNをZ3コードに変換し、これがすべての妥当な入力サイズ(たとえば、1 <= args.Length <= 5およびビットベクトルサイズ6)に対して正しいことを証明しました。この答えは正しいです。
usr

素晴らしい答えケビン。あなたの貢献をありがとう!meta.stackoverflow.com/a/303292/993547
Patrick Hofman、

4

各数値をとして書くことができるという事実を使用できます。y = ax + bここxで、は定数です。それぞれay / x(その除算の整数部分)になります。各bはy % x(その除算の残り/剰余)になります。たとえば、最大数の平方根を定数として選択するなど、インテリジェントな方法でこの定数を選択するxと、オーバーフローの問題なしに数値の平均を取得できます。

数値の任意のリストの平均は、以下を見つけることで見つけることができます。

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

ここで、%モジュロを示し、/表し、除算の「全体」の部分を表します。

プログラムは次のようになります。

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

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

これは、Patrick Hofmanのソリューションとまったく同じですが、最終版よりも正確ではありません
phuclv 14

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

ここで、全体のコードのテストとベンチマークを含むが、結果が印象的なことではありません。


1

この関数は、2つの除算で結果を計算します。それは他の約数とワードサイズにうまく一般化するはずです。

ダブルワードの加算結果を計算し、除算を実行します。

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

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

{1,2,3}答えのセットは2ですが、コードが返され1ます。
ウルグベクウミロフ2014年

@UlugbekUmirovコードが修正されました。処理にはdoubleタイプを使用する必要があります
Khaled.K

1
それは私が避けたいことです-そのdoubleような場合に精度を失うことになるので、の使用。
ウルグベクウミロフ2014年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.