Comment restreindre une valeur flottante à seulement deux endroits après la virgule décimale en C?

214

Comment puis-je arrondir une valeur flottante (telle que 37,777779) à deux décimales (37,78) en C?

Sakib Arifin
la source
15
Vous ne pouvez pas arrondir correctement le nombre lui-même, car float(et double) ne sont pas des virgules flottantes décimales - ce sont des virgules flottantes binaires - donc l'arrondi aux positions décimales n'a pas de sens. Vous pouvez cependant arrondir la sortie.
Pavel Minaev,
63
Ce n'est pas dénué de sens; c'est inexact. Il y a une grosse différence.
Brooks Moses
2
À quel type d'arrondi vous vous attendez? Moitié ou arrondi au pair le plus proche?
Truthseeker Rangwan

Réponses:

407

Si vous souhaitez simplement arrondir le nombre à des fins de sortie, la "%.2f"chaîne de format est en effet la bonne réponse. Cependant, si vous voulez réellement arrondir la valeur à virgule flottante pour un calcul ultérieur, quelque chose comme le suivant fonctionne:

#include <math.h>

float val = 37.777779;

float rounded_down = floorf(val * 100) / 100;   /* Result: 37.77 */
float nearest = roundf(val * 100) / 100;  /* Result: 37.78 */
float rounded_up = ceilf(val * 100) / 100;      /* Result: 37.78 */

