Quel est l’intérêt de float_t et quand doit-il être utilisé?

Je travaille avec un client qui utilise une ancienne version de GCC (3.2.3 pour être précis) mais souhaite mettre à niveau et une des raisons invoquées comme obstacle à la mise à niveau vers une version plus récente est la différence de taille du type float_t qui, bien sûr, est correct:

Sur GCC 3.2.3

 sizeof(float_t) = 12 sizeof(float) = 4 sizeof(double_t) = 12 sizeof(double) = 8 

Sur GCC 4.1.2

 sizeof(float_t) = 4 sizeof(float) = 4 sizeof(double_t) = 8 sizeof(double) = 8 

mais quelle est la raison de cette différence? Pourquoi la taille est-elle float_t plus petite et quand faut-il utiliser ou ne pas utiliser float_t ou double_t ?

Float_t s’explique par le fait que, pour certains processeurs et compilateurs utilisant un type plus grand, par exemple, double long pour float pourrait être plus efficace. Ainsi, float_t permet au compilateur d’utiliser le type le plus grand au lieu de float.

Ainsi, dans le cas des OP utilisant float_t, le changement de taille correspond à ce que permet la norme. Si le code d’origine souhaitait utiliser les tailles de flottant les plus petites, il devrait utiliser float.

Il y a quelques raisons dans open-std doc

Par exemple, les définitions de type float_t et double_t (définies dans ) sont destinées à permettre une utilisation efficace d’architectures avec des formats plus efficaces et plus larges. Annexes

Le “pourquoi” est que certains compilateurs renverront des valeurs à virgule flottante dans un registre à virgule flottante. Ces registres ont une seule taille. Par exemple, sur X86, sa largeur est de 80 bits. Les résultats d’une fonction qui renvoie une valeur à virgule flottante seront placés dans ce registre, que le type ait été déclaré ou non comme flottant, double, float_t ou double_t. Si la taille de la valeur de retour et la taille du registre à virgule flottante diffèrent, une instruction sera nécessaire à un moment donné pour arrondir à la taille souhaitée.

Le même type de conversion est également nécessaire pour les entiers, mais pour les ajouts et les soustractions ultérieurs, il n’y a pas de surcharge, car il existe des instructions permettant de choisir les octets à impliquer dans l’opération. Les règles de conversion d’entiers en une taille plus petite spécifient que les bits les plus significatifs doivent être éliminés, de sorte que le résultat de la réduction des effectifs peut produire un résultat radicalement différent (par exemple (court) (2147450880) -> -32768). une raison qui semble convenir à la communauté de programmation.

Lors de la réduction des effectifs en virgule flottante, le résultat est spécifié pour être arrondi au nombre représentable le plus proche. Si les entiers étaient soumis aux mêmes règles, l’exemple ci-dessus tronquerait ainsi (short) (2147450880) -> +32767. De toute évidence, un peu plus de logique est nécessaire pour effectuer une opération telle que la simple troncature des bits supérieurs. Avec la virgule flottante, l’exposant et le significande changent de taille entre float, double et long double, donc c’est plus compliqué. De plus, il faut prendre en compte les problèmes de conversion entre l’infini, le NaN, les nombres normalisés et les nombres renormalisés. Le matériel peut implémenter ces conversions dans le même temps qu’une addition d’entier, mais si la conversion doit être implémentée dans un logiciel, cela peut prendre 20 instructions, ce qui peut avoir un effet notable sur les performances. Étant donné que le modèle de programmation C garantit que les mêmes résultats sont générés, que la virgule flottante soit implémentée matériellement ou logiquement, le logiciel est obligé d’exécuter ces instructions supplémentaires afin de se conformer au modèle de calcul. Les types float_t et double_t ont été conçus pour exposer le type de valeur de retour le plus efficace .

