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

참으로 좋은 캐치 :)
Andreas Niedermair

우리가 이것에 대해 걱정할 필요가 있다고 확신합니까? 이것은 프레임 워크에 의해 처리되어야하지 않습니까?
Bolu

11
배제 BigInteger되거나 decimal배제 되는 실제 이유가 있습니까? 아니면 이것을 어렵게 만들기 위해서입니까?
jpmc26

답변:


142

이 코드는 작동하지만 그렇게 예쁘지는 않습니다.

먼저 세 가지 값을 모두 나눈 다음 (값을 내림 처리하므로 나머지는 '잃어 버립니다') 나머지를 나눕니다.

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

위의 샘플은 하나 이상의 음수 값이있을 때 항상 제대로 작동하지 않습니다.

Ulugbek과 논의한 바와 같이 댓글 수가 아래에서 폭발적으로 증가하고 있으므로 양수 및 음수 값 모두에 대한 현재 BEST 솔루션이 있습니다.

Ulugbek Umirov , James S , KevinZ , Marc van Leeuwen , gnasher729 의 답변과 의견 덕분에 이것이 현재 솔루션입니다.

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) == 1동안f(-2,-2,8) == 2
KevinZ 2014 년

11
모듈로 연산의 뇌 손상 시맨틱으로 인해 변수에 음수 값이 허용되는 경우 결과가 1 씩 벗어난 결과, 즉 내림이 아닌 반올림 될 수 있습니다. 예를 들어 x, y가 3의 양의 배수이고 z가 -2 (x+y)/3이면 너무 많은 것을 얻 습니다.
Marc van Leeuwen 2014 년

6
@KevinZ : ... 그 효과는 애초에 특별한 경우의 행동을 원하지 않는 프로그래머에 의해 취소되어야합니다. 컴파일러가 모듈러스에서 파생했을 수있는 나머지에서 파생하지 않고 프로그래머가 모듈러스를 지정하도록하는 것이 도움이 될 것입니다.
supercat 2014 년

26

NB-Patrick은 이미 훌륭한 대답을했습니다 . 이를 확장하면 다음과 같이 정수의 수에 대한 일반 버전을 수행 할 수 있습니다.

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않지만 더 작은 유형의 경우 두 번째 합계가 오버플로 될 수 있습니다.
user541686

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 방법은 많은 이산 단계가 필요한 것처럼 보입니다.
supercat

@ supercat 예, 그 방법을 알고 있습니다. 해커의 기쁨에 의한 방법은 훨씬 더 정확하지만 다음에 구현
하겠습니다

"더 정확하다"가 무슨 뜻인지 잘 모르겠습니다. 역수 곱은 많은 경우에 정확한 값을 직접 산출하거나, 그렇지 않으면 한두 단계로 세분화 할 수있는 값을 산출 할 수 있습니다. BTW, 0x55555556을 곱하면 "추가"없이 정확한 결과를 얻을 수 있다고 제안 했어야한다고 생각합니다. 또한 루프 조건이 정확합니까? 루프에서 H와 L을 수정하는 것은 무엇입니까?
supercat 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.
supercat 2014 년

1
@ gnasher729 64x64 → 128 비트 곱셈을 할 수없는 경우가 많기 때문에 32 비트 컴퓨터에서 최적화를 사용할 수 있는지 궁금합니다
phuclv

7

합계를 사용하는 대신 숫자 간의 차이를 기반으로 숫자의 평균을 계산할 수 있습니다.

x가 최대, y가 중앙값, z가 최소라고 가정 해 보겠습니다. 우리는 그것들을 max, median 및 min이라고 부를 것입니다.

@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

@Bolu 코멘트는 long.MinValue에만 적용됩니다. 그래서 우리의 경우에 작동하도록이 조건을 추가했습니다.
La-comadreja 2014 년

중앙값이 초기화되지 않은 상태에서 어떻게 사용할 수 있습니까?
phuclv

@ LưuVĩnhPhúc, 중앙값은 최소값과 최대 값 사이의 값입니다.
La-comadreja 2014 년

1
하지입니다 (double)(2 / 3)0.0 같다?
phuclv

5

C는 유클리드 나눗셈이 아닌 내림 나눗셈을 사용하기 때문에 세 개의 부호있는 값보다 세 개의 부호없는 값의 적절하게 반올림 된 평균을 계산하는 것이 더 쉬울 수 있습니다. 부호없는 평균을 취하기 전에 각 숫자에 0x8000000000000000UL을 더하고 결과를 취한 후 빼고 확인되지 않은 캐스트 백을 사용하여 Int64부호있는 평균을 얻습니다.

부호없는 평균을 계산하려면 세 값 중 상위 32 비트의 합을 계산합니다. 그런 다음 세 값의 하위 32 비트의 합계와 위에서의 합계, 1을 더한 값을 계산합니다 [더하기 1은 반올림 된 결과를 생성하는 것입니다]. 평균은 0x55555555 곱하기 첫 번째 합계에 1/3을 더한 값입니다.

32 비트 프로세서의 성능은 각각 길이가 32 비트 인 세 개의 "합계"값을 생성하여 향상 될 수 있으므로 최종 결과는 다음과 같습니다 ((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으로 나누는 몫과 나머지로 분할하는 것보다 빠릅니다.
supercat 2014 년

1
@ LưuVĩnhPhúc : 의미 적으로 숫자처럼 동작하고 합법적 인 C 프로그램에서 오버플로가 허용되지 않는 부호있는 값과 달리 부호없는 값은 일반적으로 래핑 추상 대수 링의 구성원처럼 동작하므로 래핑 의미가 잘 정의되어 있습니다.
supercat 2014 년

1
튜플은 -3, -2, -1을 나타냅니다. 각 값에 0x8000U를 추가 한 후 값은 7F + FF 7F + FE 7F + FD로 절반으로 분할되어야합니다. 상단 및 하단 절반을 추가하여 17D + 2FA를 산출합니다. 477을 산출하는 하단 절반의 합에 상단 절반의 합을 더합니다. 17D에 55를 곱하여 7E81을 산출합니다. 477을 3으로 나누면 17D가됩니다. 7E81을 17D에 추가하여 7FFE를 생성합니다. 그것에서 8000을 빼고 -2를 얻습니다.
supercat

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

멋진 대답 Kevin. 귀하의 기여에 감사드립니다! meta.stackoverflow.com/a/303292/993547
Patrick Hofman

4

각 숫자를 다음과 같이 쓸 수 있다는 사실을 사용할 수 있습니다 . y = ax + bwhere xis a constant. 각각 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);
}

이 아니라면, 단지 적은 올바른 패트릭 호프만의 솔루션과 동일한 최종 버전이
phuclv

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

이 함수는 결과를 두 부분으로 계산합니다. 다른 제수와 단어 크기로 잘 일반화되어야합니다.

두 단어 더하기 결과를 계산 한 다음 나누기를 계산하여 작동합니다.

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됩니다.
바카 Umirov

@UlugbekUmirov 코드 수정, 처리를 위해 이중 유형을 사용해야 함
Khaled.K 2014-06-04

1
그게 내가 피하고 싶은 double것입니다.. 이런 경우 정밀도를 잃을 것이기 때문입니다.
Ulugbek Umirov 2014.06.04

0

이 시도:

long n = Array.ConvertAll(new[]{x,y,z},v=>v/3).Sum()
     +  (Array.ConvertAll(new[]{x,y,z},v=>v%3).Sum() / 3);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.