Notez qu'il existe trois règles d'arrondi différentes que vous pouvez choisir: arrondir vers le bas (c'est-à-dire tronquer après deux décimales), arrondi au plus proche et arrondir vers le haut. Habituellement, vous voulez arrondir au plus proche.

Comme plusieurs autres l'ont souligné, en raison des caprices de la représentation en virgule flottante, ces valeurs arrondies peuvent ne pas être exactement les valeurs décimales "évidentes", mais elles seront très très proches.

Pour beaucoup (beaucoup!) Plus d'informations sur l'arrondi, et en particulier sur les règles de bris d'égalité pour l'arrondi au plus proche, voir l'article Wikipedia sur l'arrondi .

Dale Hagglund
la source
4
Peut-il être modifié pour prendre en charge l'arrondi avec une précision arbitraire?
1
@slater Lorsque vous dites «précision arbitraire», demandez-vous d'arrondir à, par exemple, trois au lieu de deux décimales, ou d'utiliser des bibliothèques qui implémentent des valeurs décimales de précision illimitées? Si le premier, ce que j'espère sont des ajustements évidents à la constante 100; sinon, faites exactement les mêmes calculs que ceux indiqués ci-dessus, avec la bibliothèque multi-précision que vous utilisez.
Dale Hagglund
2
@DaleHagglung Le premier, merci. Est-ce que l'ajustement pour remplacer 100 par pow (10, (int) Precision souhaitée)?
3
Oui. Pour arrondir après k décimales, utilisez un facteur d'échelle de 10 ^ k. Cela devrait être très facile à voir si vous écrivez des valeurs décimales à la main et jouez avec des multiples de 10. Supposons que vous travaillez avec la valeur 1,23456789 et que vous souhaitez l'arrondir à 3 décimales. L'opération à votre disposition est arrondie à entière . Alors, comment déplacez-vous les trois premières décimales afin qu'elles soient à gauche du point décimal? J'espère qu'il est clair que vous multipliez par 10 ^ 3. Vous pouvez maintenant arrondir cette valeur à un entier. Ensuite, vous remettez les trois chiffres de poids faible en divisant par 10 ^ 3.
Dale Hagglund
1
Puis-je faire en sorte que cela fonctionne doublesaussi? Ne semble pas faire le travail que je veux :( (en utilisant flooret ceil).
Mme Nobody
87

Utilisation de % .2f dans printf. Il n'imprime que 2 décimales.

Exemple:

printf("%.2f", 37.777779);

Production:

37.77
Andrew Coleson
la source
Cette méthode est meilleure car il n'y a aucune perte de précision.
albert
2
@albert Cela a également l'avantage de ne pas perdre de floatportée car cela val * 100pourrait déborder.
chux
42

En supposant que vous parlez de la valeur d'impression, la réponse d' Andrew Coleson et d' AraK est correcte:

printf("%.2f", 37.777779);

Mais notez que si vous visez à arrondir le nombre à exactement 37,78 pour un usage interne (par exemple pour comparer avec une autre valeur), ce n'est pas une bonne idée, en raison de la façon dont les nombres à virgule flottante fonctionnent: vous ne le faites généralement pas voulez faire des comparaisons d'égalité pour la virgule flottante, utilisez plutôt une valeur cible +/- une valeur sigma. Ou encodez le nombre sous forme de chaîne avec une précision connue, et comparez cela.

Voir le lien dans la réponse de Greg Hewgill à une question connexe , qui explique également pourquoi vous ne devriez pas utiliser la virgule flottante pour les calculs financiers.

John Carter
la source
1
A voté pour répondre à ce qui peut être la question derrière la question (ou la question qui aurait dû être derrière la question!). C'est un point assez important.
Brooks Moses
En fait, 37,78 peuvent être présentés exactement par virgule flottante. Le flotteur a 11 à 12 chiffres pour la précision. Cela devrait suffire pour adresser 3778 377.8 ou toutes sortes de 4 chiffres décimaux.
Anonymous White
@HaryantoCiu ouais assez bien, j'ai un peu édité ma réponse.
John Carter,
précision dynamique:printf("%.*f", (int)precision, (double)number);
Minhas Kamal
24

Que dis-tu de ça:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);
Daniil
la source
4
-1: a) cela ne fonctionnera pas pour les nombres négatifs (ok, l'exemple est positif mais quand même). b) vous ne mentionnez pas qu'il est impossible de stocker la valeur décimale exacte dans le flotteur
John Carter
32
@therefromhere: (a) Vous avez raison (b) Qu'est-ce que c'est? Un test au lycée?
Daniil
1
pourquoi avez-vous ajouté 0,5?
muhammad tayyab
1
Il est nécessaire de suivre les règles d'arrondi.
Daniil
1
les règles d'arrondi dans le contexte du commentaire @Daniil sont arrondies au plus proche
Shmil The Cat
20
printf("%.2f", 37.777779);

Si vous souhaitez écrire sur la chaîne C:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);
AraK
la source
@Sinan: Pourquoi le montage? @AraK: Non, vous devez faire attention à la taille :). Utilisez snprintf ().
BAI
1
@aib: Je suppose que / ** / sont des commentaires de style C et que la question est taguée pour C
Michael Haren
5
C89 ne permettait que le style / ** / -, C99 a introduit la prise en charge du style //. Utilisez un compilateur boiteux / ancien (ou forcez le mode C89) et vous ne pourrez pas utiliser le style //. Cela dit, nous sommes en 2009, considérons-les à la fois en style C et C ++.
Andrew Coleson
11

Il n'y a pas moyen d'arrondir les uns floataux autres floatcar les arrondisfloat peut ne pas être représentable (limitation des nombres à virgule flottante). Par exemple, disons que vous arrondissez 37,777779 à 37,78, mais le nombre représentable le plus proche est 37,781.

Cependant, vous pouvez "arrondir" un floaten utilisant une fonction de chaîne de format.

Andrew Keeton
la source
3
Ce n'est pas différent de dire "il n'y a aucun moyen de diviser deux flottants et d'obtenir un flottant, car le résultat divisé peut ne pas être représentable", ce qui peut être vrai, mais n'est pas pertinent. Les flotteurs sont toujours inexacts, même pour quelque chose d'aussi basique que l'addition; l'hypothèse est toujours que ce que vous obtenez réellement est "le flotteur qui se rapproche le plus de la réponse arrondie exacte".
Brooks Moses
Ce que je voulais dire, c'est que vous ne pouvez pas arrondir de a floatà n décimales, puis vous attendre à ce que le résultat ait toujours n décimales. Vous en aurez toujours un float, mais pas celui que vous attendiez.
Andrew Keeton
9

