Comment ajouter des valeurs dans un type long en langage C
Le langage C propose un certain nombre de types prédéfinis. Un type de données représente un ensemble de valeurs d'une même nature. Dans ce contexte, le type long est un type entier destiné à stocker des valeurs entières sur une plus grande plage que int sur certaines plateformes. Mais attention : cette particularité peut poser un problème en matière de portabilité d'un programme. Il est donc important de comprendre la nature du type long, ses limites, comment l'initialiser, l'affecter et surtout comment gérer les opérations d'ajout tout en évitant les dépassements de capacité (overflow/underflow).
Déclaration et initialisation d'une variable long
Deux possibilités vous sont offertes pour déclarer une variable. Dans les deux cas, il vous faudra aussi typer la variable et terminer votre déclaration par un caractère « ; ». Voici trois exemples concrets de déclarations de variable. Par exemple :
- déclarer la variable et l'initialiser : elle est initialisée à la valeur 5.
- déclaration avec initialisation à la valeur 10.99 (pour les flottants), mais pour long on utilisera des entiers.
Remarque : en C, il y a une différence entre majuscules et minuscules. Aucun mot clé du langage ne porte le nom long en majuscules/minuscules près. Remarque également : sur-typer vos variables peut engendrer une surconsommation mémoire importante.
Tailles, portabilité et limites du type long
Préciser la nature de la plate-forme est importante. En conséquence voici ce que garantit la norme sur le langage C : la taille en octets peut varier suivant la plate-forme d'exécution. L'opérateur sizeof permet d'obtenir la taille effective (la valeur retournée sera bien un nombre d'octets, et non pas un nombre de bits). Nous reviendrons par la suite sur l'ensemble des types prédéfinis que peut prendre une variable.
Nous avons abordé les limites des types à plusieurs reprises dans les chapitres précédents, mais nous ne nous sommes pas encore véritablement arrêté sur ce point. Au début de ce cours, nous vous avons précisé que tous les types avaient des bornes et qu’en conséquence, ils ne pouvaient stocker qu’un intervalle défini de valeurs. Les bornes des types entiers (par exemple pour long) sont accessibles via des macroconstantes (dans <limits.h>). À ces macroconstantes, il faut également ajouter CHAR_BIT qui vous indique le nombre bits composant un multiplet.
Lire aussi: Modifier Activité Kbis Auto-Entrepreneur
Le franchissement de la limite d’un type est appelé un dépassement de capacité (overflow ou underflow en anglais, suivant que la limite maximale ou minimale est outrepassée). Si une opération travaillant avec des entiers signés dépasse la capacité du type du résultat, le comportement est indéfini. Si une opération travaillant avec des entiers non signés dépasse la capacité du type du résultat, alors la valeur "boucle" (wrap-around). Il est donc essentiel de détecter et prévenir ces dépassements lorsque l'on ajoute des valeurs dans un type long.
Ajouter des valeurs dans un long : risques et pratiques
Garder en mémoire que des conversions implicites ont lieu lors des opérations. C’est ici que le bât blesse : ce n’est pas précisé. Des conversions implicites peuvent provoquer des résultats inattendus. Gardez également en mémoire que des opérations d'addition peuvent produire un dépassement. Par exemple, tester if (a + b > LONG_MAX) est dangereux car l'expression elle-même peut déborder.
Afin d’empêcher un dépassement, il va nous falloir le détecter et ceci, bien entendu, à l’aide de conditions qui ne doivent pas elles-mêmes produire de dépassement. Ainsi, ce genre de condition est à proscrire :
- if (a + b > LONG_MAX)
Ce qui peut être contrôlé de façon sûre est : vérifier que a n’est pas supérieur à LONG_MAX - b (pour l'addition positive), ou que a n’est pas inférieur à LONG_MIN - b (pour l'addition impliquant des négatifs). En pratique, on utilise les macros LONG_MAX et LONG_MIN (définies dans <limits.h>).
Approche sûre pour l'addition
Afin d’empêcher l'overflow à l'addition de deux long a et b, on peut effectuer des tests préalables basés sur les limites :
Lire aussi: INPI et Micro-Entreprise : Démarches
- si b > 0, vérifier que a <= LONG_MAX - b ;
- si b < 0, vérifier que a >= LONG_MIN - b ;
Ces tests évitent l'évaluation de a + b avant la garantie que l'opération est sûre. Dans un contexte où l'on souhaite retourner une indication d'erreur, on peut utiliser errno et affecter ERANGE en cas de dépassement.
Cas particuliers : négation, multiplication et division
Mais ?! Bonne question ! L'opérateur de négation - peut produire un dépassement. En effet, souvenez-vous : il y a une asymétrie entre la borne inférieure et supérieure de la représentation en complément à deux (par exemple, en valeur absolue, LONG_MIN est supérieur à LONG_MAX). Dès lors, la négation de la borne inférieure produira un dépassement. Pour la multiplication, cela se corse un peu : selon les signes des opérandes, il faut effectuer des vérifications différentes. Si l’un des deux opérandes est positif, on doit vérifier que le maximum n’est pas dépassé ; si les deux sont négatifs, vérifier que le résultat ne dépasse pas LONG_MAX ; etc.
Rassurez-vous, la division est nettement moins pénible à vérifier. Notez que, mathématiquement, l’expression LONG_MIN % -1 vaut zéro. Toutefois, la norme C conditionne le bon fonctionnement du modulo au fait que le résultat de la division de ses deux opérandes soit représentable. En pratique, il faut donc vérifier aussi les cas limites pour la division (par exemple division par zéro et la situation INT_MIN / -1).
Gestion des dépassements : stratégies
Nous savons à présent ce qu’est un dépassement, quand ils se produisent et dans quel cas ils sont problématiques (en vérité, potentiellement tous, puisque même si la valeur boucle, ce n’est pas forcément ce que nous souhaitons). Reste à présent pour nous à les gérer, c’est-à-dire les détecter et les éviter. Malheureusement pour nous, la bibliothèque standard ne nous fournit aucun outil à cet effet, mise à part les macroconstantes qui vous ont été présentées.
Afin de préciser à l’utilisateur qu’un dépassement a été détecté (toutes les valeurs du type long pouvant être retournées), vous pouvez recourir à la variable errno, à laquelle vous affecterez la valeur ERANGE en cas de dépassement. Ceci étant posé, passons à la réalisation de ces fonctions : construire des wrappers pour l’addition, la soustraction, la multiplication et la division qui effectuent les vérifications nécessaires avant d'exécuter l'opération.
Lire aussi: Comment Calculer la TVA ?
Exemple logique (pseudocode d'une fonction d'addition sûre)
Pour empêcher un dépassement, voici la logique générale à respecter :
- si b > 0, vérifier que a <= LONG_MAX - b ; sinon ERANGE
- si b < 0, vérifier que a >= LONG_MIN - b ; sinon ERANGE
- si b == 0, l'addition est sûre
Pour la soustraction, le principe est le même que pour l’addition en adaptant les tests (on peut réutiliser l'addition en inversant le signe du second opérande). Pour la multiplication, les tests sont plus nombreux et dépendent des signes des opérandes. En guise d'exercice, essayez de trouver la solution par vous-même.
Remarques pratiques et outils
Notez également qu'en C, il existe des suffixes L et LL pour indiquer explicitement les littéraux longs et long long, et qu’il est nécessaire d’utiliser le bon format pour printf (par exemple %ld pour long). Gardez en mémoire que des conversions implicites peuvent survenir entre types et provoquer des comportements inattendus.
Enfin, si votre programme doit être portable, préciser la nature de la plate-forme et utiliser des types entiers à largeur définie (int32_t, int64_t) définis dans <stdint.h> peut aider à garantir des bornes connues. Pour des besoins supérieurs à long, vous pouvez utiliser long long (avec LL) ou des types entiers à largeur fixe.
Bonnes pratiques résumé
- Utiliser les macroconstantes LONG_MAX et LONG_MIN (<limits.h>) pour connaître les bornes.
- Avant d'additionner, effectuer des tests préalables sur les opérandes pour éviter l'évaluation d'une expression qui pourrait déborder.
- Utiliser errno = ERANGE pour signaler un dépassement détecté dans des fonctions utilitaires.
- Préférer les types à largeur fixe (<stdint.h>) quand la portabilité est critique.
- Faire attention aux conversions implicites et aux suffixes L/LL pour les littéraux.
En appliquant ces principes, vous pourrez ajouter des valeurs dans des variables de type long de manière sûre et portable, en détectant et en traitant les dépassements de capacité plutôt qu'en laissant le comportement indéfini ou le wrap-around surprendre votre programme.
balises:
