Comment les mathématiques fondamentales sont-elles évaluées efficacement par les langages de programmation?

22

Alors que je m'implique de plus en plus dans la théorie derrière la programmation, je me trouve fasciné et abasourdi par des choses apparemment simples. Je me rends compte que ma compréhension de la majorité des processus fondamentaux est justifiée par une logique circulaire

Q : Comment ça marche?

R : Parce que c'est le cas!

Je déteste cette réalisation! J'adore la connaissance, et en plus j'aime apprendre, ce qui m'amène à ma question (même si elle est large).

Question:

Comment les opérateurs mathématiques fondamentaux sont-ils évalués avec les langages de programmation?

Comment les méthodes actuelles ont-elles été améliorées?

Exemple

var = 5 * 5; 

Mon interprétation:

$num1 = 5; $num2 = 5; $num3 = 0;
while ($num2 > 0) {
    $num3 = $num3 + $num1;
    $num2 = $num2 - 1;
}
echo $num3;

Cela semble être très inefficace. Avec des facteurs plus élevés, cette méthode est très lente tandis que la méthode intégrée standard est instantanée. Comment simuleriez-vous la multiplication sans itérer l'addition?

var = 5 / 5;

Comment cela se fait-il? Je ne peux pas penser à un moyen de le diviser littéralement 5 en 5 parties égales.

var = 5 ^ 5; 

Itérations d'itérations d'addition? Mon interprétation:

$base = 5;
$mod = 5;
$num1 = $base;
while ($mod > 1) {

    $num2 = 5; $num3 = 0;
    while ($num2 > 0) {
        $num3 = $num3 + $num1;
        $num2 = $num2 - 1;
    }
    $num1 = $num3;
    $mod -=1;
}
echo $num3;

Encore une fois, c'est extrêmement inefficace, mais je ne peux pas penser à une autre façon de le faire. Cette même question s'étend à toutes les fonctions mathématiques liées qui sont gérées automatiquement.

