49 votes

Résultat non intuitif de l'attribution d'un nombre de double précision à une variable int en C

Quelqu'un pourrait-il me donner une explication pourquoi je obtenir deux différents numéros, resp. 14 et 15, ainsi qu'une sortie dans le code suivant?

#include <stdio.h>  

int main()
{
    double Vmax = 2.9; 
    double Vmin = 1.4; 
    double step = 0.1; 

    double a =(Vmax-Vmin)/step;
    int b = (Vmax-Vmin)/step;
    int c = a;

    printf("%d  %d",b,c);  // 14 15, why?
    return 0;
}

Je m'attends à obtenir 15 dans les deux cas, mais il semble que je ne suis pas certains fondamentaux de la langue.

Je ne suis pas sûr si c'est pertinent, mais j'ai été faire le test dans CodeBlocks. Cependant, si je tape les mêmes lignes de code dans une ligne compilateur ( celui-là par exemple), je reçois une réponse de 15 pour les deux imprimés variables.

45voto

chux Points 13185

... pourquoi je reçois deux numéros différents ...

Outre les habituels float-point de questions, le calcul des chemins d' b et c sont arrivés de différentes manières. c est calculé par la première économie de la valeur en tant que double a.

double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;

C permet l'intermédiaire des mathématiques à virgule flottante être calculé en utilisant plus large de types. Vérifier la valeur de FLT_EVAL_METHOD de <float.h>.

À l'exception de l'affectation et de la distribution (qui supprimer toutes supplément de portée et de précision), ...

-1 indéterminable;

0 évaluer toutes les opérations et les constantes juste pour la gamme et la précision de la type;

1 évaluer les opérations et les constantes de type float et double de la la gamme et la précision de l' double type, évaluer long double les opérations et les constantes de la gamme et la précision de l' long double type;

2 évaluer l'ensemble des opérations et des constantes de la gamme et la précision de la long double type.

C11dr §5.2.4.2.2 9

L'OP a rapporté 2

En enregistrant le quotient en double a = (Vmax-Vmin)/step;, la précision est forcé d' double alors qu' int b = (Vmax-Vmin)/step; peut calculer qu' long double.

Cette différence subtile résultats de (Vmax-Vmin)/step (calculé peut-être qu' long double) enregistrées sous forme d'un double contre restant long double. L'un comme l'15 (ou juste au-dessus), et l'autre un peu moins de 15. int troncature amplifie cette différence de 15 et 14.

Sur un autre compilateur, les résultats peuvent tous les deux avoir été la même en raison d' FLT_EVAL_METHOD < 2 ou autres à virgule flottante caractéristiques.


La Conversion en int à partir d'un nombre à virgule flottante est sévère avec les numéros de près d'un nombre entier. Souvent préférable d' round() ou lround(). La meilleure solution est dépendant de la situation.

26voto

cmaster Points 7460

C'est en effet une question intéressante, voici ce qui se passe précisément à votre matériel. Cette réponse donne les calculs exacts avec la précision de l'IEEE double de précision flotte, c'est à dire 52 bits de mantisse plus un implicite peu. Pour plus de détails sur la représentation, voir l' article de wikipedia.

Ok, donc vous devez d'abord définir quelques variables:

double Vmax = 2.9;
double Vmin = 1.4;
double step = 0.1;

Les valeurs respectives en binaire sera

Vmax =    10.111001100110011001100110011001100110011001100110011
Vmin =    1.0110011001100110011001100110011001100110011001100110
step = .00011001100110011001100110011001100110011001100110011010

Si vous compter les bits, vous verrez que j'ai donné le premier bit est défini, plus de 52 bits vers la droite. C'est exactement la précision à laquelle votre ordinateur stocke une double. Notez que la valeur de step a été arrondis.

Maintenant, vous faire quelques calculs sur ces nombres. La première opération, la soustraction, les résultats dans le résultat précis:

 10.111001100110011001100110011001100110011001100110011
- 1.0110011001100110011001100110011001100110011001100110
--------------------------------------------------------
  1.1000000000000000000000000000000000000000000000000000

Ensuite, vous divisez par step, qui a été arrondi par votre compilateur:

   1.1000000000000000000000000000000000000000000000000000
 /  .00011001100110011001100110011001100110011001100110011010
--------------------------------------------------------
1110.1111111111111111111111111111111111111111111111111100001111111111111

En raison de l'arrondissement des step, le résultat est un peu au-dessous 15. Contrairement à avant, j'ai pas arrondi immédiatement, parce que c'est précisément là que les choses intéressantes qui se passe: Votre CPU peut en effet stocker des nombres à virgule flottante de précision supérieure à un double, de sorte que l'arrondissement n'a pas lieu immédiatement.

Donc, quand vous convertir le résultat de l' (Vmax-Vmin)/step directement à un int, votre PROCESSEUR simplement de couper les bits après la fraction de point (c'est la façon dont l'implicite double -> int de conversion est défini par les normes linguistiques):

               1110.1111111111111111111111111111111111111111111111111100001111111111111
cutoff to int: 1110

Toutefois, si vous commencez stocker le résultat dans une variable de type double, l'arrondissement a lieu:

               1110.1111111111111111111111111111111111111111111111111100001111111111111
rounded:       1111.0000000000000000000000000000000000000000000000000
cutoff to int: 1111

Et c'est précisément le résultat que vous avez obtenu.

22voto

Steve Summit Points 16971

Le "simple" réponse, c'est que ceux apparemment simples numéros 2.9, 1.4, et de 0,1 sont tous représentés à l'interne comme à virgule flottante binaire et binaire, le nombre de 1/10 est représenté comme l'infiniment extensible fraction binaire 0.00011001100110011...[2] . (Ceci est analogue à la façon dont 1/3 en décimal finit par être 0.333333333... .) De nouveau converti en décimal, ces numéros d'origine à la fin des choses comme 2.8999999999, 1.3999999999, et 0.0999999999. Et quand vous faire des maths sur eux, ceux .0999999999 ont tendance à proliférer.

Et puis l'autre problème est que le chemin par lequel vous calculer quelque chose, que vous le stocker dans des variables intermédiaires d'un type particulier, ou de calculer le "tout à la fois", ce qui signifie que le processeur peut utiliser les registres internes avec plus de précision que le type double -- peut finir par faire une différence significative.

La ligne de fond est que lorsque vous convertissez un double retour à un int, vous souhaitez presque toujours ronde, ne pas tronquer. Ce qui s'est passé ici est que (en effet) un calcul de chemin vous a donné 15.0000000001 qui tronquée à 15, tandis que l'autre vous a donné 14.999999999 qui tronquée tout le chemin jusqu'à 14 ans.

Voir également la question de 14,4 un dans le C FAQ de la liste.

6voto

Un équivalent problème est analysé dans l'analyse des programmes en C pour FLT_EVAL_METHOD==2.

Si FLT_EVAL_METHOD==2:

double a =(Vmax-Vmin)/step;
int b = (Vmax-Vmin)/step;
int c = a;

calcule b par l'évaluation d'un long double expression puis la tronquant à un int, alors que, pour d' c c'est l'évaluation de long double, tronquant à double puis de int.

Si les deux valeurs ne sont pas obtenus avec le même processus, ce qui peut conduire à des résultats différents car les types flottants ne fournit pas d'habitude exacte de l'arithmétique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X