Pourquoi C n'a-t-il pas de flotteurs non signés?

131

Je sais, la question semble étrange. Les programmeurs pensent parfois trop. Veuillez lire la suite ...

En CI, utilisez signedet unsignedentiers beaucoup. J'aime le fait que le compilateur me prévienne si je fais des choses comme assigner un entier signé à une variable non signée. Je reçois des avertissements si je compare des entiers signés avec des entiers non signés et bien plus encore.

J'aime ces avertissements. Ils m'aident à garder mon code correct.

Pourquoi n'avons-nous pas le même luxe pour les flotteurs? Une racine carrée ne renverra certainement jamais un nombre négatif. Il existe également d'autres endroits où une valeur flottante négative n'a aucune signification. Candidat parfait pour un flotteur non signé.

Btw - Je ne suis pas vraiment intéressé par le seul petit plus de précision que je pourrais obtenir en supprimant le bit de signe des flotteurs. Je suis super content des floats tels qu'ils sont en ce moment. Je voudrais juste marquer un flottant comme non signé parfois et obtenir le même genre d'avertissements que je reçois avec des entiers.

Je ne connais aucun langage de programmation prenant en charge les nombres à virgule flottante non signés.

Une idée de pourquoi ils n'existent pas?


ÉDITER:

Je sais que le FPU x87 n'a pas d'instructions pour traiter les flotteurs non signés. Utilisons simplement les instructions de flotteur signées. Une mauvaise utilisation (par exemple, passer en dessous de zéro) pourrait être considérée comme un comportement indéfini de la même manière qu'un débordement d'entiers signés n'est pas défini.

