Comment puis-je sans risque durée moyenne de deux entiers non signés en C++?
À l'aide de math entier à lui seul, que je voudrais en "toute sécurité" moyenne deux entiers non signés en C++.
Ce que je veux dire par "en toute sécurité" est d'éviter les débordements (et tout ce qui peut être pensé).
Par exemple, avec une moyenne de 200 et 5000, c'est simple:
unsigned int a = 200;
unsigned int b = 5000;
unsigned int average = (a + b) / 2; //Equals: 2600 as intended
Mais dans le cas de 4294967295 et 5000 puis:
unsigned int a = 4294967295;
unsigned int b = 5000;
unsigned int average = (a + b) / 2; //Equals: 2499 instead of 2147486147
Le meilleur que j'ai trouvé est:
unsigned int a = 4294967295;
unsigned int b = 5000;
unsigned int average = (a / 2) + (b / 2); //Equals: 2147486147 as expected
Sont t-il de meilleures façons de faire?
- Ne pouvez-vous pas jeté la somme de
long long
? - La troisième option de donner la mauvaise réponse, si a et b sont impairs (puisqu'il va s'arrondir vers le bas les deux moitiés).
- Numéro de brevet AMÉRICAIN 6,007,232. Le calcul de la moyenne de deux nombres entiers arrondi vers zéro en un seul cycle d'instruction: google.com/patents?id=eAIYAAAAEBAJ&dq=6007232 utilise essentiellement
return (a >> 1) + (b >> 1) + (a & b & 0x1);
- ...wow. Je suis sauver ce lien pour la prochaine fois que quelqu'un se plaint à propos des brevets logiciels.
- il est intéressant de voir comment beaucoup de réponses ci-dessous contiennent cette solution brevetée. Je suis sûr que la plupart d'entre eux n'a développé de façon indépendante, peut-être même sur place pour leur réponse. Qui semble indiquer que le brevet ne répond pas à la norme de non-évidence.
- c'est un matériel des brevets (notez que le résultat est produit en un seul cycle d'horloge)
- Je ne suis pas sûr que ce soit une vraie distinction. Le code @ArunSaha a écrit fera le CPU devenir le circuit décrit dans le brevet. Il peut même travailler dans un cycle d'instruction sur un x86, mais je ne suis pas certain. Peu importe, que le code C++ peut être trivialement changé dans le code VHDL, et puis c'est matériel...
- York: dites-vous ops réponse ne marche pas? il sait. Si votre parler ArunSaha commentaire ou sellibitze réponse, puis vous avez oublié le
+ (a & b & 0x1)
partie.
Vous devez vous connecter pour publier un commentaire.
Votre dernière approche semble prometteuse. Vous pouvez améliorer manuellement compte tenu de la plus faible de bits de a et de b:
Cela donne des résultats corrects dans le cas où a et b sont impairs.
MODIFIER
Voici un article connexe: http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
high - low
être signé, donc cela peut facilement overlow de la même manière que dans le problème original. vous pouvez l'éviter, seule la prise en compte de cette différence, non signé, de sorte que vous avez à savoir laquelle est la plus grande.int
est la même que la taille du pointeur, donc il faut une machine spéciale pour ce genre de débordement, avec un énorme espace d'adressage et les petits entiers.int
seront toujours en 32 bits. Veuillez lire l'article attentivement avant de prendre une forte remarques à ce sujet.Votre méthode n'est pas correcte si les deux nombres sont impairs, par exemple 5 et 7, la moyenne est de 6 mais votre méthode #3 renvoie 5.
Essayez ceci:
avec les mathématiques seuls opérateurs:
(a >> (1 + b) >> (1 + a)) & b & 1
. (Votre deuxième exemple est correct, cependant).Si vous n'avez pas l'esprit un peu x86 assembly en ligne (GNU C syntaxe), vous pouvez profiter de supercat la suggestion d'utiliser tourner-à-porter après un complément de mettre le haut de 32 bits de l'intégralité de 33 bits résultat dans un registre.
Bien sûr, vous avez généralement devrait esprit à l'aide inline-asm, car il va à l'encontre de certaines optimisations (https://gcc.gnu.org/wiki/DontUseInlineAsm). Mais ici nous allons de toute façon:
La
%
modificateur pour indiquer au compilateur les arguments sont commutative ne fait pas les aider à mieux asm dans le cas que j'ai essayé, l'appel de la fonction avec y étant une constante ou un pointeur-deref (mémoire opérande). Probablement à l'aide d'une correspondance de contrainte pour une sortie opérande défaites que, puisque vous ne pouvez pas l'utiliser avec d'écriture-lecture des opérandes.Comme vous pouvez le voir sur le Godbolt compilateur explorer, cette compile correctement, et de ce fait une version où nous changer les opérandes de
unsigned long
, avec la même asm inline. clang3.9 fait un gâchis, même si, et décide d'utiliser la"m"
option pour le"rme"
contrainte, de sorte qu'il stocke en mémoire et utilise une mémoire opérande.RCR-en-un n'est pas trop lent, mais c'est toujours 3 uop sur Skylake, avec 2 cycle de latence. C'est génial sur les Processeurs AMD, où RCR à un seul cycle de latence. (Source: Agner le Brouillard de l'instruction tables, voir aussi le x86 la balise wiki pour x86 performance des liens). C'est toujours mieux que @sellibitze version, mais pire que @Sheldon est dépendant de l'ordre de version. (Voir code sur Godbolt)
Mais n'oubliez pas que inline-asm défaites optimisations comme la constante de propagation, de sorte que toute pure-C++ version sera mieux dans ce cas.
x
ety
sont ramassés.cdecl
(la valeur par défaut pour le C et le non-membre de fonctions C++), ce qui, vous voudrez peut-être regarder si vous voulez plus d'informations.add %eax, %eax; rcr %eax
serait valide).x = foo();
avant l'asm déclaration, compiler pour 32-bits, et d'optimiser la avec-O3 et vous devriez le voir à l'aide de lax
déjà dans EAX comme le[y]
/[res]
opérande.Et la bonne réponse est...
Ce que vous avez est très bien, avec le petit détail qu'il va prétendre que la moyenne des 3 et 3 est 2. Je devine que vous ne voulez pas que, heureusement, il y a une solution facile:
- Ce juste des bosses de la moyenne dans le cas que les deux divisions ont été tronqués.
Si le code est pour un micro intégré, et si la vitesse est critique, langage d'assemblage peuvent être utiles. Sur de nombreux microcontrôleurs, le résultat de l'ajouter naturellement aller dans le porte drapeau, et les instructions existent pour le déplacer de nouveau dans un registre. Sur un BRAS, l'exploitation moyenne (source et dest. dans les registres) peut être effectué de deux instructions; C-équivalent en langue produira probablement au moins 5, et probablement un peu juste plus que cela.
D'ailleurs, sur les machines avec des temps de parole tailles, les différences peuvent être encore plus importants. Sur un 8-bit PIC-18 de la série, avec une moyenne de deux nombres de 32 bits prendrait douze instructions. Faire les changements, d'ajouter et de correction, faudrait 5 instructions pour chaque quart de travail, huit pour l'ajouter, et huit pour la correction, donc 26 (pas assez de 2,5 x différence, mais probablement plus important en termes absolus).
De la dernière approche
ne fonctionne pas, parfois, à cause des erreurs d'arrondi.
(((a&b << 1) + (a^b)) >> 1)
est aussi une belle façon.Courtoisie: http://www.ragestorm.net/blogs/?p=29
(a&b)+((a^b)>>1)
.attend avg == 5.0 pour ce test