Le compilateur définit un FLT_EVAL_METHOD , qui spécifie combien de précision doit être utilisée dans les calculs intermédiaires. Pour les entiers, la règle consiste à effectuer des calculs intermédiaires en utilisant la plus grande précision des opérandes impliqués. Cela correspondrait à un FLT_EVAL_METHOD == 0. Cependant, le K & R d’origine spécifiait que tous les calculs intermédiaires devaient être effectués en double, donnant ainsi FLT_EVAL_METHOD == 1. Cependant, avec l’introduction du standard à virgule flottante IEEE, il est devenu courant sur certaines plates-formes, notamment les Macintosh PowerPC et Windows X86, de réaliser des calculs intermédiaires en long double – 80 bits, donnant ainsi FLT_EVAL_METHOD == 2.

Les tests de régression seront affectés par le modèle de calcul FLT_EVAL_METHOD . Ainsi, votre code de régression devrait en tenir compte. Une solution consiste à tester FLT_EVAL_METHOD et à avoir différentes twigs pour chaque modèle. Une méthode similaire consisterait à tester sizeof (float_t) et à avoir des twigs différentes. Une troisième méthode consisterait à utiliser une sorte d’epsilon pour vérifier si les résultats sont suffisamment proches.

Malheureusement, certains calculs prennent une décision basée sur les résultats d’un calcul, aboutissant à un résultat vrai ou faux , qui ne peuvent pas être résolus à l’aide d’un epsilon. Cela se produit, par exemple, en infographie pour décider si un point se trouve à l’intérieur ou à l’extérieur d’un polygone, ce qui détermine si un pixel particulier doit être rempli. Si votre régression implique l’un de ceux-ci, vous ne pouvez pas utiliser la méthode epsilon et devez utiliser différentes twigs en fonction du modèle de calcul.

Une autre façon de résoudre la régression décisionnelle entre les modèles consiste à convertir explicitement le résultat en une précision souhaitée. Cela fonctionne la plupart du temps sur de nombreux compilateurs, mais certains d’entre eux pensent qu’ils sont plus intelligents que vous et refusent d’effectuer la conversion. Cela se produit dans le cas où un résultat intermédiaire est stocké dans un registre mais utilisé dans un calcul ultérieur. Vous pouvez utiliser autant de précision que vous le souhaitez dans le résultat intermédiaire, mais le compilateur ne fera rien – à moins de déclarer le résultat intermédiaire volatil . Cela oblige ensuite le compilateur à réduire la taille et à stocker le résultat intermédiaire dans une variable de la taille spécifiée en mémoire , puis à le récupérer lorsque cela est nécessaire pour le calcul. La norme de virgule flottante IEEE est exacte pour les opérations élémentaires (+ – * /) et la racine carrée. Je crois que sin (), cos (), exp (), log (), etc. sont spécifiés pour être situés à moins de 2 ULP (unités situées dans la position la moins significative) du résultat numérique le plus proche pouvant être représenté. Le format long double (80 bits) a été conçu pour permettre le calcul de ces autres fonctions transcendantales exactement au résultat numérique le plus proche.

Cela couvre beaucoup des problèmes soulevés (et impliqués) dans ce fil, mais ne répond pas à la question de savoir quand utiliser les types float_t et double_t. De toute évidence, vous devez le faire lorsque vous vous connectez à une API qui utilise ces types, en particulier lorsque vous transmettez l’adresse de l’un de ces types.

Si votre principale préoccupation concerne les performances, envisagez d’utiliser les types float_t et double_t dans vos calculs et vos API. Mais il est fort probable que l’augmentation de la performance obtenue ne soit ni mesurable ni perceptible.

Toutefois, si vous êtes préoccupé par la régression entre différents compilateurs et différentes machines, évitez au maximum ces types et utilisez la transtypage de manière généralisée pour assurer la compatibilité entre plates-formes.

La norme C99 dit:

Les types float_t double_t

Les types flottants sont au moins aussi larges que float et double, et tels que double_t est au moins aussi large que float_t . Si FLT_EVAL_METHOD est égal à 0 , float_t et double_t sont respectivement float et double ; si FLT_EVAL_METHOD est égal à 1 , ils sont tous deux double ; si FLT_EVAL_METHOD est égal à 2 , ils sont tous deux long double ; et pour les autres valeurs de FLT_EVAL_METHOD , elles sont par ailleurs définies par l’implémentation.178)

Et en effet, dans les versions précédentes de gcc, ils étaient définis comme long double par défaut.