Nils Pipenbrinck
la source
4
Intéressant, pouvez-vous publier un exemple de cas où la vérification du type de signature a été utile?
litb, votre commentaire était-il dirigé contre moi? si oui, je ne comprends pas
Iraimbilanja yeah :) fabs ne peut pas renvoyer un nombre négatif, car il renvoie la valeur absolue de son argument
Johannes Schaub - litb
Je n'ai pas demandé comment un flottement hypothétique non signé pouvait aider à la correction.ce que j'ai demandé était: dans quelle situation pipenbrinck a-t-il trouvé la vérification de type de signature Int utile (l'amenant à rechercher le même mécanisme pour les flotteurs). en ce qui concerne la sécurité du type
1
Il existe une micro-optimisation non signée pour la vérification de point dans la plage: ((unsigned) (p-min)) <(max-min), qui n'a qu'une seule branche, mais, comme toujours, il est préférable de profiler pour voir si cela aide vraiment (je l'ai principalement utilisé sur 386 cœurs, donc je ne sais pas comment les processeurs modernes font face).
Skizz

Réponses:

114

La raison pour laquelle C ++ ne prend pas en charge les flottants non signés est qu'il n'y a pas d'opérations de code machine équivalentes que le CPU doit exécuter. Il serait donc très inefficace de l'appuyer.

Si C ++ le supportait, alors vous utiliseriez parfois un flottant non signé sans vous rendre compte que votre performance vient d'être supprimée. Si C ++ le supportait, chaque opération en virgule flottante devrait être vérifiée pour voir si elle est signée ou non. Et pour les programmes qui effectuent des millions d'opérations en virgule flottante, ce n'est pas acceptable.

La question serait donc de savoir pourquoi les implémenteurs de matériel ne le prennent pas en charge. Et je pense que la réponse à cela est qu'il n'y avait pas de norme de flotteur non signée définie à l'origine. Puisque les langues aiment être rétrocompatibles, même si elles étaient ajoutées, les langues ne pourraient pas s'en servir. Pour voir la spécification en virgule flottante, vous devriez regarder la norme IEEE 754 Floating-Point .

Vous pouvez contourner le fait de ne pas avoir de type virgule flottante non signé en créant une classe float non signée qui encapsule un float ou un double et émet des avertissements si vous essayez de passer un nombre négatif. C'est moins efficace, mais probablement si vous ne les utilisez pas intensément, vous ne vous soucierez pas de cette légère perte de performance.

Je vois vraiment l'utilité d'avoir un flotteur non signé. Mais C / C ++ a tendance à choisir l'efficacité qui convient le mieux à tout le monde plutôt que la sécurité.

Brian R. Bondy
la source
17
C / C ++ ne nécessite pas d'opérations de code machine spécifiques pour implémenter le langage. Les premiers compilateurs C / C ++ pouvaient générer du code en virgule flottante pour le 386 - un processeur sans FPU! Le compilateur générerait des appels de bibliothèque pour émuler les instructions FPU. Par conséquent, un ufloat pourrait être effectué sans le support du processeur
Skizz
10
Skizz, bien que ce soit correct, Brian a déjà abordé cette question: parce qu'il n'y a pas de code machine équivalent, les performances seront horribles en comparaison.
Anthony
2
@Brian R. Bondy: Je vous ai perdu ici: "parce qu'il n'y a pas d'opérations de code machine équivalentes pour le CPU à exécuter ...". Pouvez-vous expliquer, en termes plus simples?
Lazer
2
La raison pour laquelle OP voulait prendre en charge les flottants non signés était pour les messages d'avertissement, donc vraiment cela n'a rien à voir avec la phase de génération de code du compilateur - seulement à voir avec la façon dont il vérifie le type au préalable - donc leur prise en charge dans le code machine n'est pas pertinente et (comme cela a été ajouté au bas de la question) des instructions normales en virgule flottante pourraient être utilisées pour l'exécution réelle.
Joe F
2
Je ne suis pas sûr de comprendre pourquoi cela devrait affecter les performances. Tout comme avec int's, toutes les vérifications de type liées aux signes peuvent avoir lieu au moment de la compilation. OP suggère que unsigned floatcela serait implémenté de manière régulière floatavec des vérifications au moment de la compilation pour s'assurer que certaines opérations non significatives ne sont jamais effectuées. Le code machine et les performances qui en résultent peuvent être identiques, que vos flottants soient signés ou non.
xanderflood
14

Il existe une différence significative entre les entiers signés et non signés en C / C ++:

value >> shift

les valeurs signées laissent le bit supérieur inchangé (extension de signe), les valeurs non signées effacent le bit supérieur.

La raison pour laquelle il n'y a pas de flottant non signé est que vous rencontrez rapidement toutes sortes de problèmes s'il n'y a pas de valeurs négatives. Considère ceci:

float a = 2.0f, b = 10.0f, c;
c = a - b;

Quelle est la valeur de c? -8. Mais qu'est-ce que cela signifierait dans un système sans nombres négatifs. FLOAT_MAX - 8 peut-être? En fait, cela ne fonctionne pas car FLOAT_MAX - 8 est FLOAT_MAX en raison des effets de précision, donc les choses sont encore plus délicates. Et si cela faisait partie d'une expression plus complexe:

float a = 2.0f, b = 10.0f, c = 20.0f, d = 3.14159f, e;
e = (a - b) / d + c;

Ce n'est pas un problème pour les entiers en raison de la nature du système de complément à 2.

Considérez également les fonctions mathématiques standard: sin, cos et tan ne fonctionneraient que pour la moitié de leurs valeurs d'entrée, vous ne pouviez pas trouver le journal des valeurs <1, vous ne pouviez pas résoudre les équations quadratiques: x = (-b +/- racine ( bb - 4.ac)) / 2.a, et ainsi de suite. En fait, cela ne fonctionnerait probablement pas pour une fonction complexe car celles-ci ont tendance à être implémentées comme des approximations polynomiales qui utiliseraient des valeurs négatives quelque part.

Ainsi, les flotteurs non signés sont assez inutiles.

Mais cela ne veut pas dire qu'une classe dont la plage vérifie les valeurs flottantes n'est pas utile, vous pouvez vouloir limiter les valeurs à une plage donnée, par exemple les calculs RVB.

Skizz
la source
@Skizz: si la représentation est un problème, vous voulez dire que si quelqu'un peut concevoir une méthode pour stocker des flottants aussi efficace que 2's complement, il n'y aura pas de problème avec des flottants non signés?
Lazer
3
value >> shift for signed values leave the top bit unchanged (sign extend) Êtes-vous sûr de cela? Je pensais que c'était un comportement défini par l'implémentation, du moins pour les valeurs signées négatives.
Dan
@Dan: Je viens de regarder la norme récente et elle indique en effet qu'elle est définie par l'implémentation - je suppose que c'est juste au cas où il y aurait un processeur qui n'a pas de décalage à droite avec l'instruction d'extension de signe.
Skizz
1
la virgule flottante sature traditionnellement (vers - / + Inf) au lieu de l'enroulement. Vous pouvez vous attendre à ce que le débordement de soustraction non signée soit saturé 0.0, ou éventuellement Inf ou NaN. Ou soyez simplement un comportement indéfini, comme l'OP suggéré dans une modification de la question. Re: fonctions trigonométriques: ne définissez donc pas les versions d'entrée non signées de sinet ainsi de suite, et assurez-vous de traiter leur valeur de retour comme signée. La question ne proposait pas de remplacer float par float non signé, mais simplement en ajoutant unsigned floatun nouveau type.
Peter Cordes
9

(En passant, Perl 6 vous permet d'écrire

subset Nonnegative::Float of Float where { $_ >= 0 };

et ensuite vous pouvez utiliser Nonnegative::Floatcomme vous le feriez pour tout autre type.)

Il n'y a pas de support matériel pour les opérations en virgule flottante non signées, donc C ne le propose pas. C est principalement conçu pour être un «assemblage portable», c'est-à-dire aussi près du métal que possible sans être attaché à une plate-forme spécifique.

[Éditer]

C est comme l'assemblage: ce que vous voyez est exactement ce que vous obtenez. Un implicite "Je vais vérifier que ce flotteur n'est pas négatif pour vous" va à l'encontre de sa philosophie de conception. Si vous le voulez vraiment, vous pouvez ajouter assert(x >= 0)ou similaire, mais vous devez le faire explicitement.

éphémère
la source
svn.perl.org/parrot/trunk/languages/perl6/docs/STATUS dit oui, mais n'analyseof ... pas.
éphémère le
8

Je crois que l'int non signé a été créé en raison de la nécessité d'une marge de valeur plus grande que celle que l'int signataire pourrait offrir.

Un flotteur a une marge beaucoup plus grande, il n'y a donc jamais eu de besoin «physique» d'un flotteur non signé. Et comme vous le faites remarquer vous-même dans votre question, la précision supplémentaire de 1 bit n'est pas à tuer.

Edit: Après avoir lu la réponse de Brian R. Bondy , je dois modifier ma réponse: il a tout à fait raison de dire que les processeurs sous-jacents n'avaient pas d'opérations flottantes non signées. Cependant, je maintiens ma conviction qu'il s'agissait d'une décision de conception basée sur les raisons que j'ai énoncées ci-dessus ;-)

Treb
la source
2
En outre, l'addition et la soustraction d'entiers sont les mêmes signés ou non signés - virgule flottante, pas tellement. Qui ferait le travail supplémentaire pour prendre en charge les flottants signés et non signés étant donné l'utilité marginale relativement faible d'une telle fonctionnalité?
éphémère
7

Je pense que Treb est sur la bonne voie. Il est plus important pour les entiers que vous ayez un type correspondant non signé. Ce sont ceux qui sont utilisés dans le décalage de bits et utilisés dans les cartes de bits . Un petit signe vient juste sur le chemin. Par exemple, en décalant vers la droite une valeur négative, la valeur résultante est une implémentation définie en C ++. Faire cela avec un entier non signé ou déborder de celui-ci a une sémantique parfaitement définie car il n'y a pas de bit de ce type.

Donc, pour les entiers au moins, le besoin d'un type non signé séparé est plus fort que de simplement donner des avertissements. Tous les points ci-dessus ne doivent pas être pris en compte pour les flotteurs. Donc, je pense qu'il n'y a pas vraiment besoin de support matériel pour eux, et C ne les supportera déjà pas à ce stade.

Johannes Schaub - litb
la source
5

Une racine carrée ne renverra certainement jamais un nombre négatif. Il existe également d'autres endroits où une valeur flottante négative n'a aucune signification. Candidat parfait pour un flotteur non signé.

C99 prend en charge les nombres complexes et une forme générique de type sqrt, donc sqrt( 1.0 * I)sera négatif.


Les commentateurs ont mis en évidence une légère brillance ci-dessus, en ce que je faisais référence à la sqrtmacro de type générique plutôt qu'à la fonction, et elle renverra une valeur à virgule flottante scalaire par troncature du complexe à son composant réel:

#include <complex.h>
#include <tgmath.h>

int main () 
{
    complex double a = 1.0 + 1.0 * I;

    double f = sqrt(a);

    return 0;
}

Il contient également un brain-pet, car la partie réelle du sqrt de tout nombre complexe est positive ou nulle, et sqrt (1.0 * I) est sqrt (0.5) + sqrt (0.5) * I not -1.0.

Pete Kirkham
la source
Oui, mais vous appelez une fonction avec un nom différent si vous travaillez avec des nombres complexes. Le type de retour est également différent. Bon point cependant!
Nils Pipenbrinck le
4
Le résultat de sqrt (i) est un nombre complexe. Et comme les nombres complexes ne sont pas ordonnés, vous ne pouvez pas dire qu'un nombre complexe est négatif (c'est-à-dire <0)
quinmars
1
quinmars, bien sûr que ce n'est pas csqrt? ou parlez-vous de mathématiques au lieu de C? Je suis d'accord quand même que c'est un bon point :)
Johannes Schaub - litb
En effet, je parlais de mathématiques. Je n'ai jamais traité des nombres complexes en c.
quinmars le
1
"la racine carrée ne renverra certainement jamais un nombre négatif." -> sqrt(-0.0)produit souvent -0.0. Bien sûr, -0,0 n'est pas une valeur négative .
chux
4