De plus, si vous utilisez C ++, vous pouvez simplement créer une fonction comme celle-ci:

string prd(const double x, const int decDigits) {
    stringstream ss;
    ss << fixed;
    ss.precision(decDigits); // set # places after decimal
    ss << x;
    return ss.str();
}

Vous pouvez ensuite sortir n'importe quel double myDoubleavec des nplaces après la virgule décimale avec un code comme celui-ci:

std::cout << prd(myDouble,n);
synaptik
la source
7

Vous pouvez toujours utiliser:

float ceilf(float x); // don't forget #include <math.h> and link with -lm.

exemple:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;
ZeroCool
la source
Cela tronque au point décimal (c'est-à-dire produira 37), et il doit arrondir à deux endroits après le point décimal.
Pavel Minaev
Arrondir à deux endroits après la virgule décimale est cependant une variation triviale (mais doit toujours être mentionné dans la réponse; ZeroCool, vous voulez ajouter une modification?): Float roundValue = ceilf (valueToRound * 100.0) / 100.0;
Brooks Moses
Dans un état de sommeil :)
ZeroCool
Comment se fait-il que cette solution ne soit pas plus populaire? Cela fonctionne exactement comme il le devrait avec un minimum de code. Y a-t-il une mise en garde avec cela?
Andy
7

En C ++ (ou en C avec des transtypages de style C), vous pouvez créer la fonction:

/* Function to control # of decimal places to be output for x */
double showDecimals(const double& x, const int& numDecimals) {
    int y=x;
    double z=x-y;
    double m=pow(10,numDecimals);
    double q=z*m;
    double r=round(q);

    return static_cast<double>(y)+(1.0/m)*r;
}

Ensuite std::cout << showDecimals(37.777779,2);produirait: 37,78.

Évidemment, vous n'avez pas vraiment besoin de créer les 5 variables dans cette fonction, mais je les laisse là pour que vous puissiez voir la logique. Il existe probablement des solutions plus simples, mais cela fonctionne bien pour moi - d'autant plus que cela me permet d'ajuster le nombre de chiffres après la décimale selon mes besoins.

synaptik
la source
5

Utilisez toujours la printffamille de fonctions pour cela. Même si vous souhaitez obtenir la valeur sous forme de flottant, il vaut mieux utiliser snprintfpour obtenir la valeur arrondie sous forme de chaîne, puis l'analyser à nouveau avec atof:

#include <math.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>

double dround(double val, int dp) {
    int charsNeeded = 1 + snprintf(NULL, 0, "%.*f", dp, val);
    char *buffer = malloc(charsNeeded);
    snprintf(buffer, charsNeeded, "%.*f", dp, val);
    double result = atof(buffer);
    free(buffer);
    return result;
}

Je dis cela parce que l'approche montrée par la réponse actuellement la plus votée et plusieurs autres ici - multiplier par 100, arrondir à l'entier le plus proche, puis diviser par 100 à nouveau - est défectueuse de deux manières:

  • Pour certaines valeurs, il arrondira dans la mauvaise direction car la multiplication par 100 modifie le chiffre décimal déterminant la direction d'arrondi de 4 à 5 ou vice versa, en raison de l'imprécision des nombres à virgule flottante
  • Pour certaines valeurs, la multiplication puis la division par 100 ne sont pas aller-retour, ce qui signifie que même si aucun arrondi n'a lieu, le résultat final sera erroné

Pour illustrer le premier type d'erreur - la direction d'arrondi étant parfois erronée - essayez d'exécuter ce programme:

int main(void) {
    // This number is EXACTLY representable as a double
    double x = 0.01499999999999999944488848768742172978818416595458984375;

    printf("x: %.50f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.50f\n", res1);
    printf("Rounded with round, then divided: %.50f\n", res2);
}

