Come posso limitare un valore float a solo due posizioni dopo il punto decimale in C?


214

Come posso arrotondare un valore float (come 37.777779) a due cifre decimali (37.78) in C?


15
Non è possibile arrotondare correttamente il numero stesso, perché float(e double) non sono virgola mobile decimale - sono virgola mobile binaria - quindi arrotondare a posizioni decimali non ha senso. È possibile arrotondare l'output, tuttavia.
Pavel Minaev,

63
Non ha senso; è inesatto. C'è abbastanza differenza.
Brooks Moses,

2
Che tipo di arrotondamento ti aspetti? Half-up o arrotondamento al più vicino anche?
Cercatore di verità Rangwan,

Risposte:


407

Se vuoi solo arrotondare il numero per scopi di output, la "%.2f"stringa di formato è davvero la risposta corretta. Tuttavia, se si desidera effettivamente arrotondare il valore in virgola mobile per ulteriori calcoli, qualcosa di simile al seguente funziona:

#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 */

Nota che ci sono tre diverse regole di arrotondamento che potresti voler scegliere: arrotondare per difetto (cioè, troncare dopo due cifre decimali), arrotondato al più vicino e arrotondato per eccesso. Di solito, vuoi arrotondare al più vicino.

Come molti altri hanno sottolineato, a causa delle stranezze della rappresentazione in virgola mobile, questi valori arrotondati potrebbero non essere esattamente i valori decimali "ovvi", ma saranno molto vicini.

Per molte (molto!) Ulteriori informazioni sull'arrotondamento, e in particolare sulle regole di pareggio per l'arrotondamento al più vicino, vedi l'articolo di Wikipedia sull'arrotondamento .


4
Può essere modificato per supportare l'arrotondamento con precisione arbitraria?

1
@slater Quando dici "precisione arbitraria", chiedi di arrotondare, ad esempio, tre anziché due cifre decimali o di utilizzare librerie che implementano valori decimali di precisione illimitati? Se il primo, fai ciò che spero siano ovvi adattamenti alla costante 100; altrimenti, fai gli stessi calcoli esatti mostrati sopra, proprio con qualunque libreria multi-precisione che stai usando.
Dale Hagglund,

2
@DaleHagglung Il primo, grazie. La regolazione per sostituire 100 con pow (10, (int) desiderataPrecision)?

3
Sì. Per arrotondare dopo k decimali, utilizzare un fattore di scala di 10 ^ k. Questo dovrebbe essere davvero facile da vedere se scrivi alcuni valori decimali a mano e giochi con multipli di 10. Supponi di lavorare con il valore 1.23456789 e desideri arrotondarlo a 3 decimali. L'operazione a tua disposizione è rotonda per intero . Quindi, come si spostano le prime tre cifre decimali in modo che siano rimaste della virgola? Spero sia chiaro che si moltiplica per 10 ^ 3. Ora puoi arrotondare quel valore a un numero intero. Quindi, rimetti le tre cifre di ordine inferiore dividendo per 10 ^ 3.
Dale Hagglund,

1
Posso farlo funzionare con doublestroppo in qualche modo? Non sembra fare il lavoro che desidero :( (usando floore ceil).
signora Nessuno il

87

Utilizzando % .2f in printf. Stampa solo 2 punti decimali.

Esempio:

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

Produzione:

37.77

In questo modo è meglio perché non c'è perdita di precisione.
Albert

2
@albert Questo ha anche il vantaggio di non perdere la floatportata in quanto val * 100potrebbe traboccare.
chux - Ripristina Monica il

42

Supponendo che tu stia parlando del valore per la stampa, quindi Andrew Coleson e la risposta di AraK sono corrette:

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

Ma nota che se stai mirando a arrotondare il numero esattamente a 37,78 per uso interno (ad esempio per confrontare un altro valore), allora questa non è una buona idea, a causa del modo in cui funzionano i numeri in virgola mobile: di solito non lo fai vuoi fare confronti di uguaglianza per virgola mobile, invece usa un valore target +/- un valore sigma. O codificare il numero come una stringa con una precisione nota e confrontarlo.

Vedi il link nella risposta di Greg Hewgill a una domanda correlata , che spiega anche perché non dovresti usare il virgola mobile per i calcoli finanziari.


1
È stato votato per rispondere a quella che potrebbe essere la domanda dietro la domanda (o la domanda che avrebbe dovuto essere dietro la domanda!). Questo è un punto piuttosto importante.
Brooks Moses,

In realtà 37.78 possono essere presentati esattamente in virgola mobile. Float ha da 11 a 12 cifre per la precissione. Questo dovrebbe essere sufficiente per indirizzare 3778 377,8 o tutti i tipi di 4 cifre decimali.
Anonimo White

@HaryantoCiu sì, abbastanza giusto, ho modificato un po 'la mia risposta.
John Carter,

precisione dinamica:printf("%.*f", (int)precision, (double)number);
Minhas Kamal,

24

Cosa ne pensi di questo:

float value = 37.777779;
float rounded = ((int)(value * 100 + .5) / 100.0);

4
-1: a) questo non funzionerà con numeri negativi (ok, l'esempio è positivo ma comunque). b) non dici che è impossibile memorizzare l'esatto valore decimale nel float
John Carter,

32
@therefromhere: (a) Hai ragione (b) Cos'è questo? Un test al liceo?
Daniil,

1
perché hai aggiunto 0,5?
muhammad tayyab,

1
È necessario seguire le regole di arrotondamento.
Daniil,

1
le regole di arrotondamento nel contesto del commento di @Daniil sono arrotondate al più vicino
Shmil The Cat

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

Se vuoi scrivere su C-string:

char number[24]; // dummy size, you should take care of the size!
sprintf(number, "%.2f", 37.777779);

@Sinan: Perché la modifica? @AraK: No, si dovrebbe prendere cura della dimensione :). Usa snprintf ().
AIB

1
@aib: Immagino perché / ** / sono commenti in stile C e la domanda è taggata per C
Michael Haren,

5
C89 ha permesso solo / ** / - style, C99 ha introdotto il supporto per // - style. Usa un compilatore vecchio / scadente (o forza la modalità C89) e non sarai in grado di usare lo stile //. Detto questo, è il 2009, consideriamoli entrambi in stile C e C ++.
Andrew Coleson,

11

Non c'è modo di arrotondare a un floataltro floatperché arrotondatofloat potrebbe non essere rappresentabile (una limitazione dei numeri in virgola mobile). Ad esempio, supponiamo di arrotondare da 37.777779 a 37.78, ma il numero rappresentabile più vicino è 37.781.

Tuttavia, è possibile "arrotondare" a floatutilizzando una funzione stringa di formato.


3
Questo non è diverso dal dire "non c'è modo di dividere due float e ottenere un float, perché il risultato diviso potrebbe non essere rappresentabile", il che può essere precisamente vero ma è irrilevante. I galleggianti sono sempre inesatti, anche per qualcosa di semplice come un'aggiunta; il presupposto è sempre che ciò che si ottiene effettivamente è "il float che si avvicina di più alla risposta esatta arrotondata".
Brooks Moses,

Quello che intendevo dire è che non è possibile arrotondare da a floata n decimali e quindi aspettarsi che il risultato abbia sempre n decimali. Riceverai comunque un float, non solo quello che ti aspettavi.
Andrew Keeton,

9

Inoltre, se stai usando C ++, puoi semplicemente creare una funzione come questa:

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

È quindi possibile generare un doppio myDoublecon i npunti dopo la virgola con un codice come questo:

std::cout << prd(myDouble,n);

7

Puoi ancora usare:

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

esempio:

float valueToRound = 37.777779;
float roundedValue = ceilf(valueToRound * 100) / 100;

Questo si tronca in punto decimale (cioè produrrà 37) e dovrà arrotondare a due posizioni dopo il punto decimale.
Pavel Minaev,

L'arrotondamento a due posizioni dopo il punto decimale è una variazione banale, sebbene (ma dovrebbe ancora essere menzionato nella risposta; ZeroCool, vuoi aggiungere una modifica?): Float arrotondatoValore = ceilf (valueToRound * 100.0) / 100.0;
Brooks Moses,

Into of a sleep :)
ZeroCool

Come mai questa soluzione non è più popolare? Funziona esattamente come dovrebbe con un codice minimo. C'è qualche avvertimento con esso?
Andy,

7

In C ++ (o in C con cast in stile C), è possibile creare la funzione:

/* 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;
}

Quindi std::cout << showDecimals(37.777779,2);produrrebbe: 37.78.

Ovviamente non hai davvero bisogno di creare tutte e 5 le variabili in quella funzione, ma le lascio lì in modo da poter vedere la logica. Probabilmente ci sono soluzioni più semplici, ma questo funziona bene per me, soprattutto perché mi permette di regolare il numero di cifre dopo il decimale di cui ho bisogno.


5

Utilizzare sempre la printffamiglia di funzioni per questo. Anche se si desidera ottenere il valore come float, è consigliabile utilizzare snprintfper ottenere il valore arrotondato come stringa e quindi analizzarlo nuovamente con 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;
}

Lo dico perché l'approccio mostrato dalla risposta attualmente più votata e molti altri qui - moltiplicando per 100, arrotondando al numero intero più vicino e quindi dividendolo di nuovo per 100 - è imperfetto in due modi:

  • Per alcuni valori, arrotonderà nella direzione sbagliata perché la moltiplicazione per 100 modifica la cifra decimale determinando la direzione di arrotondamento da 4 a 5 o viceversa, a causa dell'imprecisione dei numeri in virgola mobile
  • Per alcuni valori, moltiplicare e poi dividere per 100 non andata e ritorno, il che significa che anche se non si verificano arrotondamenti, il risultato finale sarà errato

Per illustrare il primo tipo di errore - talvolta la direzione di arrotondamento è errata - prova a eseguire questo programma:

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);
}

Vedrai questo output:

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

Si noti che il valore con cui abbiamo iniziato era inferiore a 0,015, quindi la risposta matematicamente corretta quando si arrotonda a 2 decimali è 0,01. Ovviamente, 0,01 non è esattamente rappresentabile come doppio, ma prevediamo che il nostro risultato sia il doppio più vicino a 0,01. L'uso snprintfci dà quel risultato, ma l'uso round(100 * x) / 100ci dà 0,02, che è sbagliato. Perché? Perché 100 * xci dà esattamente 1,5 come risultato. Moltiplicando per 100 cambia quindi la direzione corretta in cui arrotondare.

Per illustrare il secondo tipo di errore - il risultato a volte è sbagliato a causa * 100e / 100non è realmente inverso l'uno dall'altro - possiamo fare un esercizio simile con un numero molto grande:

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);
}

Il nostro numero ora non ha nemmeno una parte frazionaria; è un valore intero, appena memorizzato con type double. Quindi il risultato dopo l'arrotondamento dovrebbe essere lo stesso numero con cui abbiamo iniziato, giusto?

Se esegui il programma sopra, vedrai:

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

Ops. Il nostro snprintfmetodo restituisce di nuovo il risultato giusto, ma l'approccio moltiplica, quindi arrotonda, quindi divide. Questo perché il valore matematicamente corretto di 8631192423766613.0 * 100, 863119242376661300.0non è esattamente rappresentabile come doppio; il valore più vicino è 863119242376661248.0. Quando lo dividi per 100, ottieni8631192423766612.0 - un numero diverso da quello con cui hai iniziato.

Speriamo che sia una dimostrazione sufficiente che l'uso roundfper arrotondare a un numero di cifre decimali è rotto e che snprintfinvece dovresti usare . Se ti sembra un orribile hack, forse sarai rassicurato dalla consapevolezza che è sostanzialmente ciò che fa CPython .


+1 per un esempio concreto di ciò che non va nella mia risposta e in quelli simili ad essa, grazie alla stranezza del virgola mobile IEEE e fornendo un'alternativa semplice. Ero perifericamente consapevole, molto tempo fa, di molti sforzi messi in stampa e gli amici mi sono stati al sicuro per valori di virgola mobile a scatto circolare. Immagino che il lavoro svolto allora potrebbe essere mostrato qui.
Dale Hagglund,

Ahem ... Ci scusiamo per la parola insalata verso la fine lì, che ora è troppo tardi per modificarla. Quello che intendevo dire era "... un grande sforzo messo in printf e amici per renderli sicuri ..."
Dale Hagglund

4

Uso float roundf(float x) .

"Le funzioni di arrotondamento arrotondano il loro argomento al valore intero più vicino in formato a virgola mobile, arrotondando i casi a metà distanza dallo zero, indipendentemente dalla direzione di arrotondamento corrente." C11dr §7.12.9.5

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

A seconda floatdell'implementazione, i numeri che possono apparire a metà strada non lo sono. poiché il virgola mobile è in genere orientato alla base 2. Inoltre, arrotondare con precisione al più vicino 0.01su tutti i casi "a metà strada" è molto impegnativo.

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

Sebbene "1.115" sia "a metà strada" tra 1,11 e 1,12, quando convertito in float, il valore è 1.115000009537...e non è più "a metà strada", ma più vicino a 1.12 e arrotondato al più vicino floatdi1.120000004768...

"1.125" è "a metà strada" tra 1,12 e 1,13, quando convertito in float, il valore è esattamente 1.125ed è "a metà strada". Si arrotonda verso 1.13 a causa di legami regola anche e giri al più vicino floatdi1.129999995232...

Sebbene "1.135" sia "a metà strada" tra 1,13 e 1,14, quando convertito in float, il valore è 1.134999990463...e non è più "a metà strada", ma più vicino a 1.13 e arrotondato al più vicino floatdi1.129999995232...

Se viene utilizzato il codice

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

Sebbene "1.135" sia "a metà strada" tra 1,13 e 1,14, quando convertito in float, il valore è 1.134999990463...e non è più "a metà strada", ma più vicino a 1.13 ma arrotondato erroneamente a floatdi a 1.139999985695...causa della precisione più limitata di floatvs. double. Questo valore errato può essere visto come corretto, a seconda degli obiettivi di codifica.


4

Ho creato questa macro per arrotondare i numeri float. Aggiungilo nella tua intestazione / essendo del file

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

Ecco un esempio:

float x = ROUNDF(3.141592, 100)

x è uguale a 3.14 :)


Questo si tronca, ma la domanda richiede arrotondamenti. Inoltre, è soggetto ad errori di arrotondamento nelle operazioni in virgola mobile.
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));

}

Qui n il numero di decimali

esempio:

double d = 100.23456;

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

printf("%f", f_round(d, 2));// result: 100.23

-1 per quattro motivi: 1) la mancanza di spiegazione, 2) la vulnerabilità al buffer overflow - questo traboccerà e quindi molto probabilmente si bloccherà , se dvalè enorme 3) lo strano if/ elseblocco in cui si fa esattamente la stessa cosa in ogni ramo e 4) l'uso complicato di sprintfcostruire l'identificatore di formato per una seconda sprintfchiamata; è più semplice utilizzare .*e passare il doppio valore e il numero di cifre decimali come argomenti alla stessa sprintfchiamata.
Mark Amery,

3

Definizione del codice:

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

Risultati:

a = 8.000000
sqrt(a) = r = 2.828427
roundz(r,2) = 2.830000
roundz(r,3) = 2.828000
roundz(r,5) = 2.828430

0

Consentitemi innanzitutto di giustificare il motivo per cui ho aggiunto un'altra risposta a questa domanda. In un mondo ideale, arrotondare non è davvero un grosso problema. Tuttavia, nei sistemi reali, potrebbe essere necessario affrontare diversi problemi che possono comportare arrotondamenti che potrebbero non essere quelli previsti. Ad esempio, è possibile che tu stia eseguendo calcoli finanziari in cui i risultati finali sono arrotondati e visualizzati agli utenti con 2 cifre decimali; questi stessi valori sono archiviati con precisione fissa in un database che può includere più di 2 cifre decimali (per vari motivi; non esiste un numero ottimale di posizioni da conservare ... dipende dalle situazioni specifiche che ciascun sistema deve supportare, ad esempio articoli di piccole dimensioni i cui prezzi sono frazioni di un centesimo per unità); e, calcoli in virgola mobile eseguiti su valori in cui i risultati sono più / meno epsilon. Ho affrontato questi problemi e ho sviluppato la mia strategia nel corso degli anni. Non pretenderò di aver affrontato tutti gli scenari o di avere la risposta migliore, ma di seguito è riportato un esempio del mio approccio finora che supera questi problemi:

Supponiamo che 6 decimali siano considerati una precisione sufficiente per i calcoli su float / doppi (una decisione arbitraria per l'applicazione specifica), usando la seguente funzione / metodo di arrotondamento:

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'arrotondamento al secondo decimale per la presentazione di un risultato può essere eseguito come:

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

Per val = 6.825, il risultato è 6.83come previsto.

Per val = 6.824999, il risultato è 6.82. Qui il presupposto è che il calcolo è risultato esattamente 6.824999e il settimo decimale è zero.

Per val = 6.8249999, il risultato è 6.83. Il settimo decimale 9in questo caso fa sì che la Round(val,6)funzione dia il risultato atteso. In questo caso, potrebbe esserci un numero qualsiasi di messaggi finali 9.

Per val = 6.824999499999, il risultato è 6.83. L'arrotondamento all'ottava cifra decimale come primo passo, vale a dire Round(val,8), si occupa dell'unico caso in cui viene calcolato un risultato in virgola mobile calcolato 6.8249995, ma viene rappresentato internamente come 6.824999499999....

Infine, l'esempio della domanda ... val = 37.777779risulta 37.78.

Questo approccio potrebbe essere ulteriormente generalizzato come:

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

dove N è la precisione da mantenere per tutti i calcoli intermedi su float / doppi. Questo funziona anche su valori negativi. Non so se questo approccio sia matematicamente corretto per tutte le possibilità.


0

Codice C semplice per arrotondare un numero:

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

Questo produrrà:

4

-1

... oppure puoi farlo alla vecchia maniera senza librerie:

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;

Questo ovviamente se si desidera rimuovere le informazioni extra dal numero.


-2

questa funzione prende il numero e la precisione e restituisce il numero arrotondato

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;
}

converte il numero in virgola mobile in int spostando a sinistra il punto e verificando la condizione maggiore di cinque.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.