В среднем 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
Примечание: я прочитал все эти вопросы о среднем из 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
вопрос: есть ли способ вычислить усеченное среднее из 3 очень больших целых чисел только с использованием long тип? Не рассматривайте этот вопрос как специфичный для C#, просто мне легче предоставить образцы в C#.
12 ответов:
этот код будет работать, но не так красиво.
Он сначала делит все три значения (он выравнивает значения, поэтому вы "теряете" остаток), а затем делит остаток:
long n = x / 3 + y / 3 + z / 3 + ( x % 3 + y % 3 + z % 3 ) / 3обратите внимание, что приведенный выше пример не всегда работает должным образом при наличии одного или нескольких отрицательных значений.
Как обсуждалось с Улугбеком, поскольку количество комментариев взрывается ниже, вот текущее лучшее решение как для положительных, так и для отрицательных ценности.
Спасибо за ответы и комментарии Улугбек Умиров,James S,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); }
NB-Патрик уже дал отличный ответ!--3-->. Расширяя это, вы можете сделать общую версию для любого количества целых чисел, например:
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;
Патрик Хофман имеет опубликовано отличным решением. Но при необходимости он все еще может быть реализован несколькими другими способами. Используя алгоритм здесь у меня есть другое решение. При тщательной реализации это может быть быстрее, чем несколько подразделений в системах с медленными аппаратными делителями. Он может быть дополнительно оптимизирован с помощью деление на константы техника от восторга хакера
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 / C++ на 64-битных платформах это намного проще
__int128int64_t average = ((__int128)x + y + z)/3;
вы можете рассчитать среднее число чисел на основе различий между числами, а не с помощью суммы.
скажем, 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)); }
поскольку C использует напольное деление, а не евклидово деление, может быть проще вычислить правильно округленное среднее из трех беззнаковых значений, чем три подписанных. Просто добавьте 0x8000000000000000UL к каждому числу, прежде чем принимать среднее значение без знака, вычитайте его после получения результата и используйте непроверенное приведение к
Int64чтобы получить среднее значение со знаком.чтобы вычислить среднее значение без знака, вычислите сумму верхних 32 бит из трех значений. Затем вычислить сумму нижние 32 бита из трех значений, плюс сумма сверху, плюс один, плюс один, чтобы получить округленный результат]. Среднее значение будет 0x55555555 раз больше первой суммы, плюс одна треть второй.
производительность на 32-разрядных процессорах может быть повышена путем получения трех значений "суммы", каждое из которых имеет длину 32 бита, так что конечный результат
((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3; это может быть дополнительно улучшено путем заменыsumL/3С((sumL * 0x55555556UL) >> 32), хотя последнее будет зависеть от JIT-оптимизатора [он может знать, как заменить деление на 3 с умножением, и его код может быть на самом деле более эффективным, чем явная операция умножения].
вы можете использовать тот факт, что вы можете написать каждое из чисел как
y = ax + b, гдеx- константа. Каждый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 ); } }
исправления Патрик ХофманС 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 ) ); }это всегда дает пол () среднего и устраняет все возможные крайние случаи.
Если вы знаете, что у вас есть 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); }
Я также попробовал его и придумал более быстрое решение (хотя только в 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; }здесь код, включая тест и бенчмарк, то результаты не так уж и впечатляет.
эта функция вычисляет результат в двух дивизионах. Он должен хорошо обобщаться на другие делители и размеры слов.
Он работает путем вычисления результата сложения двух слов, а затем разработки деления.
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; }
мат
(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)); }
попробуйте это:
long n = Array.ConvertAll(new[]{x,y,z},v=>v/3).Sum() + (Array.ConvertAll(new[]{x,y,z},v=>v%3).Sum() / 3);
Comments