Pourquoi les numéros non signés sont-ils implémentés?

12

Je ne peux pas comprendre pourquoi les systèmes à microprocesseur implémentent des nombres non signés. Je suppose que le coût est juste le double du nombre de branches conditionnelles, car supérieur à, inférieur à, .etc, a besoin d'un algorithme différent de signé, existe-t-il encore des algorithmes pour lesquels les nombres non signés sont un avantage significatif?

ma question est en partie pourquoi ils doivent être dans le jeu d'instructions plutôt que d'être pris en charge par un compilateur?

jtw
la source
27
Fondamentalement, les nombres non signés sont la norme, signés sont mis en œuvre pour fournir des nombres négatifs.
Pieter B
37
De nombreuses données mondiales ne sont pas numériques. Les données non numériques sont facilement manipulables à l'aide de types non signés. Le fait que Java n'ait pas de types numériques non signés est un échec, ce qui provoque beaucoup de bugs dans les choses qui doivent manipuler des données non numériques (par exemple la compression, etc.).
Erik Eidt
6
@jtw Erik dit qu'il n'y a rien de tel qu'une couleur de pixel négative ou un caractère négatif. Il serait donc inutile d'utiliser des entiers signés pour cela, vous abandonneriez la moitié de l'espace d'adressage.
Martin Maat
26
Je ne sais pas si je suis seul ici, mais je trouve étonnamment rare d'avoir besoin d' entiers signés lors du développement d'applications. Presque tout le temps, ce dont j'ai besoin est soit un nombre naturel (non signé) (une taille positive, généralement), soit un nombre à virgule flottante signé. Les exceptions sont des choses comme la monnaie, mais elles sont très rares; pour moi, les entiers non signés sont la norme et les entiers signés sont l'exception!
Thomas
11
Du point de vue d'un processeur, presque tous les chiffres ne sont pas signés. Quelques instructions peuvent interpréter les bits comme signés (.eg arithmetic-right-shift), mais en réalité, le complément à deux permet au CPU de traiter les entiers signés comme des entiers non signés, ce qui signifie qu'aucun circuit spécial (ou très peu) n'est requis par le CPU pour prendre en charge les deux. .
Cornstalks

Réponses:

39

Les nombres non signés sont une interprétation d'une séquence de bits. C'est également l'interprétation la plus simple et la plus utilisée en interne au CPU car les adresses et les codes op ne sont que des bits. L'adressage mémoire / pile et l'arithmétique sont les fondements du microprocesseur, enfin, du traitement. En remontant la pyramide d'abstraction, une autre interprétation fréquente des bits est celle d'un caractère (ASCII, Unicode, EBCDIC). Ensuite, il existe d'autres interprétations telles que IEEE Floating virgule, RGBA pour les graphiques, etc. Aucun de ceux-ci n'est de simples nombres signés (IEEE FP n'est pas simple, et l'arithmétique qui les utilise est très compliquée).

De plus, avec l'arithmétique non signée, il est assez simple (sinon le plus efficace) d'implémenter les autres. L'inverse est pas vrai.

Kristian H
la source
3
EBCDIC n'a qu'un seul "je".
Ruslan
4
@Ruslan - mais il se prononce comme s'il en avait deux. <g>
Pete Becker
5
@PeteBecker non ça ne l'est pas. EBCDIC est prononcé eb -see-dick.
Mike Nakis
19

La majeure partie du coût du matériel pour les opérations de comparaison est la soustraction. La sortie de la soustraction utilisée par comparaison est essentiellement trois bits d'état:

  • si tous les bits sont nuls (c'est-à-dire la condition égale),
  • le bit de signe du résultat
  • le bit de retenue de la soustraction (c'est-à-dire le 33e bit de poids fort sur un ordinateur 32 bits)

Avec la bonne combinaison de test de ces trois bits après l'opération de soustraction, nous pouvons déterminer toutes les opérations relationnelles signées, ainsi que toutes les opérations relationnelles non signées (ces bits sont également la façon dont le dépassement est détecté, signé vs non signé). Le même matériel ALU de base peut être partagé pour implémenter toutes ces comparaisons (sans parler de l'instruction de soustraction), jusqu'à la vérification finale de ces trois bits d'état, qui diffère selon la comparaison relationnelle souhaitée. Donc, ce n'est pas beaucoup de matériel supplémentaire.

Le seul coût réel est la nécessité du codage de modes de comparaison supplémentaires dans l'architecture du jeu d'instructions, ce qui peut diminuer légèrement la densité des instructions. Pourtant, il est assez normal que le matériel ait beaucoup d'instructions qui ne sont pas utilisées par une langue donnée.

Erik Eidt
la source
1
La comparaison des nombres non signés ne nécessite pas de soustraction. il peut être obtenu par une comparaison au niveau du bit de gauche à droite.
Jonathan Rosenne
10
@JonathanRosenne Mais ce n'est pas ainsi que les processeurs l'implémentent. Au contraire, il est presque impensable pour un processeur complément à 2 de ne pas implémenter de soustraction (avec ou sans report / emprunt) dans son ALU. Immédiatement après, la pensée d'un designer est d'utiliser cet ALU nécessaire pour tuer un autre oiseau avec la même pierre, comparaison. La comparaison devient alors simplement une soustraction où le résultat n'est pas réécrit dans le fichier de registre.
Iwillnotexist Idonotexist
4
+1: c'est la bonne réponse à la question posée. En résumé: parce que l'implémentation d'opérations non signées est presque gratuite lorsque vous avez déjà implémenté signé .
Periata Breatta du
10
@PeriataBreatta Cela fonctionne également dans l'autre sens. Les nombres signés et non signés dans les processeurs modernes sont presque identiques, ce qui est le principal point que l'OP n'a pas reconnu. Même les instructions de comparaison sont les mêmes pour les signés et les non signés - c'est l'une des raisons pour lesquelles le complément à deux a remporté les guerres entières signées :)
Luaan
3
@svidgen> comme d'autres réponses l'ont dit, cela fonctionne dans l'autre sens. La préoccupation principale est les nombres non signés, qui sont utilisés pour pratiquement tout (adresse mémoire, io / ports, représentations de caractères,…). Les numéros signés ne sont pas chers une fois que vous n'êtes pas signé et sont utiles dans les rares cas où ils sont souhaitables.
spectras
14

Parce que, si vous avez besoin de compter quelque chose qui est toujours >= 0 , vous réduisez inutilement votre espace de comptage de moitié en utilisant des entiers signés.

Considérez le PK INT auto-incrémenté que vous pourriez mettre sur vos tables de base de données. Si vous y utilisez un entier signé, votre table stocke la MOITIÉ autant d'enregistrements que possible pour la même taille de champ sans aucun avantage.

Ou les octets d'une couleur RGBa. Nous ne voulons pas commencer maladroitement à compter ce concept de nombre naturellement positif à un nombre négatif. Un numéro signé briserait le modèle mental ou diviserait de moitié notre espace. Un entier non signé correspond non seulement au concept, mais fournit deux fois la résolution.

Du point de vue matériel, les entiers non signés sont simples. Ils sont probablement la structure de bits la plus simple pour effectuer des calculs. Et, sans aucun doute, nous pourrions simplifier le matériel en simulant des types entiers (ou même virgule flottante!) Dans un compilateur. Alors, pourquoi les entiers non signés et signés sont-ils implémentés dans le matériel?

Et bien ... la performance!

Il est plus efficace d'implémenter des entiers signés dans le matériel que dans le logiciel. Le matériel peut être chargé d'effectuer des calculs sur l'un ou l'autre type d'entier dans une seule instruction. Et c'est très bien , car le matériel écrase les bits plus ou moins en parallèle. Si vous essayez de simuler cela dans un logiciel, le type entier que vous choisissez de "simuler" nécessitera sans aucun doute de nombreuses instructions et sera sensiblement plus lent.

svidgen
la source
2
Dans ce sens, vous pouvez vous enregistrer une opération lors de la vérification des limites du tableau. Si vous utilisez un entier non signé, il vous suffit de vérifier que l'index fourni est inférieur à la taille du tableau (car il ne peut pas être négatif).
riwalk
2
@ dan04 Cela peut certainement ... Mais, si vous utilisez un entier à incrémentation automatique commençant à 0 ou 1, ce qui est une pratique assez courante, vous avez exclu l'utilisation de la moitié de vos nombres disponibles. Et bien que vous puissiez éventuellement commencer à compter à -2 ^ 31 (ou autre), vous aurez un cas potentiel de "bord" au milieu de votre espace d'identification.
svidgen
1
Couper votre domaine en deux est cependant un argument faible. Il y a de fortes chances que si votre application nécessite plus de 2 milliards, elle nécessite également plus de 4 milliards.
corsiKa
1
@corsiKa: pour cette raison, s'il en faut plus de 4, il en faut probablement 8, puis 16, etc. Où finit-il?
whatsisname
1
@whatsisname généralement, vous utilisez des types entiers de 8, 16, 32 ou 64 bits. Dire que non signé est meilleur parce que vous obtenez les 32 bits au lieu de la plage limitée de 31 bits d'espace entier positif dans un octet signé n'est pas très important dans la plupart des cas.
corsiKa
9

Votre question se compose de deux parties:

  1. Quel est le but des entiers non signés?

  2. Les entiers non signés valent-ils la peine?

1. Quel est le but des entiers non signés?

Les nombres non signés représentent tout simplement une classe de quantités pour lesquelles les valeurs négatives n'ont pas de sens. Bien sûr, vous pourriez dire que la réponse à la question "combien de pommes ai-je?" peut être un nombre négatif si vous devez des pommes à quelqu'un, mais qu'en est-il de la question de "combien de mémoire ai-je?" --vous ne pouvez pas avoir une quantité de mémoire négative. Ainsi, les entiers non signés sont très appropriés pour représenter de telles quantités, et ils ont l'avantage de pouvoir représenter deux fois la plage de valeurs positives que les entiers signés peuvent. Par exemple, la valeur maximale que vous pouvez représenter avec un entier signé 16 bits est 32767, tandis qu'avec un entier non signé 16 bits, elle est 65535.

2. Les entiers non signés valent-ils la peine?

Les entiers non signés ne représentent pas vraiment de problème, alors, oui, ils en valent la peine. Vous voyez, ils ne nécessitent pas un ensemble supplémentaire d '"algorithmes"; les circuits requis pour les implémenter sont un sous-ensemble des circuits requis pour implémenter des entiers signés.

Un processeur n'a pas un multiplicateur pour les entiers signés et un multiplicateur différent pour les entiers non signés; il n'a qu'un seul multiplicateur, qui fonctionne de manière légèrement différente selon la nature de l'opération. La prise en charge de la multiplication signée nécessite un tout petit peu plus de circuits que non signé, mais comme elle doit être prise en charge de toute façon, la multiplication non signée est pratiquement gratuite, elle est incluse dans le package.

En ce qui concerne l'addition et la soustraction, il n'y a aucune différence dans les circuits. Si vous lisez la représentation dite des compléments à deux des entiers, vous constaterez qu'elle est si intelligemment conçue que ces opérations peuvent être effectuées exactement de la même manière, quelle que soit la nature des entiers.

La comparaison fonctionne également de la même manière, car il ne s'agit que de soustraire et d'éliminer le résultat, la seule différence réside dans les instructions de branchement conditionnel (saut), qui fonctionnent en examinant différents drapeaux du processeur définis par le instruction (de comparaison) précédente. Dans cette réponse: /programming//a/9617990/773113, vous pouvez trouver une explication de leur fonctionnement sur l'architecture Intel x86. Ce qui se passe, c'est que la désignation d'une instruction de saut conditionnel comme signée ou non signée dépend des indicateurs qu'elle examine.

Mike Nakis
la source
1
ma question supposait tout cela, par algorithme, je voulais dire que la règle pour moins de plus que etc. était différente. Le coût que je vois est d'avoir beaucoup d'instructions supplémentaires. Si les programmes de haut niveau aiment voir les données comme des modèles de bits, cela peut être facilement mis en œuvre par un compilateur
jtw
3
@jtw - mais le fait est que ces instructions supplémentaires sont en fait très similaires aux instructions requises pour les numéros signés, et presque tous les circuits requis pour eux peuvent être partagés . Le coût supplémentaire de la mise en œuvre des deux types est presque nul.
Periata Breatta du
1
oui, cela répond à ma question, l'ajout des instructions de branchement supplémentaires a un petit coût et elles sont souvent utiles dans la pratique
jtw
1
"Les opérations non signées nécessitent un traitement supplémentaire en ce qui concerne la division et la multiplication" Je pense que vous avez cela à l'envers. La multiplication et la division sont plus faciles avec des valeurs non signées. La manipulation supplémentaire est requise pour traiter les opérandes signés.
Cody Gray
@CodyGray Je savais que quelqu'un se présenterait pour dire cela. Vous avez raison, bien sûr. C'est le raisonnement derrière ma déclaration, que j'ai à l'origine omis pour des raisons de brièveté: un processeur ne pouvait pas offrir la multiplication et la division non signées uniquement, parce que les versions signées sont si utiles. En fait, la multiplication et la division signées sont un must; non signés sont facultatifs. Par conséquent, si non signé doit également être proposé, cela peut être considéré comme nécessitant un tout petit peu plus de circuits.
Mike Nakis
7

Les microprocesseurs sont intrinsèquement non signés. Les numéros signés sont la chose qui est mise en œuvre, et non l'inverse.

Les ordinateurs peuvent fonctionner et fonctionnent bien sans numéros signés, mais c'est nous, les humains qui ont besoin de nombres négatifs, d'où la signature a été inventée.

Pieter B
la source
4
De nombreux microprocesseurs ont des instructions signées et non signées pour diverses opérations.
whatsisname
1
@whatsisname: C'est le contraire: de nombreux microprocesseurs n'ont que des instructions non signées. A quelques ont des instructions signées. En effet, avec l'arithmétique du complément 2s, la valeur du bit est la même quelle que soit la météo, le nombre est signé ou non et la façon dont on lit le nombre n'est qu'une question d'interprétation - il est donc plus facile de l'implémenter en tant que fonction de compilation. Généralement, seuls les anciens micros qui supposent que les programmeurs n'utilisent pas de compilateurs ont des instructions signées de fantaisie pour rendre le code assembleur lisible.
slebetman
3

Parce qu'ils ont un bit de plus qui est facilement disponible pour le stockage, et vous n'avez pas à vous soucier des nombres négatifs. Il n'y a pas grand-chose de plus que cela.

Maintenant, si vous avez besoin d'un exemple de l'endroit où vous auriez besoin de ce bit supplémentaire, il y a beaucoup à trouver si vous regardez.

Mon exemple préféré vient des bitboards dans les moteurs d'échecs. Il y a 64 cases sur un échiquier, unsigned longoffrant ainsi un stockage parfait pour une variété d'algorithmes tournant autour de la génération de mouvements. Compte tenu du fait que vous avez besoin d'opérations binaires (ainsi que d'opérations de décalage !!), il est facile de voir pourquoi il est plus facile de ne pas avoir à se soucier de ce qui se passe si le MSB est défini. Cela peut être fait avec signé long, mais il est beaucoup plus facile d'utiliser non signé.

