2010-12-31 25 views
11

La C incorporata che sto usando non ha una funzione round() è matematica lib, quale sarebbe un modo conciso per implementarlo in C? Stavo pensando di stamparlo su una stringa, cercando la posizione decimale, quindi trovando il primo carattere dopo il periodo, quindi arrotondando se> = 5, altrimenti verso il basso. ecc. Stava chiedendo se c'è qualcosa di più intelligente.Modo conciso per implementare round() in C?

Grazie, Fred

+2

Si noti che 'round()' è in C99, quindi non è necessariamente in tutte le librerie C in ogni caso. – chrisaycock

+0

Le funzioni floor o ceil sono disponibili? – Cratylus

+4

L'arrotondamento è abbastanza inutile, solo gli umani si preoccupano di questo. Gli unici che diventano un po 'impazienti di dover leggere 7 o 15 cifre nel risultato. La macchina non si cura, non si stanca. Non è un problema, printf() giri abbastanza bene. Nel caso in cui: non implementare un programma di contabilità in C senza avere una libreria che calcola in base 10. –

risposta

15

Si potrebbe reinventare la ruota, come suggeriscono molte altre risposte.In alternativa, potresti utilizzare la ruota di un'altra persona: suggerirei Newlib, che è concesso in licenza BSD e destinato all'uso su sistemi embedded. Gestisce correttamente numeri negativi, NaN, infiniti e casi che non sono rappresentabili come numeri interi (perché troppo grandi), così come lo fanno in modo efficiente che utilizza esponenti e mascheramento piuttosto che operazioni a virgola mobile generalmente più costose. Inoltre, è regolarmente collaudato, quindi sai che non ha bug in agguato.

La Newlib sorgente può essere un po 'imbarazzante per navigare, ecco sono i bit che si desidera:

versione galleggiante: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/sf_round.c;hb=master

Doppia versione: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/s_round.c;hb=master

macro Word-estrazione definita qui: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libm/common/fdlibm.h;hb=master

Se sono necessari altri file da lì, il genitore di rectory è questa: https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=tree;f=newlib/libm/common;hb=master

Per la registrazione, ecco il codice per la versione flottante. Come puoi vedere, c'è un po 'di complessità necessaria per gestire correttamente tutti i casi possibili.

float roundf(x) 
{ 
    int signbit; 
    __uint32_t w; 
    /* Most significant word, least significant word. */ 
    int exponent_less_127; 

    GET_FLOAT_WORD(w, x); 

    /* Extract sign bit. */ 
    signbit = w & 0x80000000; 

    /* Extract exponent field. */ 
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127; 

    if (exponent_less_127 < 23) 
    { 
     if (exponent_less_127 < 0) 
     { 
      w &= 0x80000000; 
      if (exponent_less_127 == -1) 
      /* Result is +1.0 or -1.0. */ 
      w |= ((__uint32_t)127 << 23); 
     } 
     else 
     { 
      unsigned int exponent_mask = 0x007fffff >> exponent_less_127; 
      if ((w & exponent_mask) == 0) 
      /* x has an integral value. */ 
      return x; 

      w += 0x00400000 >> exponent_less_127; 
      w &= ~exponent_mask; 
     } 
    } 
    else 
    { 
     if (exponent_less_127 == 128) 
     /* x is NaN or infinite. */ 
     return x + x; 
     else 
     return x; 
    } 
    SET_FLOAT_WORD(x, w); 
    return x; 
} 
+1

+1 Sono completamente d'accordo con questo, nella mia risposta a una versione C++ di questa domanda [qui] (http: //stackoverflow.com/a/24348037/1708801) Ho collegato a un articolo in tre parti sul motivo per cui rotolare il proprio arrotondamento è così difficile. –

+1

Potrebbe essere bello avere dei link aggiornati, quei link vanno subito al livello più alto. – LovesTha

+1

In particolare, poiché il codice incollato si basa su macro che si trovano dietro collegamenti che non funzionano :) – LovesTha

2

Nei giorni antichi, quando arrotondamento non è stato ben definito tra i sistemi, abbiamo scritto una funzione di arrotondamento in scala che prima moltiplicato il numero in modo che l'arrotondamento è stato fatto da troncare il numero.
Per arrotondare a 2 posizioni decimali, moltiplicare per 100, aggiungere 0,5, troncare i risultati e dividere per 100.
Ecco come è stato fatto per le macchine utensili a controllo numerico quando i controlli non potevano eseguire un programma NC a meno che non era azzeccato.

7
int round(float x) 
{ 
    return (int)(x + 0.5); 
} 

Caveat: Funziona solo su numeri positivi.

+2

Stai dimenticando i numeri negativi: round (-1.3) = 0 nel tuo caso. –

+5