Korvin Szanto
la source
1
Un peu d'histoire sur moi, je vais au collège pour l'informatique, et plus tard dans la théorie mathématique de la vie ainsi que peut-être la philosophie et la physique théorique. Beaucoup d'aspirations, peu de temps.
Korvin Szanto
10
Peut-on présumer que vous avez consulté tous les liens de en.wikipedia.org/wiki/Category:Computer_arithmetic ?
JB King du
2
Fondamentalement, cela ressemble à la façon dont on vous a enseigné la multiplication à plusieurs chiffres et la division longue à l'école primaire. Prenez un chiffre de A, multipliez par B. Décalez par dix. Prenez le chiffre suivant de A, multipliez par B. Répétez pour tous les chiffres, ajoutez-les tous ensemble. Parce que c'est binaire, la multiplication à un chiffre est plus simple (c'est x0 ou x1) et au lieu de décaler de dix, vous doublez. La division est similaire.
Renseignez-vous sur Monica

Réponses:

35

Pour vraiment comprendre comment fonctionne l'arithmétique à l'intérieur d'un ordinateur, vous devez avoir programmé en langage assembleur. De préférence un avec une petite taille de mot et sans instructions de multiplication et de division. Quelque chose comme le 6502.

Sur le 6502, pratiquement toute l'arithmétique se fait dans un registre appelé l'accumulateur. (Un registre est un emplacement de mémoire spécial à l'intérieur du processeur auquel on peut accéder rapidement.) Donc, pour ajouter deux nombres, vous chargez le premier nombre dans l'accumulateur, puis vous y ajoutez le deuxième.

Mais c'est trop simpliste. Le 6502 étant un processeur 8 bits, il ne peut gérer que des nombres compris entre 0 et 255. La plupart du temps, vous souhaiterez pouvoir travailler avec des nombres plus importants. Vous devez les ajouter en morceaux, 8 bits à la fois. Le processeur a un indicateur Carry qui est défini lorsque le résultat de l'ajout de deux nombres déborde de l'accumulateur. Le processeur ajoute que lors de l'ajout, il peut donc être utilisé pour "porter le 1" en supposant que vous commencez par l'octet de poids faible d'un nombre. Un ajout de plusieurs octets sur le 6502 ressemble à ceci:

  1. Drapeau de transport transparent (CLC)
  2. Charger l'octet de poids faible du premier nombre (LDA, accumulateur de charge)
  3. Ajouter l'octet de poids faible du deuxième numéro (ADC, ajouter avec report)
  4. Stocker l'octet de résultat le plus bas (STA, stocker l'accumulateur)
  5. Répétez les étapes 2 à 4 avec des octets successivement d'ordre supérieur
  6. Si à la fin, le report est réglé, vous avez débordé; prendre les mesures appropriées, telles que générer un message d'erreur (BCS / BCC, branche si report / suppression)

La soustraction est similaire, sauf que vous définissez le report en premier, utilisez l'instruction SBC au lieu d'ADC et, à la fin, le report est clair s'il y a eu un dépassement.

Mais attendez! Et les nombres négatifs? Eh bien, avec le 6502, ceux-ci sont stockés dans un format appelé complément à deux. En supposant un nombre de 8 bits, -1 est stocké en tant que 255, car lorsque vous ajoutez 255 à quelque chose, vous obtenez un de moins dans l'accumulateur (plus un report). -2 est stocké comme 254 et ainsi de suite, jusqu'à -128, qui est stocké comme 128. Ainsi, pour les entiers signés, la moitié de la plage 0-255 d'un octet est utilisée pour les nombres positifs et la moitié pour les nombres négatifs. (Cette convention vous permet de simplement vérifier le bit élevé d'un nombre pour voir s'il est négatif.)

Pensez-y comme une horloge de 24 heures: ajouter 23 à l'heure se traduira par une heure une heure plus tôt (le lendemain). Donc 23 est l'équivalent modulaire de l'horloge à -1.

Lorsque vous utilisez plus d'un octet, vous devez utiliser des nombres plus grands pour les négatifs. Par exemple, les entiers 16 bits ont une plage de 0 à 65536. Ainsi, 65535 est utilisé pour représenter -1, et ainsi de suite, car l'ajout de 65535 à n'importe quel nombre entraîne une de moins (plus un report).

Sur le 6502, il n'y a que quatre opérations arithmétiques: additionner, soustraire, multiplier par deux (décalage vers la gauche) et diviser par deux (décalage vers la droite). La multiplication et la division peuvent être effectuées en utilisant uniquement ces opérations lors du traitement en binaire. Par exemple, envisagez de multiplier 5 (binaire 101) et 3 (binaire 11). Comme pour la multiplication décimale longue, nous commençons par le chiffre droit du multiplicateur et multiplions 101 par 1, donnant 101. Ensuite, nous déplaçons le multiplicande à gauche et multiplions 1010 par 1, donnant 1010. Ensuite, nous additionnons ces résultats ensemble, donnant 1111, ou 15. Puisque nous ne multiplions jamais que par 1 ou 0, nous ne multiplions pas vraiment; chaque bit du multiplicateur sert simplement d' indicateur qui nous indique s'il faut ajouter le multiplicande (décalé) ou non.

La division est analogue à la division longue manuelle utilisant des diviseurs d'essai, sauf en binaire. Si vous divisez par une constante, il est possible de le faire d'une manière analogue à la soustraction: plutôt que de diviser par X, vous multipliez par un rendu précalculé de 1 / X qui produit le résultat souhaité plus un débordement. Aujourd'hui encore, c'est plus rapide que la division.

Essayez maintenant de faire des calculs à virgule flottante dans l'assemblage ou de convertir des nombres à virgule flottante en formats de sortie agréables dans l'assemblage. Et rappelez-vous, nous sommes en 1979 et la vitesse d'horloge est de 1 MHz, vous devez donc le faire le plus efficacement possible.

Les choses fonctionnent toujours à peu près comme ça aujourd'hui, sauf avec des tailles de mots plus grandes et plus de registres, et bien sûr la plupart des calculs sont effectués par le matériel maintenant. Mais cela se fait toujours de la même manière fondamentale. Si vous additionnez le nombre de décalages et les ajouts requis pour une multiplication, par exemple, cela correspond assez bien au nombre de cycles requis pour une instruction de multiplication matérielle sur les premiers processeurs ayant une telle instruction, comme la 6809, où elle a été exécutée en microcode de la même manière que vous le feriez manuellement. (Si vous avez un budget de transistors plus important, il existe des moyens plus rapides d'effectuer les décalages et les ajouts, de sorte que les processeurs modernes n'effectuent pas ces opérations de manière séquentielle et peuvent effectuer des multiplications en aussi peu qu'un seul cycle.)