riwalk
la source
3

Ayant une formation en mathématiques pures, c'est une approche légèrement plus mathématique pour quiconque s'intéresse.

Si nous commençons avec un entier signé et non signé 8 bits, ce que nous avons est essentiellement les entiers modulo 256, en ce qui concerne l'addition et la multiplication, à condition que le complément de 2 soit utilisé pour représenter des entiers négatifs (et c'est ainsi que tous les processeurs modernes le font) .

Là où les choses diffèrent, c'est à deux endroits: l'un est les opérations de comparaison. Dans un sens, les entiers modulo 256 sont mieux considérés comme un cercle de nombres (comme le font les entiers modulo 12 sur une horloge analogique à l'ancienne). Pour que les comparaisons numériques (x <y) soient significatives, nous devions décider quels nombres sont inférieurs aux autres. Du point de vue du mathématicien, nous voulons incorporer les entiers modulo 256 dans l'ensemble de tous les entiers d'une manière ou d'une autre. Le mappage de l'entier 8 bits dont la représentation binaire est tous des zéros à l'entier 0 est la chose évidente à faire. Nous pouvons ensuite procéder à la cartographie des autres afin que '0 + 1' (le résultat de la mise à zéro d'un registre, disons ax, et de l'incrémentation de un, via 'inc ax') passe à l'entier 1, et ainsi de suite. Nous pouvons faire de même avec -1, par exemple en mappant «0-1» à l'entier -1 et «0-1-1» à l'entier -2. Nous devons nous assurer que cette intégration est une fonction, donc nous ne pouvons pas mapper un seul entier 8 bits à deux entiers. En tant que tel, cela signifie que si nous mappons tous les nombres dans l'ensemble d'entiers, 0 sera là, ainsi que certains entiers inférieurs à 0 et certains supérieurs à 0. Il existe essentiellement 255 façons de le faire avec un entier 8 bits (selon au minimum que vous voulez, de 0 à -255). Ensuite, vous pouvez définir «x <y» en termes de «0 <y - x».

Il existe deux cas d'utilisation courants, pour lesquels la prise en charge matérielle est judicieuse: l'un avec tous les entiers non nuls supérieurs à 0, et l'autre avec une répartition d'environ 50/50 autour de 0. Toutes les autres possibilités sont facilement émulées en traduisant les nombres via un ajout supplémentaire. et sub 'avant les opérations, et la nécessité de cela est si rare que je ne peux pas penser à un exemple explicite dans un logiciel moderne (puisque vous pouvez simplement travailler avec une mantisse plus grande, disons 16 bits).

L'autre problème est celui de la mise en correspondance d'un entier 8 bits dans l'espace des nombres entiers 16 bits. -1 va-t-il à -1? C'est ce que vous voulez si 0xFF est censé représenter -1. Dans ce cas, l'extension du signe est la chose la plus judicieuse à faire, de sorte que 0xFF passe à 0xFFFF. D'un autre côté, si 0xFF était censé représenter 255, alors vous voulez qu'il soit mappé à 255, donc à 0x00FF, plutôt qu'à 0xFFFF.

C'est également la différence entre les opérations de «décalage» et de «décalage arithmétique».

En fin de compte, cependant, cela revient au fait que les int dans le logiciel ne sont pas des entiers, mais des représentations en binaire, et que seuls certains peuvent être représentés. Lors de la conception du matériel, des choix doivent être faits quant à ce qu'il faut faire nativement dans le matériel. Comme avec le complément à 2, les opérations d'addition et de multiplication sont identiques, il est logique de représenter les entiers négatifs de cette façon. Il ne s'agit alors que d'opérations qui dépendent des entiers que vos représentations binaires sont censées représenter.

John Allsup
la source
J'aime l'approche mathématique, mais au lieu de penser simplement à une promotion vers une taille spécifique plus grande, je pense qu'il est plus agréable de généraliser en termes d'opérations sur des nombres binaires de longueur infinie. Soustrayez 1 de tout nombre dont les k chiffres les plus à droite sont 0 et les k chiffres les plus à droite du résultat seront 1, et on peut prouver par induction que si on a effectué des calculs avec un nombre infini de bits, chaque bit serait 1. Pour non signé mathématiques, on ignore tout sauf les bits inférieurs d'un nombre.
supercat
2

Permet d'examiner le coût de mise en œuvre pour ajouter des entiers non signés à une conception de processeur avec des entiers signés existants.

Un CPU typique a besoin des instructions arithmétiques suivantes:

  • ADD (qui ajoute deux valeurs et définit un indicateur si l'opération déborde)
  • SUB (qui soustrait une valeur d'une autre et définit divers indicateurs - nous en discuterons ci-dessous)
  • CMP (qui est essentiellement «SUB et ignore le résultat, ne conserve que les indicateurs»)
  • LSH (décalage à gauche, définir un drapeau en cas de débordement)
  • RSH (décalage à droite, définir un indicateur si un 1 est décalé)
  • Variantes de toutes les instructions ci-dessus qui gèrent le transport / l'emprunt des drapeaux, vous permettant ainsi d'enchaîner les instructions facilement pour fonctionner sur des types plus grands que les registres du processeur
  • MUL (multiplier, définir des drapeaux, etc. - non disponible universellement)
  • DIV (diviser, définir des drapeaux, etc. - il manque beaucoup d'architectures CPU)
  • Passer d'un type entier plus petit (par exemple 16 bits) à un plus grand (par exemple 32 bits). Pour les entiers signés, cela est généralement appelé MOVSX (déplacer avec l'extension du signe).

Il a également besoin d'instructions logiques:

  • Branche sur zéro
  • Branche sur une plus grande
  • Branche sur moins
  • Branche en cas de débordement
  • Versions négatives de tout ce qui précède

Pour effectuer les branches ci-dessus sur des comparaisons entières signées, la manière la plus simple est de faire en sorte que l'instruction SUB définisse les indicateurs suivants:

  • Zéro. Définissez si la soustraction a abouti à une valeur de zéro.
  • Débordement. Définissez si la soustraction a emprunté une valeur au bit le plus significatif.
  • Signe. Défini sur le bit de signe du résultat.

Ensuite, les branches arithmétiques sont implémentées comme suit:

  • Branche sur zéro: si le drapeau zéro est défini
  • Branche sur moins: si l'indicateur de signe est différent de l'indicateur de débordement
  • Branche plus grande: si le drapeau de signe est égal au drapeau de débordement et que le drapeau zéro est effacé.

Les négations de celles-ci devraient découler évidemment de la façon dont elles sont mises en œuvre.

Ainsi, votre conception existante implémente déjà tout cela pour les entiers signés. Voyons maintenant ce que nous devons faire pour ajouter des entiers non signés:

  • ADD - l'implémentation d'ADD est identique.
  • SUB - nous devons ajouter un drapeau supplémentaire: le drapeau de report est défini lorsqu'une valeur est empruntée au-delà du bit le plus significatif du registre.
  • CMP - ne change pas
  • LSH - ne change pas
  • RSH - le décalage à droite pour les valeurs signées conserve la valeur du bit le plus significatif. Pour les valeurs non signées, nous devrions plutôt le mettre à zéro.
  • MUL - si votre taille de sortie est le même que l' entrée, aucun traitement spécial est nécessaire (x86 n'avoir un traitement spécial, mais seulement parce qu'il a une sortie dans une paire de registres, mais notez que cette installation est en fait assez rarement utilisé, serait donc un candidat plus évident à laisser hors d'un processeur que les types non signés)
  • DIV - aucun changement requis
  • Passez d'un type plus petit à un type plus grand - vous devez ajouter MOVZX, déplacer avec l'extension zéro. Notez que MOVZX est extrêmement simple à implémenter.
  • Branche sur zéro - inchangée
  • Branche sur moins - saute lorsque le drapeau est porté.
  • Branche plus grande - saute si le drapeau de portage et zéro sont tous les deux dégagés.

Notez que dans chaque cas, les modifications sont très simples et peuvent être implémentées simplement en activant ou désactivant une petite section de circuits, ou en ajoutant un nouveau registre d'indicateur qui peut être contrôlé par une valeur qui doit être calculée dans le cadre de la mise en œuvre de l'instruction de toute façon.

Par conséquent, le coût de l'ajout d'instructions non signées est très faible . Quant à savoir pourquoi cela doit être fait , notez que les adresses mémoire (et les décalages dans les tableaux) sont des valeurs intrinsèquement non signées. Comme les programmes passent beaucoup de temps à manipuler les adresses mémoire, le fait d'avoir un type qui les gère correctement facilite l'écriture des programmes.

Periata Breatta
la source
merci, cela répond à ma question, le coût est faible et les instructions sont souvent utiles
jtw
1
La multiplication double-taille non signée est essentielle lors de l'arithmétique multi-précision, et est probablement bonne pour une amélioration de la vitesse globale meilleure que 2x lors du chiffrement RSA. De plus, la division est différente dans les cas signés et non signés, mais comme le cas non signé est plus facile et que la division est assez rare et suffisamment lente pour que l'ajout de quelques instructions ne nuise pas beaucoup, la chose la plus simple à faire serait d'implémenter uniquement la division non signée. puis envelopper avec une logique de gestion des signes.
supercat
2

Les nombres non signés existent en grande partie pour gérer les situations où l'on a besoin d'un anneau algébrique enveloppant (pour un type non signé 16 bits, ce serait l'anneau d'entiers congruents mod 65536). Prenez une valeur, ajoutez une quantité inférieure au module, et la différence entre les deux valeurs sera la quantité qui a été ajoutée. Par exemple, si un compteur d'utilité lit 9995 au début d'un mois et que l'on utilise 23 unités, le compteur affichera 0018 à la fin du mois. Lorsque vous utilisez un type d'anneau algébrique, il n'est pas nécessaire de faire quoi que ce soit de spécial pour faire face au débordement. La soustraction de 9995 à 0018 donnera 0023, précisément le nombre d'unités qui ont été utilisées.

Sur le PDP-11, la machine pour laquelle C a été implémenté pour la première fois, il n'y avait pas de types entiers non signés, mais des types signés pouvaient être utilisés pour l'arithmétique modulaire qui était comprise entre 32767 et -32768 plutôt qu'entre 65535 et 0. Les instructions entières sur d'autres cependant, les plates-formes n’ont pas fait le tour plutôt que d'exiger que les implémentations doivent émuler les deux entiers complémentaires utilisés dans le PDP-11, le langage a plutôt ajouté des types non signés qui devaient principalement se comporter comme des anneaux algébriques, et a permis aux types entiers signés de se comporter d'autres manières en cas de débordement.

Dans les premiers jours de C, il y avait de nombreuses quantités qui pouvaient dépasser 32767 (le commun INT_MAX) mais pas 65535 (le commun UINT_MAX). Il est donc devenu courant d'utiliser des types non signés pour contenir de telles quantités (par exemple size_t). Malheureusement, rien dans le langage ne fait la distinction entre les types qui devraient se comporter comme des nombres avec un peu de plage positive, et les types qui devraient se comporter comme des anneaux algébriques. Au lieu de cela, le langage fait que les types plus petits que "int" se comportent comme des nombres tandis que les types de taille normale se comportent comme des anneaux algébriques. Par conséquent, la fonction d'appel comme:

uint32_t mul(uint16_t a, uint16_t b) { return a*b; }

avec (65535, 65535) aura un comportement défini sur les systèmes où intest 16 bits (c'est-à-dire retour 1), un comportement différent où intest 33 bits ou plus (retour 0xFFFE0001), et un comportement indéfini sur les systèmes où "int" est n'importe où dans entre [notez que gcc donnera généralement des résultats arithmétiquement corrects avec des résultats entre INT_MAX + 1u et UINT_MAX, mais générera parfois du code pour la fonction ci-dessus qui échoue avec de telles valeurs!]. Pas très utile.

Pourtant, le manque de types qui se comportent toujours comme des nombres ou comme un anneau algébrique ne change pas le fait que les types d'anneaux algébriques sont presque indispensables pour certains types de programmation.

supercat
la source