Vous verrez cette sortie:

x: 0.01499999999999999944488848768742172978818416595459
Rounded with snprintf: 0.01000000000000000020816681711721685132943093776703
Rounded with round, then divided: 0.02000000000000000041633363423443370265886187553406

Notez que la valeur avec laquelle nous avons commencé était inférieure à 0,015, et donc la réponse mathématiquement correcte lors de l'arrondi à 2 décimales est 0,01. Bien sûr, 0,01 n'est pas exactement représentable comme un double, mais nous nous attendons à ce que notre résultat soit le double le plus proche de 0,01. L'utilisation snprintfnous donne ce résultat, mais l'utilisation round(100 * x) / 100nous donne 0,02, ce qui est faux. Pourquoi? Parce que cela 100 * xnous donne exactement 1,5 comme résultat. La multiplication par 100 change donc la direction correcte pour arrondir.

Pour illustrer le deuxième type d'erreur - le résultat étant parfois erroné à cause de * 100et / 100pas vraiment inverses les uns des autres - nous pouvons faire un exercice similaire avec un très grand nombre:

int main(void) {
    double x = 8631192423766613.0;

    printf("x: %.1f\n", x);

    double res1 = dround(x, 2);
    double res2 = round(100 * x) / 100;

    printf("Rounded with snprintf: %.1f\n", res1);
    printf("Rounded with round, then divided: %.1f\n", res2);
}

Notre nombre n'a désormais même plus de partie fractionnaire; c'est une valeur entière, juste stockée avec le typedouble . Donc, le résultat après arrondi devrait être le même nombre que celui avec lequel nous avons commencé, non?

Si vous exécutez le programme ci-dessus, vous verrez:

x: 8631192423766613.0
Rounded with snprintf: 8631192423766613.0
Rounded with round, then divided: 8631192423766612.0

Oups. Notre snprintfméthode renvoie à nouveau le bon résultat, mais l'approche multiplier puis arrondir puis diviser échoue. En effet , la valeur mathématiquement correcte 8631192423766613.0 * 100, 863119242376661300.0n'est pas exactement représentable comme un double; la valeur la plus proche est 863119242376661248.0. Lorsque vous divisez cela par 100, vous obtenez 8631192423766612.0- un nombre différent de celui avec lequel vous avez commencé.

J'espère que c'est une démonstration suffisante que l'utilisation roundfde l'arrondi à un certain nombre de décimales est rompue et que vous devriez utiliser à la snprintfplace. Si cela vous semble un horrible hack, vous serez peut-être rassuré par la connaissance que c'est essentiellement ce que fait CPython .

Mark Amery
la source
+1 pour un exemple concret de ce qui ne va pas avec ma réponse et ceux qui lui sont similaires, grâce à l'étrangeté de la virgule flottante IEEE, et offrant une alternative simple. J'étais conscient de la périphérie, il y a assez longtemps, de beaucoup d'efforts déployés dans la presse écrite et mes amis les protégeaient pour les détournements de virgule flottante. J'imagine que le travail effectué alors pourrait apparaître ici.
Dale Hagglund
Ahem ... Désolé pour le mot salade vers la fin là-bas, qu'il est maintenant trop tard pour modifier. Ce que je voulais dire, c'était "... beaucoup d'efforts investis dans printf et ses amis pour les sécuriser ..."
Dale Hagglund
4

Utilisez float roundf(float x).

"Les fonctions d'arrondi arrondissent leur argument à la valeur entière la plus proche au format à virgule flottante, arrondissant à mi-chemin les cas loin de zéro, quelle que soit la direction d'arrondi actuelle." C11dr §7.12.9.5

#include <math.h>
float y = roundf(x * 100.0f) / 100.0f; 

