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#でサンプルを提供する方が簡単です。
このコードは機能しますが、それほどきれいではありません。
最初に3つの値すべてを分割し(値をフロアリングするため、残りを「失う」)、次に残りを分割します。
long n = x / 3
+ y / 3
+ z / 3
+ ( x % 3
+ y % 3
+ z % 3
) / 3
1つ以上の負の値がある場合、上記のサンプルが常に適切に機能するとは限らないことに注意してください。
Ulugbekで説明したように、コメントの数が爆発的に増えているため、正と負の両方の値に対する現在のベストソリューションを以下に示します。
lugbek 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);
}
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;
合計を使用するのではなく、数値間の差に基づいて数値の平均を計算できます。
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));
}
パトリック・ホフマンは 素晴らしい解決策を投稿 を持っています。ただし、必要に応じて他のいくつかの方法で実装できます。アルゴリズムの使用 here 別の解決策があります。慎重に実装すると、ハードウェア除数が遅いシステムの複数の部門よりも高速になる可能性があります。 定数で割る ハッカーの喜びのテクニックを使用して、さらに最適化することができます
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;
Cはユークリッド除算ではなくフロア除算を使用するため、3つの符号付きの値よりも適切に丸められた3つの符号なしの値の平均を計算する方が簡単な場合があります。符号なしの平均をとる前に各数値に0x8000000000000000ULを追加し、結果をとった後にそれを減算し、Int64
への未チェックのキャストを使用して符号付きの平均を取得します。
符号なし平均を計算するには、3つの値の上位32ビットの合計を計算します。次に、3つの値の下位32ビットの合計、上からの合計、プラス1を計算します[プラス1は、丸められた結果を生成します]。平均は、最初の合計の0x55555555倍に2番目の合計の3分の1を加えたものになります。
32ビットプロセッサのパフォーマンスは、それぞれが32ビット長である3つの「合計」値を生成することにより強化され、最終結果は((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3
になります。 sumL/3
を((sumL * 0x55555556UL) >> 32)
に置き換えることでさらに強化される可能性がありますが、後者はJITオプティマイザーに依存します[除算を3で乗算する方法を知っているかもしれません。明示的な乗算演算よりも効率的です]。
パッチの適用 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()が得られ、考えられるすべてのEdgeケースが排除されます。
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);
}
各番号を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 );
}
}
私も試してみて、より高速なソリューションを考え出しました(ただし、約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;
}
この関数は、2つの除算で結果を計算します。他の約数とWordサイズにうまく一般化する必要があります。
ダブルワード加算結果を計算し、除算を実行することで機能します。
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);