Je suppose que cela dépend du fait que seules les spécifications à virgule flottante IEEE sont signées et que la plupart des langages de programmation les utilisent.

Article Wikipédia sur les nombres à virgule flottante IEEE-754

Edit: De plus, comme indiqué par d'autres, la plupart du matériel ne prend pas en charge les flottants non négatifs, de sorte que le type normal de flotteurs est plus efficace à faire car il existe un support matériel.

Tobias Wärre
la source
C a été introduit bien avant l'apparition de la norme IEEE-754
phuclv
@phuclv Il n'y avait pas non plus de matériel en virgule flottante courant. Il a été adopté dans la norme C "quelques" années plus tard. Il y a probablement de la documentation sur Internet à ce sujet. (En outre, l'article de wikipedia mentionne C99).
Tobias Wärre
Je ne comprends pas ce que tu veux dire. Il n'y a pas de "matériel" dans votre réponse, et IEEE-754 est né après C, donc les types à virgule flottante en C ne peuvent pas dépendre de la norme IEEE-754, à moins que ces types
aient
@phuclv C est / était également connu sous le nom d'assemblage portable, il peut donc être assez proche du matériel. Les langages gagnent en fonctionnalités au fil des ans, même si (avant moi) float était implémenté en C, c'était probablement une opération logicielle et assez coûteuse. Au moment de répondre à cette question, j'avais évidemment une meilleure compréhension de ce que j'essayais d'expliquer que je ne le fais maintenant. Et si vous regardez la réponse acceptée, vous comprendrez peut-être pourquoi j'ai mentionné la norme IEE754. Ce que je ne comprends pas, c'est que vous avez choisi une réponse de 10 ans qui n'est pas celle acceptée?
Tobias Wärre
3