Selon votre floatimplémentation, les chiffres qui peuvent sembler être à mi-chemin ne le sont pas. comme virgule flottante est généralement orientée base-2. De plus, l'arrondi précis au plus proche 0.01dans tous les cas "à mi-chemin" est le plus difficile.

void r100(const char *s) {
  float x, y;
  sscanf(s, "%f", &x);
  y = round(x*100.0)/100.0;
  printf("%6s %.12e %.12e\n", s, x, y);
}

int main(void) {
  r100("1.115");
  r100("1.125");
  r100("1.135");
  return 0;
}

 1.115 1.115000009537e+00 1.120000004768e+00  
 1.125 1.125000000000e+00 1.129999995232e+00
 1.135 1.134999990463e+00 1.139999985695e+00

Bien que "1.115" soit "à mi-chemin" entre 1.11 et 1.12, une fois converti en float, la valeur est 1.115000009537...et n'est plus "à mi-chemin", mais plus proche de 1.12 et arrondie au plus proche floatde1.120000004768...

"1.125" est "à mi-chemin" entre 1.12 et 1.13, une fois converti en float, la valeur est exactement 1.125et est "à mi-chemin". Il arrondit vers 1,13 en raison de l'égalité des règles et arrondit au plus proche floatde1.129999995232...

Bien que "1.135" soit "à mi-chemin" entre 1.13 et 1.14, une fois converti en float, la valeur est 1.134999990463...et n'est plus "à mi-chemin", mais plus proche de 1.13 et arrondie au plus proche floatde1.129999995232...

Si le code est utilisé

y = roundf(x*100.0f)/100.0f;

Bien que "1.135" soit "à mi-chemin" entre 1.13 et 1.14, une fois converti en float, la valeur est 1.134999990463...et n'est plus "à mi-chemin", mais plus proche de 1.13 mais arrondit incorrectement à floatof en 1.139999985695...raison de la précision plus limitée de floatvs. double. Cette valeur incorrecte peut être considérée comme correcte, selon les objectifs de codage.

chux - Réintégrer Monica
la source
4

J'ai fait cette macro pour arrondir les nombres flottants. Ajoutez-le dans votre en-tête / être de fichier

#define ROUNDF(f, c) (((float)((int)((f) * (c))) / (c)))

Voici un exemple:

float x = ROUNDF(3.141592, 100)

x est égal à 3,14 :)

mou
la source
Cela tronque, mais la question demande l'arrondi. De plus, il est sujet à des erreurs d'arrondi dans les opérations en virgule flottante.
Eric Postpischil
3
double f_round(double dval, int n)
{
    char l_fmtp[32], l_buf[64];
    char *p_str;
    sprintf (l_fmtp, "%%.%df", n);
    if (dval>=0)
            sprintf (l_buf, l_fmtp, dval);
    else
            sprintf (l_buf, l_fmtp, dval);
    return ((double)strtod(l_buf, &p_str));

}

Voici nle nombre de décimales

exemple:

double d = 100.23456;

printf("%f", f_round(d, 4));// result: 100.2346

printf("%f", f_round(d, 2));// result: 100.23
user2331026
la source
-1 pour quatre raisons: 1) le manque d'explication, 2) la vulnérabilité au débordement de tampon - cela va déborder, et donc très probablement planter, si dvalc'est énorme 3) le bizarre if/ elsebloc où vous faites exactement la même chose dans chaque branche , et 4) l'utilisation trop compliquée de sprintfpour construire le spécificateur de format pour un deuxième sprintfappel; il est plus simple d'utiliser .*et de passer la double valeur et le nombre de décimales comme arguments au même sprintfappel.
Mark Amery
3

Définition du code:

#define roundz(x,d) ((floor(((x)*pow(10,d))+.5))/pow(10,d))

Résultats :

a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430
Confidentialité Zyp
la source
0

