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.
Bien que réinventer la roue soit extrêmement bon pour votre édification et votre apprentissage personnels, c'est aussi une tâche extrêmement importante. Je ne veux pas vous en dissuader car c'est un exercice important et que j'ai fait moi-même, mais vous devez être conscient qu'il y a des problèmes subtils et complexes au travail que les paquets plus volumineux abordent.
Par exemple, la multiplication. Naïvement, vous pourriez penser à la méthode «écolier», c'est-à-dire écrire un nombre au-dessus de l'autre, puis faire de longues multiplications comme vous l'avez appris à l'école. exemple:
mais cette méthode est extrêmement lente (O (n ^ 2), n étant le nombre de chiffres). Au lieu de cela, les packages bignum modernes utilisent une transformation de Fourier discrète ou une transformation numérique pour transformer cela en une opération essentiellement O (n ln (n)).
Et ce n'est que pour les entiers. Quand vous entrez dans des fonctions plus compliquées sur un certain type de représentation réelle du nombre (log, sqrt, exp, etc.), les choses deviennent encore plus compliquées.
Si vous souhaitez un peu de fond théorique, je vous recommande vivement de lire le premier chapitre du livre de Yap, "Problèmes fondamentaux de l'algorithmique" . Comme déjà mentionné, la bibliothèque gmp bignum est une excellente bibliothèque. Pour les vrais chiffres, j'ai utilisé mpfr et je l'ai aimé.
la source
Ne réinventez pas la roue: elle peut s'avérer carrée!
Utilisez une bibliothèque tierce, telle que GNU MP , qui a fait ses preuves.
la source
abort()
des échecs d'allocation, qui sont inévitables avec certains calculs incroyablement volumineux. C'est un comportement inacceptable pour une bibliothèque et une raison suffisante pour écrire votre propre code de précision arbitraire.Vous le faites essentiellement de la même manière que vous le faites avec un crayon et du papier ...
malloc
etrealloc
) selon les besoinsEn règle générale, vous utiliserez comme unité de calcul de base
comme dicté par votre architecture.
Le choix de la base binaire ou décimale dépend de vos désirs pour une efficacité spatiale maximale, une lisibilité humaine et la présence d'une absence de support mathématique Binary Coded Decimal (BCD) sur votre puce.
la source
Vous pouvez le faire avec des mathématiques de niveau secondaire. Bien que des algorithmes plus avancés soient utilisés dans la réalité. Par exemple, pour ajouter deux nombres de 1024 octets:
le résultat devra être plus grand
one place
en cas d'ajout pour tenir compte des valeurs maximales. Regarde ça :TTMath est une excellente bibliothèque si vous voulez apprendre. Il est construit en utilisant C ++. L'exemple ci-dessus était idiot, mais c'est ainsi que l'addition et la soustraction se font en général!
Une bonne référence sur le sujet est la complexité informatique des opérations mathématiques . Il vous indique l'espace requis pour chaque opération que vous souhaitez implémenter. Par exemple, si vous avez deux
N-digit
nombres, vous avez besoin2N digits
stocker le résultat de la multiplication.Comme l'a dit Mitch , ce n'est de loin pas une tâche facile à mettre en œuvre! Je vous recommande de jeter un oeil à TTMath si vous connaissez C ++.
la source
L'une des références ultimes (IMHO) est TAOCP Volume II de Knuth. Il explique de nombreux algorithmes pour représenter des nombres et des opérations arithmétiques sur ces représentations.
la source
En supposant que vous souhaitiez écrire vous-même un gros code entier, cela peut être étonnamment simple à faire, parlé comme quelqu'un qui l'a fait récemment (bien que dans MATLAB.) Voici quelques-unes des astuces que j'ai utilisées:
J'ai stocké chaque chiffre décimal individuel sous forme de nombre double. Cela simplifie de nombreuses opérations, en particulier la sortie. Bien que cela prenne plus de stockage que vous ne le souhaiteriez, la mémoire est bon marché ici et cela rend la multiplication très efficace si vous pouvez convoluer efficacement une paire de vecteurs. Alternativement, vous pouvez stocker plusieurs chiffres décimaux dans un double, mais attention alors que la convolution pour faire la multiplication peut poser des problèmes numériques sur de très grands nombres.
Stockez un bit de signe séparément.
L'ajout de deux nombres consiste principalement à ajouter les chiffres, puis à vérifier un report à chaque étape.
La multiplication d'une paire de nombres est mieux effectuée sous forme de convolution suivie d'une étape de report, du moins si vous avez un code de convolution rapide à portée de main.
Même lorsque vous stockez les nombres sous la forme d'une chaîne de chiffres décimaux individuels, la division (également les opérations mod / rem) peut être effectuée pour gagner environ 13 chiffres décimaux à la fois dans le résultat. C'est beaucoup plus efficace qu'une division qui ne fonctionne que sur 1 chiffre décimal à la fois.
Pour calculer une puissance entière d'un entier, calculez la représentation binaire de l'exposant. Ensuite, utilisez des opérations de quadrillage répétées pour calculer les puissances selon vos besoins.
De nombreuses opérations (factoring, tests de primalité, etc.) bénéficieront d'une opération powermod. Autrement dit, lorsque vous calculez mod (a ^ p, N), réduisez le résultat mod N à chaque étape de l'exponentiation où p a été exprimé sous forme binaire. Ne calculez pas d'abord a ^ p, puis essayez de le réduire mod N.
la source
Voici un exemple simple (naïf) que j'ai fait en PHP.
J'ai implémenté "Add" et "Multiply" et utilisé cela pour un exemple d'exposant.
http://adevsoft.com/simple-php-arbitrary-precision-integer-big-num-example/
Extrait de code
la source