Je pense que la raison principale est que les flotteurs non signés auraient des utilisations vraiment limitées par rapport aux ints non signés. Je n'accepte pas l'argument selon lequel c'est parce que le matériel ne le prend pas en charge. Les anciens processeurs n'avaient aucune capacité en virgule flottante, tout était émulé dans le logiciel. Si des flotteurs non signés étaient utiles, ils auraient d'abord été implémentés dans le logiciel et le matériel aurait emboîté le pas.

Ferruccio
la source
4
Le PDP-7, la première plate-forme de C, avait une unité matérielle à virgule flottante en option. Le PDP-11, la prochaine plate-forme de C, avait un flotteur 32 bits dans le matériel. Le 80x86 est venu une génération plus tard, avec une technologie qui était derrière une génération.
éphémère le
3

Les types entiers non signés en C sont définis de manière à obéir aux règles d'un anneau algébrique abstrait. Par exemple, pour toute valeur X et Y, l'ajout de XY à Y donnera X. Les types entiers non signés sont garantis d'obéir à ces règles dans tous les cas qui n'impliquent pas de conversion vers ou à partir d'un autre type numérique [ou de types non signés de tailles différentes] , et cette garantie est l’une des caractéristiques les plus importantes de ces types. Dans certains cas, il vaut la peine de renoncer à la capacité de représenter des nombres négatifs en échange des garanties supplémentaires que seuls les types non signés peuvent offrir. Les types à virgule flottante, qu'ils soient signés ou non, ne peuvent pas respecter toutes les règles d'un anneau algébrique [par exemple, ils ne peuvent pas garantir que X + YY sera égal à X], et en effet IEEE ne le fait pas ' t leur permettre même de respecter les règles d'une classe d'équivalence [en exigeant que certaines valeurs se comparent inégales à elles-mêmes]. Je ne pense pas qu'un type à virgule flottante "non signé" puisse respecter les axiomes qu'un type à virgule flottante ordinaire ne pourrait pas, donc je ne suis pas sûr des avantages qu'il offrirait.