gentil
la source
3
Hé, merci pour votre explication très détaillée! C'est exactement ce que je voulais! Étant à mon niveau, vous oubliez souvent que ce qui vous soutient est généralement plus complexe que tout ce que vous faites. C'est la raison exacte pour laquelle je veux étudier l'informatique. Je déteste le fait que si je remontais dans le temps, je ne saurais rien changer du monde, juste comment formuler une instruction SQL appropriée;) En tout cas, merci beaucoup d'avoir pris le temps d'écrire cette réponse, vous m'avez donné un testeur de goût dans ce que je vais approfondir.
Korvin Szanto
7
en désaccord, l'assemblage est encore trop élevé, si vous voulez savoir comment les ordinateurs font de l'arithmétique, vous devez regarder le matériel, ou au moins les algorithmes matériels
jk.
Eh. Une fois que vous savez qu'il existe des additionneurs et des sélecteurs, il est aussi facile de les imaginer contrôlés par le matériel que par le logiciel, et il est plus facile de jouer avec le logiciel.
kindall
4
-1. La multiplication matérielle n'a pas été effectuée avec des décalages et des ajouts depuis près de 3 décennies maintenant, et de nombreux processeurs peuvent effectuer une multiplication en un cycle. Consultez l'article Wikipedia sur Binary Multiplierpour les détails.
Mason Wheeler
24

En fin de compte, les opérations arithmétiques de base sont effectuées sur le matériel. Plus précisément, dans le CPU (ou en fait, une sous-partie de celui-ci)

En d'autres termes, ce sont des circuits électroniques. Définissez les bits appropriés en entrée et vous obtiendrez les bits appropriés en sortie. Il s'agit d'une combinaison de portes logiques de base.

http://en.wikipedia.org/wiki/Adder_%28electronics%29

http://en.wikipedia.org/wiki/Binary_multiplier

dagnelies
la source
3
Les algorithmes pour le matériel sont soigneusement spécifiés et peuvent être étudiés séparément du matériel.
S.Lott
@ S.Lott: Je trouve votre commentaire déroutant. Pour moi, les algorithmes impliquent une série d'étapes que vous suivez, une procédure, quelque chose que vous pouvez programmer. Ici, nous parlons de circuits électroniques effectuant les opérations arithmétiques de base. En d'autres termes, juste une séquence de portes où le courant circule. C'est donc plus "logique" qu'algorithmique au mieux. ... mes 2 cents.
dagnelies
6
Un algorithme est "fini, défini et efficace". Il peut être en circuits, ou fait avec du papier et un crayon, ou fait avec des Tinkertoys ou des molécules dans une boîte ou de l'ADN. L'algorithme peut être n'importe quoi. Un circuit électronique doit suivre un algorithme défini. Comme par magie, il n'a pas besoin d'algorithmes.
S.Lott
1
Un processus consistant en une seule étape serait-il considéré comme un "algorithme"? FWIW, les circuits électroniques suivent généralement une table de vérité - un traitement en une seule étape. Le fait que la table de vérité finisse par être «compilée» en portes multicouches ne nie pas le fait qu'il s'agit d'un processus en une seule étape.
slebetman
2
@ S.Lott: Un premier commentaire plus approprié serait: la "logique" du matériel est soigneusement spécifiée et peut être étudiée séparément du matériel. Et c'est effectivement le cas. L'étude de la logique binaire s'appelle l'algèbre booléenne.
slebetman
6

Tout cela est couvert avec une patience approfondie dans L'art de la programmation informatique de Don Knuth.

Des algorithmes efficaces pour additionner, soustraire, multiplier et diviser sont tous décrits en détail.

Vous pouvez lire des choses comme celle-ci qui couvrent bien la division.

http://research.microsoft.com/pubs/151917/divmodnote.pdf

S.Lott
la source
5

