J'ai 3 très grands entiers signés.
long x = long.MaxValue;
long y = long.MaxValue - 1;
long z = long.MaxValue - 2;
Je veux calculer leur moyenne tronquée. La valeur moyenne attendue est long.MaxValue - 1
, ce qui est 9223372036854775806
.
Il est impossible de le calculer comme:
long avg = (x + y + z) / 3; // 3074457345618258600
Remarque: j'ai lu toutes ces questions sur la moyenne de 2 nombres, mais je ne vois pas comment cette technique peut être appliquée à la moyenne de 3 nombres.
Ce serait très facile avec l'utilisation de BigInteger
, mais supposons que je ne puisse pas l'utiliser.
BigInteger bx = new BigInteger(x);
BigInteger by = new BigInteger(y);
BigInteger bz = new BigInteger(z);
BigInteger bavg = (bx + by + bz) / 3; // 9223372036854775806
Si je me convertis en double
, alors, bien sûr, je perds la précision:
double dx = x;
double dy = y;
double dz = z;
double davg = (dx + dy + dz) / 3; // 9223372036854780000
Si je me convertis en decimal
, cela fonctionne, mais supposons également que je ne puisse pas l'utiliser.
decimal mx = x;
decimal my = y;
decimal mz = z;
decimal mavg = (mx + my + mz) / 3; // 9223372036854775806
Question: Existe - t-il un moyen de calculer la moyenne tronquée de 3 très grands entiers uniquement avec l'utilisation de long
type? Ne considérez pas cette question comme spécifique à C #, mais il m'est plus facile de fournir des exemples en C #.
la source
long.MinValue
etlong.MaxValue
parmi les valeurs.BigInteger
oudecimal
est exclue, ou est-ce simplement pour rendre cela difficile?Réponses:
Ce code fonctionnera, mais n'est pas si joli.
Il divise d'abord les trois valeurs (il nivelle les valeurs, vous «perdez» donc le reste), puis divise le reste:
Notez que l'exemple ci-dessus ne fonctionne pas toujours correctement lorsqu'il a une ou plusieurs valeurs négatives.
Comme discuté avec Ulugbek, comme le nombre de commentaires explose ci-dessous, voici la meilleure solution actuelle pour les valeurs positives et négatives.
Grâce aux réponses et aux commentaires d' Ulugbek Umirov , James S , KevinZ , Marc van Leeuwen , gnasher729 , voici la solution actuelle:
la source
(x + y + z) / 3 = x / 3 + y / 3 + z / 3
.f(1,1,2) == 1
whilef(-2,-2,8) == 2
(x+y)/3
ce qui est trop.NB - Patrick a déjà donné une excellente réponse . En développant là-dessus, vous pouvez créer une version générique pour n'importe quel nombre d'entiers comme ceci:
la source
long
, mais pour les types plus petits, notez que la deuxième somme peut déborder.Patrick Hofman a publié une excellente solution . Mais si nécessaire, il peut toujours être mis en œuvre de plusieurs autres manières. En utilisant l'algorithme ici, j'ai une autre solution. S'il est mis en œuvre avec soin, il peut être plus rapide que les multiples divisions dans les systèmes avec des diviseurs matériels lents. Il peut être encore optimisé en utilisant la technique de division par constantes du plaisir des hackers
En C / C ++ sur les plates-formes 64 bits, c'est beaucoup plus facile avec
__int128
la source
x=y/3
via non signéx=y>>2; x+=x>>2; x+=x>>4; x+=x>>8; x+=x>>16; x+=x>>32;
. Le résultat sera très proche de x, et peut être rendu précis en calculantdelta=y-x-x-x;
et en utilisant des ajustementsx
si nécessaire.Vous pouvez calculer la moyenne des nombres en fonction des différences entre les nombres plutôt qu'en utilisant la somme.
Disons que x est le maximum, y est la médiane, z est le minimum (comme vous l'avez fait). Nous les appellerons max, médian et min.
Vérificateur conditionnel ajouté selon le commentaire de @ UlugbekUmirov:
la source
(double)(2 / 3)
égal à 0,0?Étant donné que C utilise la division par étage plutôt que la division euclidienne, il peut être plus facile de calculer une moyenne correctement arrondie de trois valeurs non signées que de trois signées. Ajoutez simplement 0x8000000000000000UL à chaque nombre avant de prendre la moyenne non signée, soustrayez-la après avoir pris le résultat et utilisez une conversion non cochée vers
Int64
pour obtenir une moyenne signée.Pour calculer la moyenne non signée, calculez la somme des 32 premiers bits des trois valeurs. Ensuite, calculez la somme des 32 bits inférieurs des trois valeurs, plus la somme d'en haut, plus un [le plus un donne un résultat arrondi]. La moyenne sera 0x55555555 fois la première somme, plus un tiers de la seconde.
Les performances sur les processeurs 32 bits peuvent être améliorées en produisant trois valeurs "somme" dont chacune est longue de 32 bits, de sorte que le résultat final est
((0x55555555UL * sumX)<<32) + 0x55555555UL * sumH + sumL/3
; il pourrait éventuellement être amélioré en remplaçantsumL/3
par((sumL * 0x55555556UL) >> 32)
, bien que ce dernier dépende de l'optimiseur JIT [il pourrait savoir comment remplacer une division par 3 par une multiplication, et son code pourrait en fait être plus efficace qu'une opération de multiplication explicite].la source
Patcher Patrick Hofman 'solution s avec supercat ' correction de, je vous donne les éléments suivants:
Et le cas des N éléments:
Cela donne toujours le plancher () de la moyenne et élimine tous les cas de bord possibles.
la source
Vous pouvez utiliser le fait que vous pouvez écrire chacun des nombres comme
y = ax + b
, oùx
est une constante. Chacuna
seraity / x
(la partie entière de cette division). Chaque b seraity % x
(le reste / modulo de cette division). Si vous choisissez cette constante de manière intelligente, par exemple en choisissant la racine carrée du nombre maximum comme constante, vous pouvez obtenir la moyenne desx
nombres sans avoir de problèmes de débordement.La moyenne d'une liste arbitraire de nombres peut être trouvée en trouvant:
où
%
désigne modulo et/
désigne la partie «entière» de la division.Le programme ressemblerait à quelque chose comme:
la source
Si vous savez que vous avez N valeurs, pouvez-vous simplement diviser chaque valeur par N et les additionner?
la source
Je l'ai également essayé et j'ai trouvé une solution plus rapide (bien que seulement d'un facteur 3/4). Il utilise une seule division
où
smallDiv3
est la division par 3 en utilisant la multiplication et en travaillant uniquement pour les petits argumentsVoici l' ensemble du code comprenant un test et un benchmark, les résultats ne sont pas si impressionnants.
la source
Cette fonction calcule le résultat en deux divisions. Il devrait bien se généraliser à d'autres diviseurs et tailles de mots.
Il fonctionne en calculant le résultat de l'addition de deux mots, puis en élaborant la division.
la source
Math
Code
la source
{1,2,3}
la réponse est2
, mais votre code reviendra1
.double
, car nous allons perdre de la précision dans un tel cas.Essaye ça:
la source