Pourquoi C ++ fait-il la promotion d’un int à un flottant lorsqu’un flottant ne peut pas représenter toutes les valeurs int?

Disons que j’ai ce qui suit:

int i = 23; float f = 3.14; if (i == f) // do something 

i serai promu à un float et les deux nombres float seront comparés, mais un float peut-il représenter toutes les valeurs int ? Pourquoi ne pas promouvoir à la fois l’ int et le float à un double ?

Lorsque int est promu unsigned dans les promotions intégrales, les valeurs négatives sont également perdues (ce qui conduit à un niveau de plaisir tel que 0u < -1 étant vrai).

Comme la plupart des mécanismes de C (hérités en C ++), les conversions arithmétiques habituelles doivent être comsockets en termes d'opérations matérielles. Les créateurs de C connaissaient très bien le langage d'assemblage des machines avec lesquelles ils travaillaient, et ils ont écrit à C pour se faire une idée immédiate et pour les gens qui s'aiment lorsqu'ils écrivent des choses jusque-là écrites en assembleur (comme UNIX). kernel).

Maintenant, les processeurs, en règle générale, n’ont pas d’instructions de type mixte (ajout de float à double, comparaison entre int et float, etc.) car ce serait un gaspillage énorme de biens immobiliers sur la tranche - vous devez implémenter autant d'opcodes que vous souhaitez prendre en charge différents types. Le fait de n'avoir que des instructions pour "append int à int", "comparer à float à float", "multiplier les unsigned avec unsigned", etc. rend les conversions arithmétiques habituelles nécessaires - elles correspondent à deux types de l'instruction famille qui a le plus de sens à utiliser avec eux.

Du sharepoint vue de quelqu'un qui a l'habitude d'écrire du code machine de bas niveau, si vous avez des types mixtes, les instructions de l'assembleur que vous envisagez le plus dans le cas général sont celles qui nécessitent le moins de conversions. C'est particulièrement le cas avec les virgules flottantes, où les conversions sont coûteuses en exécution, et particulièrement au début des années 1970, lorsque C était développé, les ordinateurs étaient lents et les calculs en virgule flottante étaient exécutés dans le logiciel. Cela apparaît dans les conversions arithmétiques habituelles - un seul opérande est converti (à la seule exception de long / unsigned int , où le long peut être converti en unsigned long , ce qui ne nécessite aucune opération sur la plupart des machines). sur tout où l'exception s'applique).

Ainsi, les conversions arithmétiques habituelles sont écrites pour faire ce que ferait un codeur d'assemblage la plupart du temps: vous avez deux types qui ne rentrent pas, convertissez-les l'un à l'autre pour que ce soit le cas. C'est ce que vous feriez dans le code assembleur, sauf si vous aviez une raison spécifique et que les personnes habituées à écrire du code assembleur avaient une raison spécifique pour forcer une conversion différente, demandant explicitement que la conversion soit naturelle. Après tout, vous pouvez simplement écrire

 if((double) i < (double) f) 