Cela se fait en picosecondes par des circuits électroniques. Google «multiplicateur matériel», etc. pour plus de détails. Les processeurs modernes sont le résultat extrêmement complexe de décennies d'amélioration continue.

BTW, Puisque vous ne multipliez pas par des ajouts répétés, pourquoi imaginez-vous qu'un ordinateur le ferait?

Kevin Cline
la source
Ma question concerne le raisonnement derrière les fonctions plutôt que les fonctions elles-mêmes, je comprends que c'est interprété par le processeur, je suis curieux de savoir comment. Plus précisément, la théorie derrière elle et comment elle pourrait être reproduite dans un pseudo-code.
Korvin Szanto
1
La multiplication que je fais dans ma tête est la mémoire. Une longue multiplication nécessite également une itération dans la façon dont je l'ai fait. Je vais aller de l'avant et lancer ensemble une fonction pour une longue multiplication
Korvin Szanto
2
@Korvin, le livre que j'ai recommandé vous sera utile si tel est votre intérêt. Je recommande également "Structure et interprétation des programmes informatiques" par Harold Abelson et Gerald Jay Sussman. Il traite ces questions en profondeur.
Jonathan Henson
Plusieurs premiers ordinateurs ne supportaient que l'addition et la soustraction. Certains ont seulement pris en charge la soustraction! Ainsi, l'opération x = y * z a été implémentée comme le font (z fois) {x + y} de même, la division x = y / z a été implémentée comme tandis que (y> z) {x + 1; y = y - z}
James Anderson
@James: ont-ils soutenu shift? Je m'attendrais à ce que la multiplication se fasse via shift et add, tandis que la division était shift, compare, soustrait.
Kevin Cline
4

Ce n'est pas censé être une réponse approfondie, mais cela devrait vous donner une idée de la façon dont les choses sont mises en œuvre. Comme vous le savez probablement, les nombres sont représentés en binaire. Par exemple, un ordinateur pourrait représenter le nombre 5 comme 00000101. Une opération très basique qu'un ordinateur peut faire est de décaler vers la gauche, ce qui donnerait 00001010 qui est décimal 10. S'il était décalé deux fois vers la droite, ce serait 00010100 (décimal 20). Chaque fois que nous décalons les chiffres vers la gauche 1 fois, nous doublons le nombre. Disons que j'avais un nombre x et que je voulais le multiplier par 17. Je pouvais décaler x vers la gauche 4 fois puis ajouter x au résultat (16x + x = 17x). Ce serait un moyen efficace de multiplier un nombre par 17. Cela devrait vous donner un aperçu de la façon dont un ordinateur peut multiplier de grands nombres sans simplement utiliser l'addition répétée.

La division peut utiliser des combinaisons d'addition, de soustraction, de décalage à droite, de décalage à gauche, etc. Il existe également de nombreuses astuces pour augmenter le nombre d'exposants.

WuHoUnited
la source
Juste pour être clair, vous pouvez généralement décaler de plus d'un bit à la fois. Donc , ces 4 opérations de décalage sont en fait une seule opération, comme: shl r0, 4.
Caleb
4

Quand j'étais enfant, j'ai appris à multiplier et à diviser avec un stylo et un papier, sans perdre de temps avec trop d'ajouts. Plus tard, j'ai appris que les racines carrées sont également calculables de cette façon.

À l'université, j'ai appris à calculer des opérations trigonométriques et logarithmiques avec une douzaine de multiplications, divisions et additions. Ils l'ont appelé la série Taylor.

Avant cela, mon père m'a donné un livre où ces opérations complexes étaient déjà calculées pour des centaines de valeurs et présentées dans des tableaux. Il y avait aussi quelques explications pour estimer l'erreur lorsque vous vouliez le sinus d'une valeur entre deux valeurs calculées.

Les unités entières, les unités à virgule flottante, le GPU et le DSP implémentent simplement toutes ces anciennes techniques sur le silicium.

mouviciel
la source
3

Je vais essayer de vous donner une idée de la façon dont les circuits numériques sont conçus pour résoudre les problèmes de traitement numérique en utilisant les problèmes que vous posez: comment les processeurs implémentent-ils les ajouts et les multiplications.