supercat
la source
1

IHMO c'est parce que la prise en charge des types à virgule flottante signés et non signés dans le matériel ou le logiciel serait trop gênant

Pour les types entiers, nous pouvons utiliser la même unité logique pour les opérations sur les entiers signés et non signés dans la plupart des situations en utilisant la belle propriété du complément de 2, car le résultat est identique dans ces cas pour les opérations add, sub, non-widening mul et la plupart des opérations au niveau du bit. Pour les opérations qui différencient la version signée et non signée, nous pouvons toujours partager la majorité de la logique . Par exemple

  • Le décalage arithmétique et logique n'a besoin que d'un léger changement dans le remplissage pour les bits supérieurs
  • L'élargissement de la multiplication peut utiliser le même matériel pour la partie principale, puis une logique distincte pour ajuster le résultat afin de modifier la signature . Non pas qu'il soit utilisé dans de vrais multiplicateurs mais c'est possible de le faire
  • La comparaison signée peut être convertie en comparaison non signée et vice versa facilement en basculant le bit supérieur ou en ajoutantINT_MIN . Également théoriquement possible, il n'est probablement pas utilisé sur le matériel, mais il est utile sur les systèmes qui ne prennent en charge qu'un seul type de comparaison (comme 8080 ou 8051)

Les systèmes qui utilisent le complément de 1 ont également juste besoin d'une petite modification de la logique car c'est simplement le bit de report enroulé autour du bit le moins significatif. Je ne suis pas sûr des systèmes de magnitude des signes, mais il semble qu'ils utilisent le complément de 1 en interne, donc la même chose s'applique