@Niki: non mi sto dimenticando di loro; Ho solo scelto di ignorarli. Aggiungere supporto per loro è banale (vedi la risposta di dan04) e il mio obiettivo era quello di illustrare in modo minimale un metodo diverso da quello spezzettato descritto dall'OP. – nmichaels

+2

Poiché il cast arriverà verso zero, funzionerà solo se x è positivo. – splicer

-1

Un modo utilizzando un'operazione stringa

float var=1.2345; 
char tmp[12]={0x0}; 
sprintf(tmp, "%.2f", var); 
var=atof(tmp); 

Un altro modo mediante operazioni numeriche

float var=1.2345; 
int i=0; 
var*=100; 
i=var; 
var=i; 
var/=100; 
13
int round(double x) 
{ 
    if (x < 0.0) 
     return (int)(x - 0.5); 
    else 
     return (int)(x + 0.5); 
} 
+2

La funzione di libreria C 'round()' restituisce un double, non un int. –

+9

Si noti inoltre che questa implementazione ha un comportamento non definito per 'x' al di fuori dell'intervallo di valori rappresentabili da numeri interi. –

+4

Provare il codice sopra indicato su 0.49999999999999994; su implementazioni che usano 'double' a 64 bit per i calcoli, aggiungendo 0.5 a quel valore restituirà 1.0 anche se dovrebbe arrotondare per difetto. – supercat

6

IEEE 754 consiglia la "metà intorno a addirittura" approccio: se la parte frazionaria di d è 0,5 quindi arrotondato al numero intero pari più vicino. Il problema è che l'arrotondamento di una parte frazionaria di 0,5 nella stessa direzione introduce bias nei risultati; quindi, devi arrotondare lo 0,5 frazionario per metà tempo e metà del tempo, quindi il bit "arrotondato al più vicino pari", arrotondando alla cifra più vicina funzionerebbe anche come farebbe una moneta equa per determinare quale strada partire.

penso che qualcosa di più simile a questo sarebbe IEEE-corretto:

#include <math.h> 

int is_even(double d) { 
    double int_part; 
    modf(d/2.0, &int_part); 
    return 2.0 * int_part == d; 
} 

double round_ieee_754(double d) { 
    double i = floor(d); 
    d -= i; 
    if(d < 0.5) 
     return i; 
    if(d > 0.5) 
     return i + 1.0; 
    if(is_even(i)) 
     return i; 
    return i + 1.0; 
} 

E questo dovrebbe essere C99-ish (che sembra indicare che i numeri con parti frazionarie di 0.5 devono essere arrotondati per eccesso) :

#include <math.h> 
double round_c99(double x) { 
    return (x >= 0.0) ? floor(x + 0.5) : ceil(x - 0.5); 
} 

e una versione più compatta del mio primo round_c99(), questo gestisce attraversando il confine 56bit mantissa meglio non basandosi su x+0.5 o x-0.5 essendo cose sensibili da fare:

#include <math.h> 
double round_c99(double d) { 
    double int_part, frac_part; 
    frac_part = modf(d, &int_part); 
    if(fabs(frac_part) < 0.5) 
     return int_part; 
    return int_part > 0.0 ? int_part + 1.0 : int_part - 1.0; 
} 

Ciò avrà problemi se |int_part| >> 1 ma arrotonda una doppia con un grande esponente è inutile. Sono sicuro che ci siano anche NaN in tutte e tre le cose, ma il mio masochismo ha dei limiti e la programmazione numerica non è davvero la mia cosa.

Il calcolo a virgola mobile ha un ampio margine per errori impercettibili, pertanto conciso potrebbe non essere il requisito migliore.

Una soluzione ancora migliore sarebbe quella di battere il venditore del compilatore all'incirca sul viso e sul collo finché non forniscono una libreria matematica corretta.

+1

'round()' è completamente specificato da C99 e si distingue dalle funzioni di arrotondamento IEEE. Nota anche che la tua funzione ha un comportamento indefinito se 'd' è troppo grande per essere arrotondato a un' int'. –

+0

@Stephen Canon: C99 specifica "round away from 0 on tie"? Posso aggirare il comportamento indefinito abbastanza facilmente se hai un comodo test anche per il doppio, non ho dovuto fare i conti con problemi in virgola mobile dai miei giorni di data mining negli anni '90. C'è una ragione per le ultime due frasi nella mia risposta. –

+0

Penso che i problemi di Stephen Canon siano risolti. Continuo a pensare che il throtting del compilatore per una libreria matematica incompleta sia l'approccio più desiderabile qui. –

0

Puoi usare intero? effettuare le seguenti operazioni:

int roundup(int m, int n) 
{ 
return (m + n - 1)/n ; 
} 

int rounddown(int m, int n) 
{ 
return m/n; 
} 

int round(int m, int n) 
{ 
    int mod = m % n; 

    if(mod >= (n + 1)/2) 
     return roundup(m, n); 
    else 
     return rounddown(m, n); 
} 
Problemi correlati