Tout d'abord, permet de résoudre la question directe: comment un langage de programmation évalue-t-il efficacement les multiplications et les ajouts. La réponse est simple, ils les compilent en multipliant et en ajoutant des instructions. Par exemple, le code suivant:

a = 1 + 1;
b = a * 20;

est simplement compilé en quelque chose comme:

ADD 1 1  a
MUL a 20 b

(notez que l'assemblage ci-dessus est pour un CPU imaginaire qui n'existe pas, par souci de simplicité).

À ce stade, vous vous rendez compte que la réponse ci-dessus déplace simplement le problème et le résout par magie matérielle. La question de suivi est évidemment comment fonctionne cette magie matérielle?

Regardons d'abord le problème le plus simple: l'addition.

D'abord, nous faisons un problème familier, en ajoutant des nombres réguliers de base 10:

 17
+28

La première étape serait d'ajouter 7 et 8. Mais cela donne 15, ce qui est plus qu'un seul chiffre. Nous portons donc le 1:

(1)
 17
+28
= 5

Maintenant, nous ajoutons 1, 1 et 2 ensemble:

 17
+28
=45

Ainsi, nous obtenons les règles suivantes:

  1. lorsque le résultat de l'addition est supérieur à un chiffre, nous conservons le chiffre le moins significatif et reportons le chiffre le plus significatif vers l'avant

  2. si nous avons un chiffre reporté dans notre colonne, nous l'ajoutons avec les chiffres que nous ajoutons

Il est maintenant temps d'interpréter les règles ci-dessus dans la base 2 - l'algèbre booléenne.

Donc, dans l'algèbre booléenne, en ajoutant 0 et 1 ensemble = 1. En ajoutant 0 et 0 = 0. Et en ajoutant 1 et 1 = 10 qui est plus d'un chiffre, nous reportons donc le 1.

À partir de cela, nous pouvons construire une table de vérité:

a b  |  sum  carry
-------------------
0 0  |   0     0
0 1  |   1     0
1 0  |   1     0
1 1  |   0     1

À partir de cela, nous pouvons construire deux circuits / équations booléennes - un pour la sortie de somme et un pour la sortie de report. La façon la plus naïve est de simplement lister toutes les entrées. Toute table de vérité, quelle que soit sa taille et sa complexité, peut être reformulée sous cette forme:

(AND inputs in first row) OR (AND of inputs in second row) OR ...

Il s'agit essentiellement de la somme des produits. Nous ne regardons que les sorties qui donnent un 1 et ignorons les 0:

sum = (NOT a AND b) OR (a AND NOT b)

Remplaçons le ET OU et NON par des symboles de langage de programmation pour le rendre plus facile à lire:

sum = (!a & b) | (a & !b)

Fondamentalement, nous avons converti le tableau comme suit:

a b  |  sum  equation
-------------------
0 0  |   0   
0 1  |   1   (!a & b)
1 0  |   1   (a & !b)
1 1  |   0   