Il est intéressant de noter, dans ce contexte, que unsigned est plus haut dans la hiérarchie int , de sorte que la comparaison de int avec unsigned se terminera par une comparaison non signée (d'où le bit 0u < -1 ). Je soupçonne que cela soit un indicateur que les personnes dans les temps anciens considéraient comme moins ressortingctives sur int que comme une extension de sa plage de valeurs: nous n'avons pas besoin du signe maintenant, donc utilisons le bit supplémentaire pour une plus grande plage de valeurs . Vous l'utiliseriez si vous aviez des raisons de penser qu'un int déborderait - un souci beaucoup plus grand dans un monde de 16 bits int .

Même le double ne peut pas représenter toutes les valeurs int , en fonction de la quantité de bits que contient int .

Pourquoi ne pas promouvoir à la fois l’int et le float à un double?

Probablement parce qu’il est plus coûteux de convertir les deux types en double que d’utiliser l’un des opérandes, qui est déjà un float , en tant que float . Il introduirait également des règles spéciales pour les opérateurs de comparaison incompatibles avec les règles pour les opérateurs arithmétiques.

Il n’y a pas non plus de garantie sur la manière dont les types à virgule flottante seront représentés, il serait donc aveugle de supposer que la conversion de int en double (ou même long double ) à des fins de comparaison résoudra tout.

Les règles de promotion de type sont conçues pour être simples et fonctionner de manière prévisible. Les types en C / C ++ sont naturellement “sortingés” par la plage de valeurs qu’ils peuvent représenter. Voir ceci pour plus de détails. Bien que les types à virgule flottante ne puissent pas représenter tous les entiers représentés par des types intégraux, car ils ne peuvent pas représenter le même nombre de chiffres significatifs, ils peuvent représenter une plage plus large.

Pour avoir un comportement prévisible, lorsqu’on exige des promotions de type, les types numériques sont toujours convertis dans le type avec la plage la plus large pour éviter le débordement dans le plus petit. Imagine ça:

 int i = 23464364; // more digits than float can represent! float f = 123.4212E36f; // larger range than int can represent! if (i == f) { /* do something */ } 

Si la conversion a été faite vers le type intégral, le float f serait certainement débordé lors de la conversion en int, conduisant à un comportement indéfini. En revanche, la conversion de i en f ne provoque qu’une perte de précision, ce qui est sans importance puisque f a la même précision, il est donc toujours possible que la comparaison réussisse. Il appartient au programmeur d’interpréter le résultat de la comparaison en fonction des besoins de l’application.

Enfin, outre le fait que les nombres à virgule flottante double précision souffrent du même problème de représentation des nombres entiers (nombre limité de chiffres significatifs), utiliser la promotion sur les deux types conduirait à une représentation de précision plus élevée pour i , précision, donc la comparaison ne réussira pas si i un chiffre plus important que f pour commencer. Maintenant, c’est aussi un comportement indéfini: la comparaison peut réussir pour certains couples ( i , f ) mais pas pour d’autres.

un float peut-il représenter toutes les valeurs int ?

Pour un système moderne typique où les deux int et float sont stockés dans 32 bits, non. Quelque chose doit donner. La valeur des entiers de 32 bits ne correspond pas à 1 pour 1 sur un ensemble de même taille incluant des fractions.

Le i sera promu à un float et les deux nombres float seront comparés…

Pas nécessairement. Vous ne savez pas vraiment quelle précision sera appliquée. C ++ 14 §5 / 12:

Les valeurs des opérandes flottants et les résultats des expressions flottantes peuvent être représentés avec une précision et une scope supérieures à celles requirejses par le type; les types ne sont pas modifiés par cela.

Bien que i après la promotion ait un type nominal float , la valeur peut être représentée en utilisant du matériel double . C ++ ne garantit pas la perte ou le débordement de précision à virgule flottante. (Ce n’est pas nouveau dans C ++ 14; il est hérité de C depuis les temps anciens.)

Pourquoi ne pas promouvoir à la fois l’ int et le float à un double ?

Si vous voulez une précision optimale partout, utilisez plutôt le double et vous ne verrez jamais de float . Ou long double , mais cela pourrait ralentir. Les règles sont conçues pour être relativement sensibles pour la majorité des cas d’utilisation de types à précision limitée, étant donné qu’une machine peut offrir plusieurs précisions alternatives.

La plupart du temps, rapide et décontracté est suffisant, de sorte que la machine est libre de faire tout ce qui est le plus facile. Cela peut signifier une comparaison arrondie, à simple précision, ou une double précision et aucun arrondi.

Mais, ces règles sont finalement des compromis, et parfois elles échouent. Pour spécifier précisément l’arithmétique en C ++ (ou C), il est utile de rendre les conversions et les promotions explicites. De nombreux guides de style pour des logiciels extrêmement fiables interdisent toute utilisation des conversions implicites, et la plupart des compilateurs proposent des avertissements pour vous aider à les supprimer.

Pour en savoir plus sur la manière dont ces compromis ont été obtenus, vous pouvez parcourir le document de justification C. (La dernière édition couvre jusqu’à C99.) Ce n’est pas seulement un bagage insensé de l’époque du PDP-11 ou du K & R.

Il est fascinant qu’un certain nombre de réponses soutiennent ici l’origine du langage C, en nommant explicitement K & R et le bagage historique comme raison pour laquelle un int est converti en flottant lorsqu’il est combiné avec un flottant.

Cela met le blâme sur les mauvaises parties. Dans K & R C, le calcul du flottant n’existait pas. Toutes les opérations en virgule flottante ont été effectuées en double précision. Pour cette raison, un entier (ou toute autre chose) n’a jamais été implicitement converti en flottant, mais seulement en double. Un float ne pourrait pas non plus être le type d’un argument de fonction: vous deviez passer un pointeur à flotter si vous vouliez vraiment, vraiment, éviter la conversion en double. Pour cette raison, les fonctions

 int x(float a) { ... } 

et

 int y(a) float a; { ... } 

avoir des conventions d’appel différentes. Le premier reçoit un argument float, le second (désormais interdit comme syntaxe) obtient un double argument.

L’arithmétique à virgule flottante simple précision et les arguments de fonction n’ont été introduits qu’avec ANSI C. Kernighan / Ritchie est innocent.

Maintenant, avec les nouvelles expressions à flotteur unique disponibles (le flottement simple n’était auparavant qu’un format de stockage), il devait également y avoir de nouvelles conversions de type. Peu importe ce que l’équipe d’ANSI C a choisi ici (et je serais à court d’un meilleur choix), ce n’est pas la faute de K & R.

Q1: Un float peut-il représenter toutes les valeurs int?

IEE754 peut représenter tous les entiers exactement comme des flottants, jusqu’à environ 2 23 , comme mentionné dans cette réponse .

Q2: Pourquoi ne pas promouvoir à la fois l’int et le float à un double?

Les règles de la norme pour ces conversions sont de légères modifications de celles de K & R: les modifications tiennent compte des types ajoutés et des règles de préservation de la valeur. Une licence explicite a été ajoutée pour effectuer des calculs d’un type «plus large» qu’absolument nécessaire, car cela peut parfois produire un code plus petit et plus rapide, sans parler de la réponse correcte plus souvent. Les calculs peuvent également être effectués dans un type «plus étroit» par la règle as if tant que le même résultat final est obtenu. La diffusion explicite peut toujours être utilisée pour obtenir une valeur dans le type souhaité.

La source

Effectuer des calculs dans un type plus large signifie que float f1; donné float f1; et float f2; , f1 + f2 pourrait être calculé en double précision. Et cela signifie que donné int i; et float f; , i == f pourrait être calculé en double précision. Mais il n’est pas nécessaire de calculer i == f en double précision, comme le précise hvd dans le commentaire.

La norme C le dit aussi. Ce sont les conversions arithmétiques habituelles. La description suivante est tirée directement de la norme ANSI C.

… si l’un des opérandes a le type float, l’autre opérande est converti en type float.

Source et vous pouvez le voir dans la référence aussi.

Un lien pertinent est cette réponse . Une source plus analytique est ici .

Voici une autre manière d’expliquer ceci: Les conversions arithmétiques habituelles sont implicitement effectuées pour convertir leurs valeurs dans un type commun. Le compilateur effectue d’abord une promotion entière, si les opérandes ont toujours des types différents, ils sont convertis dans le type qui apparaît le plus haut dans la hiérarchie suivante:

entrer la description de l'image ici

Source

Lorsqu’un langage de programmation est créé, certaines décisions sont sockets intuitivement.

Par exemple, pourquoi ne pas convertir int + float en int + int au lieu de float + float ou double + double? Pourquoi appeler int-> float une promotion si elle contient le même nombre de bits? Pourquoi ne pas appeler float-> int une promotion?

Si vous utilisez des conversions de types implicites, vous devez savoir comment elles fonctionnent, sinon convertissez-les manuellement.

Certains langages auraient pu être conçus sans aucune conversion automatique de type. Et toutes les décisions sockets lors d’une phase de conception n’auraient pas pu être logiquement sockets avec une bonne raison.

Le JavaScript avec son typage de canard a des décisions encore plus obscures sous le capot. Concevoir un langage absolument logique est impossible, je pense que cela va au théorème d’incomplétude de Godel. Vous devez équilibrer la logique, l’intuition, la pratique et les idéaux.

La question est de savoir pourquoi: Parce qu’il est rapide, facile à expliquer, facile à comstackr et ce sont toutes des raisons très importantes au moment du développement du langage C.

Vous auriez pu avoir une règle différente: pour chaque comparaison de valeurs arithmétiques, le résultat est la comparaison des valeurs numériques réelles. Ce serait quelque chose entre sortingvial si une des expressions comparées est une constante, une instruction supplémentaire quand on compare les signes sign et unsigned int et assez difficile si vous comparez long long et double et voulez des résultats corrects lorsque le long long ne peut pas être représenté comme double. (0u <-1 serait faux, car il comparerait les valeurs numériques 0 et -1 sans tenir compte de leurs types).

Dans Swift, le problème est résolu facilement en interdisant les opérations entre différents types.

Les règles sont écrites pour 16 bits (la plus petite taille requirejse). Votre compilateur avec 32 bits ints convertit sûrement les deux côtés pour doubler. De toute façon, il n’y a pas de registre flottant dans le matériel moderne, il faut donc le convertir en double. Maintenant, si vous avez 64 bits, je ne sais pas trop ce que c’est. long double serait approprié (normalement 80 bits mais ce n’est même pas standard).