Ortalama 3 uzun tam sayı


103

3 çok büyük işaretli tam sayım var.

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

Kısaltılmış ortalamalarını hesaplamak istiyorum. Beklenen ortalama değer long.MaxValue - 1, yani 9223372036854775806.

Bunu şu şekilde hesaplamak imkansızdır:

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

Not: 2 sayının ortalamasıyla ilgili tüm bu soruları okudum, ancak bu tekniğin ortalama 3 sayıya nasıl uygulanabileceğini bilmiyorum.

Kullanımı ile çok kolay olurdu BigIntegerama kullanamayacağımı varsayalım.

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

Ben dönüştürmek olursa double, o zaman, tabii ki, hassas kaybetmek:

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

Dönüştürürsem decimalişe yarıyor, ama aynı zamanda onu kullanamayacağımı varsayalım.

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

Soru: Çok büyük 3 tam sayının kesilmiş ortalamasını yalnızca longtür kullanımıyla hesaplamanın bir yolu var mı ? Bu soruyu C # -özel olarak düşünmeyin, sadece C # ile örnekler sağlamak benim için daha kolay.


1
neden genel ortalama farkı hesaplayıp maks.
Andreas Niedermair

6
@AndreasNiedermair eğer varsa long.MinValueve long.MaxValuedeğerler arasında olsaydı işe yaramazdı .
Ulugbek Umirov

gerçekten iyi yakaladım :)
Andreas Niedermair

Bunun için endişelenmemiz gerektiğinden emin misin, bu çerçeve tarafından ele alınmamalı mı?
Bolu

11
Dışlanan BigIntegerveya decimaldışlanan herhangi bir gerçek neden var mı , yoksa sadece bunu zorlaştırmak için mi?
jpmc26

Yanıtlar:


142

Bu kod işe yarayacak, ancak o kadar da güzel değil.

Önce üç değeri de böler (değerleri katlar, böylece kalanı 'kaybedersiniz') ve sonra kalanı böler:

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

Yukarıdaki örneğin, bir veya daha fazla negatif değere sahip olduğunda her zaman düzgün çalışmadığını unutmayın.

Ulugbek ile tartışıldığı gibi, aşağıda yorumların sayısı arttığından, hem pozitif hem de negatif değerler için mevcut EN İYİ çözüm burada.

Ulugbek Umirov , James S , KevinZ , Marc van Leeuwen , gnasher729'un cevapları ve yorumları sayesinde şu anki çözüm budur:

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 Hayır. Matematikte (x + y + z) / 3 = x / 3 + y / 3 + z / 3,.
Kris Vandermotten

4
1 ile 5 arasındaki tüm değişken sayıları için bunun doğruluğunu kanıtlamak için
usr

5
Elbette bu işe yarıyor gibi görünüyor, ancak tamsayı kesmenin çalışma şekli sizi mahvedecek. f(1,1,2) == 1süref(-2,-2,8) == 2
KevinZ

11
Modulo işleminin beyinden zarar görmüş semantiği nedeniyle, bunun, değişkenler için negatif değerlere izin veriliyorsa, aşağıdan ziyade yukarı yuvarlanan bir sonuç verebileceğini unutmayın. Örneğin x, y 3'ün pozitif katlarıysa ve z -2 ise, (x+y)/3hangisinin çok fazla olduğunu elde edersiniz .
Marc van Leeuwen

6
@KevinZ: ... etkisinin ilk etapta bu özel durum davranışını asla istemeyen bir programcı tarafından geri alınması gerekiyor. Derleyicinin modülden türetmiş olabileceği bir kalandan türetmek zorunda kalmak yerine programcının modülü belirtmesine izin vermek yararlı görünecektir.
supercat

26

NB - Patrick şimdiden harika bir cevap verdi . Bunu genişleterek, aşağıdaki gibi herhangi bir sayıdaki tamsayı için genel bir sürüm yapabilirsiniz:

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
Bu olmaz long, ancak daha küçük türler için ikinci toplamın taşabileceğini unutmayın.
user541686

7