Malheureusement, nous n'avons pas ce luxe pour les types à virgule flottante. En libérant simplement le bit de signe, nous aurons la version non signée. Mais alors à quoi devrions-nous utiliser ce bit?

  • Augmentez la plage en l'ajoutant à l'exposant
  • Augmentez la précision en l'ajoutant à la mantisse. Ceci est souvent plus utile, car nous avons généralement besoin de plus de précision que de plage

Mais les deux choix nécessitent un additionneur plus grand pour s'adapter à la plage de valeurs plus large. Cela augmente la complexité de la logique alors que le bit supérieur de l'additionneur reste inutilisé la plupart du temps. Encore plus de circuits seront nécessaires pour les multiplications, les divisions ou d'autres opérations complexes

Sur les systèmes qui utilisent des logiciels à virgule flottante, vous avez besoin de 2 versions pour chaque fonction, ce qui n'était pas prévu pendant le temps où la mémoire était tellement chère, ou vous deviez trouver un moyen "délicat" de partager des parties des fonctions signées et non signées

Cependant, le matériel en virgule flottante existait bien avant l'invention de C , donc je pense que le choix en C était dû au manque de support matériel en raison de la raison que j'ai mentionnée ci-dessus

Cela dit, il existe plusieurs formats à virgule flottante non signés spécialisés , principalement à des fins de traitement d'image, comme le type à virgule flottante 10 et 11 bits du groupe Khronos.

phuclv
la source
0

Je soupçonne que c'est parce que les processeurs sous-jacents ciblés par les compilateurs C n'ont pas un bon moyen de traiter les nombres à virgule flottante non signés.

Brian Ensink
la source
Les processeurs sous-jacents avaient-ils un bon moyen de traiter les nombres à virgule flottante signés? C devenait populaire lorsque les processeurs auxiliaires à virgule flottante étaient idiosyncratiques et à peine universels.
David Thornley le
1
Je ne connais pas tous les délais historiques, mais il y avait un nouveau support matériel pour les flotteurs signés, bien que rare comme vous le faites remarquer. Les concepteurs de langage pourraient en incorporer la prise en charge tandis que les backends du compilateur avaient différents niveaux de prise en charge en fonction de l'architecture ciblée.
Brian Ensink le
0

Bonne question.

Si, comme vous le dites, ce n'est que pour les avertissements au moment de la compilation et aucun changement dans leur comportement, sinon le matériel sous-jacent n'est pas affecté et en tant que tel, il ne s'agirait que d'un changement C ++ / Compiler.

J'ai gagné la même chose auparavant, mais le fait est que cela n'aiderait pas beaucoup. Au mieux, le compilateur peut trouver des affectations statiques.

unsigned float uf { 0 };
uf = -1f;

Ou minimalistiquement plus

unsigned float uf { 0 };
float f { 2 };
uf -= f;

Mais c'est à peu près tout. Avec les types entiers non signés, vous obtenez également un wraparound défini, à savoir qu'il se comporte comme l'arithmétique modulaire.

unsigned char uc { 0 };
uc -= 1;

après ce «uc» détient la valeur de 255.

Maintenant, que ferait un compilateur avec le même scénario avec un type float non signé? Si les valeurs ne sont pas connues au moment de la compilation, il faudrait générer du code qui exécute d'abord les calculs, puis effectue une vérification de signature. Mais que se passe-t-il lorsque le résultat d'un tel calcul serait de dire "-5,5" - quelle valeur doit être stockée dans un flottant déclaré non signé? On pourrait essayer l'arithmétique modulaire comme pour les types intégraux, mais cela vient avec ses propres problèmes: la plus grande valeur est incontestablement l'infini .... cela ne fonctionne pas, vous ne pouvez pas avoir "infinity - 1". Opter pour la plus grande valeur distincte qu'il peut contenir ne fonctionnera pas vraiment non plus, car vous y rencontrez une précision. "NaN" serait un candidat.

Enfin, ce ne serait pas un problème avec les nombres à virgule fixe car le modulo est bien défini.

ABaumstumpf
la source