Cela peut être directement mis en œuvre sous forme de circuit:

                _____
 a ------------|     |
    \          | AND |-.     ____
     \  ,-NOT--|_____|  \   |    |
      \/                 `--| OR |----- sum
      /\        _____    ,--|____|
     /  `-NOT--|     |  /
    /          | AND |-`
 b ------------|_____|

Les lecteurs attentifs remarqueront à ce stade que la logique ci-dessus peut en fait être implémentée comme une seule porte - une porte XOR qui a commodément le comportement requis par notre table de vérité:

                _____
 a ------------|     |
               | XOR |---- sum
 b ------------|_____|

Mais si votre matériel ne vous fournit pas de porte XOR, les étapes ci-dessus expliquent comment vous allez le définir et l'implémenter en termes de portes ET, OU et NON.

La manière de convertir les portes logiques en matériel réel dépend du matériel dont vous disposez. Ils peuvent être implémentés à l'aide de divers mécanismes physiques tant que le mécanisme fournit une sorte de comportement de commutation. Des portes logiques ont été mises en œuvre avec tout, des jets d'eau ou des bouffées d'air (fluidique) aux transisiteurs (électronique) aux chutes de billes. C'est un gros sujet à part entière, je vais donc le passer sous silence et dire qu'il est possible d'implémenter des portes logiques en tant que périphériques physiques.

Maintenant, nous faisons de même pour le signal de portage. Puisqu'il n'y a qu'une seule condition où le signal de report est vrai, l'équation est simplement:

carry = a & b

Alors, c'est simple:

                _____
 a ------------|     |
               | AND |---- carry
 b ------------|_____|

En les combinant ensemble, nous obtenons ce que l'on appelle le demi-additionneur:

                _____
 a ------;-----|     |
         |     | XOR |---- sum
 b --;---|-----|_____|
     |   |      _____
     |   '-----|     |
     |         | AND |---- carry
     '---------|_____|

Au fait, les équations du circuit ci-dessus ressemblent à ceci:

sum = a ^ b
carry = a & b

Le demi-additionneur manque quelque chose. Nous avons implémenté la première règle - si le résultat est supérieur à un chiffre par rapport au report, mais nous n'avons pas implémenté la deuxième règle - s'il y a un report, ajoutez-le avec les chiffres.

Donc, pour implémenter un additionneur complet, un circuit d'addition qui peut ajouter des nombres à plus d'un chiffre, nous devons définir une table de vérité:

a b c  |  sum  carry
---------------------
0 0 0  |   0     0
0 0 1  |   1     0
0 1 0  |   1     0
0 1 1  |   0     1
1 0 0  |   1     0
1 0 1  |   0     1
1 1 0  |   0     1
1 1 1  |   1     1

L'équation de somme est maintenant:

sum = (!a & !b & c) | (!a & b & !c) | (a & !b & !c) | (a & b & c)

Nous pouvons suivre le même processus pour factoriser et simplifier l'équation et l'interpréter comme un circuit, etc., comme nous l'avons fait ci-dessus, mais je pense que cette réponse devient trop longue.

Vous devriez maintenant avoir une idée de la façon dont la logique numérique est conçue. Il y a d'autres astuces que je n'ai pas mentionnées, telles que les cartes de Karnaugh (utilisées pour simplifier les tables de vérité) et les compilateurs logiques tels que l'espresso (pour que vous n'ayez pas à factoriser les équations booléennes à la main), mais la base est fondamentalement ce que j'ai décrit ci-dessus:

  1. Décomposez le problème jusqu'à ce que vous puissiez travailler au niveau d'un seul bit (chiffre).

  2. Définissez les sorties souhaitées à l'aide d'une table de vérité.

  3. Convertissez le tableau en une équation booléenne et simplifiez l'équation.

  4. Interprétez l'équation comme des portes logiques.

  5. Convertissez votre circuit logique en circuits matériels réels en implémentant des portes logiques.

C'est ainsi que les problèmes fondamentaux (ou plutôt de bas niveau) sont vraiment résolus - beaucoup, beaucoup de tables de vérité. Le véritable travail de création consiste à décomposer une tâche complexe telle que le décodage MP3 au niveau du bit afin que vous puissiez y travailler avec des tables de vérité.

Désolé, je n'ai pas le temps d'expliquer comment implémenter la multiplication. Vous pouvez essayer de vous y attaquer en déterminant les règles de durée de la multiplication, puis en l'interprétant en binaire, puis essayez de la décomposer en tables de vérité. Ou vous pouvez lire Wikipedia: http://en.wikipedia.org/wiki/Binary_multiplier

Slebetman
la source
2

les instructions arithmétiques de base sont exécutées avec des instructions d'assemblage très efficaces.

Des instructions plus complexes (ou abstraites) sont soit exécutées en assemblage avec des mécanismes de boucle, soit gérées dans des bibliothèques std.

Pendant que vous étudiez les mathématiques à l'université, vous commencerez à étudier des choses comme le calcul lambda et la notation Big-O. Tous ces éléments, et bien d'autres, sont utilisés par les programmeurs pour évaluer et créer des algorithmes efficaces. Quoi qu'il en soit, les choses de base sont généralement accomplies au niveau bas, comme en assemblage ou en c avec des pointeurs.

Une excellente introduction à ce sujet est "Code" de Charles Petzold.

Jonathan Henson
la source
1
Ou des tables de recherche. Beaucoup plus rapide pour précalculer les valeurs et les rechercher. Exemples Sin / Cos / Tan (division entière bien que celle-ci soit pré-calculée et stockée dans le matériel).
Martin York
1

Procurez-vous un livre comme Fundamentals of Digital Logic ... , qui je pense est encore assez standard pour les étudiants de Freshman / Sophomore EE, et progressez à travers (ed: cela coûte une petite fortune, alors recherchez une édition utilisée ou antérieure de il). Cela vous guidera à travers les additionneurs et les multiplicateurs, et vous donnera suffisamment de contexte pour commencer à comprendre certains des principes derrière ce que fait le matériel.

Votre réponse deviendra, à court terme, "parce qu'elle rassemble énormément d'éléments de logique plus simple pour évoquer ce comportement complexe" au lieu de "parce qu'elle le fait".

Si vous voulez essayer de comprendre tous les principes de la façon dont les programmes sont compilés et exécutés, optez pour cela conjointement, afin que vous puissiez finalement voir comment tout se réunit au milieu.

jonsca
la source
1

Il y a beaucoup de bonnes réponses ici. Vous avez également commencé avec la bonne idée: des opérations complexes comme la multiplication sont construites à partir d'opérations plus simples. Comme vous l'avez deviné, il existe des moyens plus rapides de multiplier sans instruction de multiplication que d'utiliser une série d'additions. Toute multiplication peut être implémentée comme la somme de multiplications plus petites ou comme une combinaison de décalages et d'additions. Exemples:

a = 5 + 5 + 5 + 5 + 5;           // 5*5, but takes 5 operations
b = (5 << 2) + 5;                // 5*5 in only 2 operations
c = (41 << 4) + (41 << 2) + 41   // 41*21 in 4 operations

La division peut également être divisée en petites opérations. XOR (^) est une instruction intégrée sur tous les processeurs que j'ai jamais examinés, mais même ainsi, elle peut être implémentée comme une combinaison de AND, OR et NOT.

J'ai le sentiment, cependant, que des réponses spécifiques vous seront moins satisfaisantes qu'une idée générale des types d'instructions fournies par un processeur et de la façon dont ces instructions peuvent être combinées en opérations plus complexes. Il n'y a rien de mieux pour ce genre de curiosité qu'une bonne dose de langage d'assemblage. Voici une introduction très accessible au langage d'assemblage MIPS.

Caleb
la source
1

Voici comment un processeur moderne peut implémenter la multiplication de deux entiers 64 bits:

Vous savez faire une multiplication à longue main. Pour multiplier deux nombres à 10 chiffres, vous devez multiplier un nombre à 10 chiffres par chacun des 10 chiffres de l'autre nombre, écrire le résultat à 11 chiffres l'un en dessous de l'autre et décalé, puis ajouter tout le nombre.

Un processeur moderne fait cela avec tous les 64 par 64 bits. Cependant, une multiplication de deux nombres à un seul bit est très simple: 1 x 1 = 1, tous les autres produits sont nuls. Ceci est implémenté avec un et logique. Et contrairement au produit décimal, où le résultat peut être à deux chiffres, un produit binaire de nombres à un seul bit est toujours un bit.

Alors maintenant, vous avez 64 lignes de 64 bits qui doivent être ajoutées. Mais 64 ajouts de nombres 64 bits sont slooooooow. Le processeur utilise donc un additionneur 3/2 ou un additionneur 7/3: si vous ajoutez 3 nombres à un seul bit, le résultat peut être 0, 1, 2 ou 3, ce qui correspond à deux bits. Si vous ajoutez 7 nombres à un seul bit, le résultat est un nombre de 0 à 7, qui peut être représenté par 3 bits. IBM prétend qu'ils peuvent faire un additionneur 7/3 avec seulement 18 circuits primitifs (documentation PowerPC), je parie qu'Intel et ARM peuvent également le faire.

Vous disposez de 4096 bits, regroupez-les en environ 600 groupes de 7 bits dans les mêmes positions binaires et utilisez environ 600 additionneurs 7/3 pour réduire le résultat de 4096 bits à moins de 2000. Ensuite, vous faites la même chose encore et encore, jusqu'à ce que vous vous retrouviez avec des paires de bits qui peuvent être introduites dans un additionneur complet ordinaire.

gnasher729
la source