Patrick Hofman harika bir çözüm yayınladı . Ancak gerekirse başka yollarla da uygulanabilir. Burada algoritmayı kullanarak başka bir çözümüm var. Dikkatlice uygulanırsa, yavaş donanım bölücülere sahip sistemlerdeki çoklu bölümlerden daha hızlı olabilir. Bilgisayar korsanının zevkinden sabitlere böl tekniği kullanılarak daha da optimize edilebilir.

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 bit platformlarda C / C ++ ile çok daha kolay __int128

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

2
32 bitlik işaretsiz değeri 3'e bölmenin iyi bir yolunun 0x55555555L ile çarpmak, 0x55555555 eklemek ve sağa 32 ile kaydırmak olduğunu öneririm. Bölme3 yönteminiz, kıyaslandığında, birçok ayrı adım gerektirecekmiş gibi görünüyor.
supercat

@supercat evet bu yöntemi biliyorum.
Hacker'ların

"Daha doğru" nun ne anlama geldiğinden emin değilim. Karşılıklı çarpımlar çoğu durumda doğrudan doğru değerler verebilir veya bir veya iki adımda iyileştirilebilen değerler verebilir. BTW, sanırım 0x55555556 ile çarpmayı önermeliydim, bu daha sonra bir "eklemeye" gerek kalmadan kesin sonuçlar verecektir. Ayrıca, döngü durumunuz doğru mu? Döngüde H ve L'yi ne değiştirir?
supercat

Bu arada, birinin bir donanım çarpımı olmasa bile, imzasız bir x=y/3yolu hızla tahmin edebilirsiniz x=y>>2; x+=x>>2; x+=x>>4; x+=x>>8; x+=x>>16; x+=x>>32;. Sonuç, x'e çok yakın olacaktır ve gerektiğinde hesaplama delta=y-x-x-x;ve ayarlama kullanılarak hassas hale getirilebilir x.
supercat

1
@ gnasher729 64x64 → 128 bit çarpma yapamadığı için 32 bit bilgisayarlarda bu optimizasyonu kullanıp kullanamayacağını merak ediyorum
phuclv

7

Toplam kullanmak yerine sayılar arasındaki farklara göre sayıların ortalamasını hesaplayabilirsiniz.

Diyelim ki x maksimum, y medyan, z minimum (sahip olduğunuz gibi). Bunlara max, median ve min diyeceğiz.

@ UlugbekUmirov'un yorumuna göre koşullu denetleyici eklendi:

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'un yorumuna bakınız: Uzun.MinValue ve long.MaxValue değerlerine sahipsem çalışmazdım
Bolu

@Bolu yorum sadece long.MinValue için geçerlidir. Bu yüzden, davamız için çalışmasını sağlamak için bu şartı ekledim.
La-comadreja

Medyanı sıfırlanmamışken nasıl kullanabilirsiniz?
phuclv

@ LưuVĩnhPhúc, medyan, minimum ve maksimum arasındaki değerdir.
La-comadreja

1
(double)(2 / 3)0.0'a eşit değil mi?
phuclv

5

C, Öklid bölünmesi yerine tabana bölünmüş bölme kullandığından, üç işaretli değere göre düzgün yuvarlanmış üç işaretsiz değer ortalamasını hesaplamak daha kolay olabilir. İşaretsiz ortalamayı almadan önce her sayıya 0x8000000000000000UL ekleyin, sonucu aldıktan sonra çıkarın ve işaretli Int64bir ortalama elde etmek için işaretlenmemiş bir geri dönüşü kullanın .

İşaretsiz ortalamayı hesaplamak için, üç değerin ilk 32 bitinin toplamını hesaplayın. Ardından, üç değerin alttaki 32 bitinin toplamını, artı yukarıdan gelen toplamı, artı bir [artı bir, yuvarlanmış bir sonuç verir] hesaplayın. Ortalama, ilk toplamın 0x55555555 katı artı saniyenin üçte biri olacaktır.

