Tout est une question de stockage adéquat et d'algorithmes pour traiter les nombres comme des parties plus petites. Supposons que vous ayez un compilateur dans lequel an int
ne peut être que de 0 à 99 et que vous souhaitiez gérer des nombres jusqu'à 999999 (nous ne nous soucierons que des nombres positifs ici pour rester simple).
Vous faites cela en donnant à chaque chiffre trois int
s et en utilisant les mêmes règles que vous (auriez dû) apprendre à l'école primaire pour l'addition, la soustraction et les autres opérations de base.
Dans une bibliothèque de précision arbitraire, il n'y a pas de limite fixe sur le nombre de types de base utilisés pour représenter nos nombres, juste ce que la mémoire peut contenir.
Ajout par exemple 123456 + 78
::
12 34 56
78
-- -- --
12 35 34
Travailler à partir de l'extrémité la moins significative:
- report initial = 0.
- 56 + 78 + 0 report = 134 = 34 avec 1 report
- 34 + 00 + 1 report = 35 = 35 avec 0 report
- 12 + 00 + 0 report = 12 = 12 avec 0 report
C'est en fait ainsi que l'addition fonctionne généralement au niveau du bit à l'intérieur de votre CPU.
La soustraction est similaire (en utilisant la soustraction du type de base et emprunter au lieu du report), la multiplication peut être effectuée avec des additions répétées (très lentes) ou des produits croisés (plus rapide) et la division est plus délicate mais peut être effectuée en décalant et en soustrayant les nombres impliqué (la longue division que vous auriez appris en tant qu'enfant).
J'ai en fait écrit des bibliothèques pour faire ce genre de choses en utilisant les puissances maximales de dix qui peuvent être insérées dans un entier au carré (pour éviter le débordement lors de la multiplication de deux int
s ensemble, comme un 16 bits int
limité à 0 à 99 pour générer 9 801 (<32 768) au carré, ou 32 bits en int
utilisant 0 à 9 999 pour générer 99 980 001 (<2 147 483 648)), ce qui a grandement facilité les algorithmes.
Quelques astuces à surveiller.
1 / Lors de l'ajout ou de la multiplication des nombres, pré-allouez l'espace maximum nécessaire puis réduisez plus tard si vous trouvez que c'est trop. Par exemple, l'ajout de deux nombres à 100 "chiffres" (où chiffre est un int
) ne vous donnera jamais plus de 101 chiffres. Multiplier un nombre à 12 chiffres par un nombre à 3 chiffres ne générera jamais plus de 15 chiffres (ajoutez le nombre de chiffres).
2 / Pour plus de vitesse, ne normalisez (réduisez le stockage requis pour) les numéros que si c'est absolument nécessaire - ma bibliothèque avait cela comme un appel séparé afin que l'utilisateur puisse choisir entre la vitesse et les problèmes de stockage.
3 / L'addition d'un nombre positif et négatif est une soustraction, et soustraire un nombre négatif revient à ajouter l'équivalent positif. Vous pouvez économiser un peu de code en demandant aux méthodes d'ajout et de soustraction de s'appeler après avoir ajusté les signes.
4 / Évitez de soustraire les grands nombres aux petits car vous vous retrouvez invariablement avec des nombres comme:
10
11-
-- -- -- --
99 99 99 99 (and you still have a borrow).
Au lieu de cela, soustrayez 10 de 11, puis annulez-le:
11
10-
--
1 (then negate to get -1).
Voici les commentaires (transformés en texte) de l'une des bibliothèques pour lesquelles j'ai dû faire cela. Le code lui-même est, malheureusement, protégé par copyright, mais vous pourrez peut-être choisir suffisamment d'informations pour gérer les quatre opérations de base. Supposons dans ce qui suit que -a
et -b
représentent des nombres négatifs et a
et b
sont des nombres nuls ou positifs.
Pour l' addition , si les signes sont différents, utilisez la soustraction de la négation:
-a + b becomes b - a
a + -b becomes a - b
Pour la soustraction , si les signes sont différents, utilisez l'addition de la négation:
a - -b becomes a + b
-a - b becomes -(a + b)
Également un traitement spécial pour nous assurer que nous soustrayons les petits nombres des grands:
small - big becomes -(big - small)
La multiplication utilise les mathématiques d'entrée de gamme comme suit:
475(a) x 32(b) = 475 x (30 + 2)
= 475 x 30 + 475 x 2
= 4750 x 3 + 475 x 2
= 4750 + 4750 + 4750 + 475 + 475
La manière dont cela est réalisé consiste à extraire chacun des chiffres de 32 un à la fois (vers l'arrière) puis à utiliser add pour calculer une valeur à ajouter au résultat (initialement zéro).
ShiftLeft
et les ShiftRight
opérations sont utilisées pour multiplier ou diviser rapidement a LongInt
par la valeur d'enroulement (10 pour les mathématiques «réelles»). Dans l'exemple ci-dessus, nous ajoutons 475 à zéro 2 fois (le dernier chiffre de 32) pour obtenir 950 (résultat = 0 + 950 = 950).
Ensuite, nous avons laissé le décalage 475 pour obtenir 4750 et le décalage droit 32 pour obtenir 3. Ajouter 4750 à zéro 3 fois pour obtenir 14250 puis ajouter au résultat de 950 pour obtenir 15200.
Décalage gauche 4750 pour obtenir 47500, décalage droit 3 pour obtenir 0. Puisque le décalage 32 à droite est maintenant nul, nous avons terminé et, en fait, 475 x 32 équivaut à 15200.
La division est également délicate mais basée sur l'arithmétique précoce (la méthode «gazinta» pour «entre»). Considérez la longue division suivante pour 12345 / 27
:
457
+-------
27 | 12345 27 is larger than 1 or 12 so we first use 123.
108 27 goes into 123 4 times, 4 x 27 = 108, 123 - 108 = 15.
---
154 Bring down 4.
135 27 goes into 154 5 times, 5 x 27 = 135, 154 - 135 = 19.
---
195 Bring down 5.
189 27 goes into 195 7 times, 7 x 27 = 189, 195 - 189 = 6.
---
6 Nothing more to bring down, so stop.
Par conséquent 12345 / 27
est 457
avec le reste 6
. Vérifier:
457 x 27 + 6
= 12339 + 6
= 12345
Ceci est implémenté en utilisant une variable de tirage vers le bas (initialement zéro) pour réduire les segments de 12345 un par un jusqu'à ce qu'il soit supérieur ou égal à 27.
Ensuite, nous soustrayons simplement 27 de cela jusqu'à ce que nous soyons en dessous de 27 - le nombre de soustractions est le segment ajouté à la ligne supérieure.
Lorsqu'il n'y a plus de segments à abattre, nous avons notre résultat.
Gardez à l'esprit que ce sont des algorithmes assez basiques. Il existe de bien meilleures façons de faire de l'arithmétique complexe si vos nombres sont particulièrement importants. Vous pouvez consulter quelque chose comme la bibliothèque d'arithmétique de précision multiple GNU - c'est nettement meilleur et plus rapide que mes propres bibliothèques.
Il a l'inconvénient plutôt malheureux en ce sens qu'il se fermera simplement s'il manque de mémoire (un défaut plutôt fatal pour une bibliothèque à usage général à mon avis) mais, si vous pouvez regarder au-delà de cela, c'est assez bon dans ce qu'il fait.
Si vous ne pouvez pas l'utiliser pour des raisons de licence (ou parce que vous ne voulez pas que votre application se ferme sans raison apparente), vous pouvez au moins obtenir les algorithmes à partir de là pour les intégrer dans votre propre code.
J'ai également trouvé que les bods de MPIR (un fork de GMP) sont plus enclins à discuter des changements potentiels - ils semblent être plus conviviaux pour les développeurs.