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.MinValue와 long.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. 각각 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 )

여기서는 %모듈로를 /나타내고 나눗셈의 '전체'부분을 나타냅니다.

프로그램은 다음과 같습니다.

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.