32 bit işlemcilerdeki performans, her biri 32 bit uzunluğunda olan üç "toplam" değer üreterek geliştirilebilir, böylece nihai sonuç şu ((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3şekildedir; İkincisi, JIT optimize ediciye bağlı olsa da, sumL/3ile değiştirilerek daha da geliştirilebilir ((sumL * 0x55555556UL) >> 32)[bir bölmeyi 3 ile bir çarpma ile nasıl değiştireceğini bilir ve kodu aslında açık bir çarpma işleminden daha verimli olabilir].


0x8000000000000000UL ekledikten sonra taşma sonucu etkilemez mi?
phuclv

@ LưuVĩnhPhúc Taşma yok. Git Cevabıma bir uygulama için. Yine de 2 32 bit int'e bölme gereksizdi.
KevinZ

@KevinZ: Her bir değeri 32 bitlik bir üst ve alt bölüme bölmek, onu üçe bölün ve kalan bölüme bölmekten daha hızlıdır.
supercat

1
@ LưuVĩnhPhúc: Anlamsal olarak sayılar gibi davranan ve meşru bir C programında taşmalarına izin verilmeyen işaretli değerlerin aksine, işaretsiz değerler genellikle sarmalayan soyut cebirsel halkanın üyeleri gibi davranır, bu nedenle sarma anlambilimleri iyi tanımlanmıştır.
supercat

1
Demet -3, -2, -1'i temsil eder. Her bir değere 0x8000U ekledikten sonra değerler ikiye bölünmelidir: 7F + FF 7F + FE 7F + FD. Üst ve alt yarıları toplayarak 17D + 2FA elde edin. Üst yarı toplamını, 477 verecek şekilde alt yarı toplamına ekleyin. 17D'yi 55 ile çarpın ve 7E81'i verin. 477'yi üçe bölün ve 17D elde edin. 7FFE'yi verecek şekilde 7E81'i 17D'ye ekleyin. Bundan 8000 çıkar ve -2 elde et.
supercat

5

Yama Patrick Hofman 'ın birlikte çözüm SuperCat ' ın düzeltme, sana şu verin:

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

Ve N eleman durumu:

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

Bu her zaman ortalamanın tabanını () verir ve olası her uç durumu ortadan kaldırır.


1
AvgN'yi Z3 koduna çevirdim ve bunun tüm makul girdi boyutları için doğru olduğunu kanıtladım (örn. 1 <= args.Length <= 5 ve bitvector boyutu 6). Bu cevap doğrudur.
usr

Harika cevap Kevin. Katkınız için teşekkürler! meta.stackoverflow.com/a/303292/993547
Patrick Hofman

4

Sayıların her birini sabit y = ax + bnerede diye yazabileceğiniz gerçeğini kullanabilirsiniz x. Her aolacaktır y / x(bu bölümün tam sayı bölümü). Her bir b y % x(bu bölümün geri kalanı / modülü) olacaktır. Bu sabiti akıllıca seçerseniz, örneğin maksimum sayının karekökünü sabit olarak seçerseniz, xtaşma problemi yaşamadan sayıların ortalamasını elde edebilirsiniz .

Rastgele bir sayı listesinin ortalaması şu şekilde bulunabilir:

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

burada %modulo ve /bölmenin 'bütün' kısmını belirtir.

Program şunun gibi görünecektir:

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 değerine sahip olduğunuzu biliyorsanız, her bir değeri N'ye bölüp bunları bir araya toplayabilir misiniz?

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

Bu değilse, sadece daha az doğru Patrick Hofman çözümüyle aynıdır son hali olduğunu
phuclv

2

Ben de denedim ve daha hızlı bir çözüm buldum (ancak yaklaşık 3/4 faktörle). Tek bir bölüm kullanır

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çarpma kullanarak 3'e bölme ve sadece küçük argümanlar için çalışma nerede

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

İşte bir test ve bir kıyaslama içeren kodun tamamı , sonuçlar o kadar etkileyici değil.


1

Bu fonksiyon sonucu iki bölümde hesaplar. Diğer bölenlere ve kelime boyutlarına güzel bir şekilde genellemelidir.

Çift sözcük toplama sonucunu hesaplayarak ve ardından bölme üzerinde çalışarak çalışır.

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

Matematik

(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

Kod

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}yanıt seti için 2, ancak kodunuz geri dönecektir 1.
Ulugbek Umirov

@UlugbekUmirov kodu düzeltildi, işlem için çift tür kullanılmalı
Khaled.K

1
Kaçınmak istediğim şey bu - kullanımı double, çünkü böyle bir durumda hassasiyeti kaybedeceğiz.
Ulugbek Umirov

0

Bunu dene:

long n = Array.ConvertAll(new[]{x,y,z},v=>v/3).Sum()
     +  (Array.ConvertAll(new[]{x,y,z},v=>v%3).Sum() / 3);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.