Среднее число из 3 длинных целых чисел

У меня есть 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

Примечание. Я прочитал все эти вопросы о среднем количестве двух чисел, но я не вижу, как этот метод может быть применен к среднему из трех чисел.

Было бы очень легко с использованием 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

Вопрос: Есть ли способ вычислить усеченное среднее из 3 очень больших целых чисел только с использованием типа long? Не рассматривайте этот вопрос как С# -специфичный, просто мне легче предоставить образцы в С#.

Ответ 1

Этот код будет работать, но это не так.

Сначала он делит все три значения (он накладывает значения, поэтому вы теряете "остаток" ), а затем делит остаток:

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

Обратите внимание, что приведенный выше пример не всегда работает должным образом при наличии одного или нескольких отрицательных значений.

Как обсуждалось с Улугбеком, поскольку число комментариев взломается ниже, вот текущее решение BEST как для положительных, так и для отрицательных значений.

Благодаря ответам и комментариям Улугбек Умиров, Джеймс С., KevinZ, Марк Ван Леувен, 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);
}

Ответ 2

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;

Ответ 3

Патрик Хофман опубликовал отличное решение. Но при необходимости он может быть реализован несколькими другими способами. Использование алгоритма здесь У меня есть другое решение. Если он будет тщательно реализован, он может быть быстрее, чем несколько делений в системах с медленными аппаратными делителями. Его можно дополнительно оптимизировать, используя делить на константы технику из хакерского восторга

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;

В C/С++ на 64-битных платформах это намного проще с __int128

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

Ответ 4

Вы можете вычислить среднее значение чисел, основанное на различиях между числами, а не на использовании суммы.

Пусть говорят, что x - max, y - медиана, z - min (как у вас есть). Назовем их max, медианными и минимальными.

Условная проверка добавлена ​​в соответствии с комментарием @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));
}

Ответ 5

Поскольку C использует разделение на основе пола, а не эвклидовое подразделение, легче вычислить правильно округленное среднее из трех значений без знака, чем три подписанных. Просто добавьте 0x8000000000000000UL к каждому номеру, прежде чем принимать значение без знака, вычтите его после принятия результата и используйте непроверенный откат до Int64, чтобы получить среднее значение.

Чтобы вычислить значение беззнакового среднего, вычислите сумму верхних 32 бит трех значений. Затем вычислите сумму нижних 32 битов трех значений плюс сумму сверху, плюс одну [плюс, чтобы получить округленный результат]. Среднее будет 0x55555555 умножить на первую сумму плюс одну треть второго.

Производительность на 32-битных процессорах может быть увеличена за счет создания трех "суммарных" значений, каждая из которых имеет длину 32 бита, так что конечный результат равен ((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3; возможно, это будет еще больше улучшено путем замены sumL/3 на ((sumL * 0x55555556UL) >> 32), хотя последнее будет зависеть от оптимизатора JIT [он может знать, как заменить деление на 3 с помощью умножения, и его код может быть фактически более эффективным, чем явная операция умножения].

Ответ 6

Можно использовать тот факт, что вы можете записать каждое из чисел как y = ax + b, где x является константой. Каждый a будет y / x (целая часть этого деления). Каждое b будет y % x (остальное/по модулю этого деления). Если вы выберете эту константу разумным способом, например, выбрав квадратный корень из максимального числа в качестве константы, вы можете получить среднее число x без проблем с переполнением.

Среднее значение произвольного списка чисел можно найти, найдя:

( ( sum( all A ) / length ) * constant ) + 
( ( sum( all A ) % length ) * constant / length) +
( ( sum( all B ) / 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 );
    }
}

Ответ 7

Исправление 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 ) );
}

Это всегда дает слово() среднего значения и исключает все возможные случаи ребер.

Ответ 8

Если вы знаете, что у вас есть 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);
}

Ответ 9

Эта функция вычисляет результат в двух делениях. Он должен хорошо делиться на другие делители и размеры слов.

Он работает, вычисляя результат сложения с двойным словом, а затем разрабатывая разделение.

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

Math

(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

Я также попробовал это и придумал более быстрое решение (хотя только в 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;
}

Вот весь код, включая тест и контрольную отметку, результаты не впечатляют.

Ответ 12

Попробуйте следующее:

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