Permettez-moi d'abord d'essayer de justifier ma raison d'ajouter une autre réponse à cette question. Dans un monde idéal, l'arrondi n'est pas vraiment un gros problème. Cependant, dans les systèmes réels, vous devrez peut-être faire face à plusieurs problèmes qui peuvent entraîner des arrondis qui ne correspondent peut-être pas à vos attentes. Par exemple, vous pouvez effectuer des calculs financiers où les résultats finaux sont arrondis et affichés aux utilisateurs sous la forme de 2 décimales; ces mêmes valeurs sont stockées avec une précision fixe dans une base de données qui peut inclure plus de 2 décimales (pour diverses raisons; il n'y a pas de nombre optimal de lieux à conserver ... dépend des situations spécifiques que chaque système doit prendre en charge, par exemple de petits articles dont les prix sont des fractions d'un sou par unité); et des calculs en virgule flottante effectués sur des valeurs où les résultats sont plus / moins epsilon. J'ai été confronté à ces problèmes et j'ai développé ma propre stratégie au fil des ans. Je ne prétendrai pas avoir fait face à tous les scénarios ou avoir la meilleure réponse, mais voici un exemple de mon approche jusqu'à présent qui surmonte ces problèmes:

Supposons que 6 décimales soient considérées comme une précision suffisante pour les calculs sur les flottants / doubles (une décision arbitraire pour l'application spécifique), en utilisant la fonction / méthode d'arrondi suivante:

double Round(double x, int p)
{
    if (x != 0.0) {
        return ((floor((fabs(x)*pow(double(10.0),p))+0.5))/pow(double(10.0),p))*(x/fabs(x));
    } else {
        return 0.0;
    }
}

L'arrondi à 2 décimales pour la présentation d'un résultat peut être effectué comme suit:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,8),6),2));

Pour val = 6.825 , le résultat est 6.83comme prévu.

Pour val = 6.824999 résultat est 6.82. Ici, l'hypothèse est que le calcul a donné exactement 6.824999et la 7ème décimale est zéro.

Car le val = 6.8249999résultat est 6.83. La 7ème décimale étant9 dans ce cas, la Round(val,6)fonction donne le résultat attendu. Dans ce cas, il pourrait y avoir un nombre illimité de 9s.

Car le val = 6.824999499999résultat est 6.83. Arrondi à la 8ème décimale dans un premier temps, c.-à-d.Round(val,8) , prend soin du seul cas désagréable par lequel un résultat en virgule flottante calculé calcule vers 6.8249995, mais est représenté en interne comme 6.824999499999....

Enfin, l'exemple de la question ...val = 37.777779 traduit par 37.78.

Cette approche pourrait être encore plus généralisée:

double val;
// ...perform calculations on val
String(Round(Round(Round(val,N+2),N),2));

où N est la précision à maintenir pour tous les calculs intermédiaires sur flottants / doubles. Cela fonctionne également sur les valeurs négatives. Je ne sais pas si cette approche est mathématiquement correcte pour toutes les possibilités.

Tim D
la source
0

Code C simple pour arrondir un nombre:

float n = 3.56;
printf("%.f", n);

Cela produira:

4
Aqeel Shamsudheen
la source
-1

... ou vous pouvez le faire à l'ancienne sans aucune bibliothèque:

float a = 37.777779;

int b = a; // b = 37    
float c = a - b; // c = 0.777779   
c *= 100; // c = 77.777863   
int d = c; // d = 77;    
a = b + d / (float)100; // a = 37.770000;

Cela bien sûr si vous souhaitez supprimer les informations supplémentaires du numéro.

NikosD
la source
-2

cette fonction prend le nombre et la précision et renvoie le nombre arrondi

float roundoff(float num,int precision)
{
      int temp=(int )(num*pow(10,precision));
      int num1=num*pow(10,precision+1);
      temp*=10;
      temp+=5;
      if(num1>=temp)
              num1+=10;
      num1/=10;
      num1*=10;
      num=num1/pow(10,precision+1);
      return num;
}

il convertit le nombre à virgule flottante en int en décalant le point vers la gauche et en vérifiant la condition supérieure à cinq.

kapil
la source