web-dev-qa-db-fra.com

Multiplication de deux entiers en C++

J'ai une question assez fondamentale, mais je ne suis pas sûr de comprendre le concept ou non. Supposons que nous ayons:

int a = 1000000;
int b = 1000000;
long long c = a * b;

Lorsque j’exécute ceci, c affiche une valeur négative, j’ai donc également changé a et b en long long et tout s’est bien passé. Alors pourquoi dois-je changer a et b, lorsque leurs valeurs sont comprises dans la plage int et que leur produit est attribué à c (qui est long long)?

J'utilise C/C++

12
essa

Les ints ne sont pas promus en long long avant multiplication, ils restent ints et le produit également. Le produit est alors converti en long long, mais trop tard, un débordement a été détecté.

Avoir l’un des a ou blong long devrait également fonctionner, car l’autre serait promu.

18
Yves Daoust

Pour les opérateurs arithmétiques, le type du résultat ne dépend pas de ce à quoi vous assignez le résultat, mais des types d'opérandes. Pour les opérateurs arithmétiques, les conversions arithmétiques usuelles sont effectuées sur les opérandes. Ceci est utilisé pour amener les opérandes à un type commun, cela signifie pour les types plus petits que unsigned / signed int si les valeurs peuvent être ajustées, elles sont promues à unsigned / signed int, dans ce cas, ils sont déjà tous les deux int, aucune conversion n'est donc requise. Voir Pourquoi un court-métrage doit-il être converti en un int avant les opérations arithmétiques en C et C++? pour les détails sur pourquoi.

Nous avons maintenant un comportement indéfini, car le dépassement d’entier signé est un comportement indéfini. C’est couvert dans le projet de section standard C++ 5 [Expr] qui dit:

Si, lors de l'évaluation d'une expression, le résultat n'est pas défini mathématiquement ou s'il ne se situe pas dans la plage de valeurs pouvant être représentées par , Le comportement n'est pas défini. [Remarque: la plupart des implémentations existantes de C++ Ignorent les débordements d'entiers. Le traitement de la division par zéro, formant un reste à l’aide d’un diviseur égal à zéro, et toutes les Exceptions en virgule flottante, varie d’une machine à l’autre et peut généralement être réglé à l’aide d’une fonction de bibliothèque. —Fin note]

De nos jours, nous avons des désinfectants pour attraper ces types de comportement indéfinis et utiliser -fsanitize=undefined avec clang et gcc interceptera cela au moment de l'exécution avec l'erreur suivante (le voir en direct):

erreur d'exécution: dépassement d'entier signé: 1000000 * mais 1000000 ne peuvent pas être représenté dans le type 'int'

Pour la section de référence 5.6 [expr.mul] dit:

[...] Les conversions arithmétiques habituelles sont effectuées sur les opérandes Et déterminent le type du résultat.

et la section 5 dit:

Sinon, les promotions intégrales (4.5) doivent être effectuées sur les deux opérandes.61 Ensuite, les règles Suivantes doivent être appliquées aux opérandes promus.

  • Si les deux opérandes ont le même type, aucune conversion supplémentaire n'est nécessaire.
2
Shafik Yaghmour

C'est un peu absurde, parce que l'instruction assembler calcule toujours 

int * int -> 64 bits de long 

si vous regardez le code machine, vous voyez: imul qui stocke 64 bits dans eax edx puis cdq qui a placé le signe bit de eax dans edx (perdant ainsi le résultat complet de 64 bits) puis eax edx sont stockés dans la variable de 64 bits

et si vous convertissez les valeurs 32 bits en 64 bits avant la multiplication, vous obtenez un appel à la fonction de multiplication 64 bits sans raison. 

(J'ai vérifié: ce n'est pas le cas lorsque le code est optimisé)

0
Edgard Neuman