round () per float in C ++


232

Ho bisogno di una semplice funzione di arrotondamento in virgola mobile, quindi:

double round(double);

round(0.1) = 0
round(-0.1) = 0
round(-0.9) = -1

Riesco a trovare ceil()e floor()in matematica.h - ma non round().

È presente nella libreria C ++ standard con un altro nome o manca?


1
Se vuoi solo emettere il numero come un numero arrotondato sembra che tu possa fare std::cout << std::fixed << std::setprecision(0) << -0.9, per esempio.
Frank,

44
Proteggere questo ... I nuovi utenti con nuovi brillanti schemi di arrotondamento dovrebbero prima leggere le risposte esistenti.
Shog9,

12
roundè disponibile da C ++ 11 in <cmath>. Sfortunatamente se sei in Microsoft Visual Studio manca ancora: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Alessandro Jacopson

3
Come noto nella mia risposta, far rotolare i tuoi roundha molti avvertimenti. Prima di C ++ 11, lo standard faceva affidamento su C90 che non includeva round. C ++ 11 si basa su C99 che ha roundma anche come ho notato include truncche ha proprietà diverse e può essere più appropriato a seconda dell'applicazione. La maggior parte delle risposte sembra anche ignorare che un utente potrebbe voler restituire un tipo integrale che presenta ancora più problemi.
Shafik Yaghmour,

2
@uvts_cvs questo non sembra essere un problema con l'ultima versione di Visual Studio, guardalo dal vivo .
Shafik Yaghmour,

Risposte:


144

Non c'è round () nella libreria standard C ++ 98. Puoi scriverne uno tu però. Quanto segue è un'implementazione del round-half-up :

double round(double d)
{
  return floor(d + 0.5);
}

Il probabile motivo per cui non esiste una funzione rotonda nella libreria standard C ++ 98 è che può effettivamente essere implementato in diversi modi. Quanto sopra è un modo comune ma ce ne sono altri come round-to-even , che è meno distorto e generalmente migliore se hai intenzione di fare molti arrotondamenti; è un po 'più complesso da implementare però.


53
Questo non gestisce correttamente i numeri negativi. La risposta per litb è corretta.
Utente registrato

39
@InnerJoin: Sì, gestisce i numeri negativi in ​​modo diverso rispetto alla risposta di Litb, ma ciò non lo rende "errato".
Roddy,

39
L'aggiunta di 0,5 prima del troncamento non riesce ad arrotondare al numero intero più vicino per diversi input, tra cui 0,49999999999999994. Vedi blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq

10
@ Sergi0: non esiste "corretto" e "errato" perché esistono più definizioni di arrotondamento che decidono cosa succede a metà percorso. Controlla i tuoi fatti prima di giudicare.
Jon

16
@MuhammadAnnaqeeb: hai ragione, le cose sono migliorate immensamente dal rilascio di C ++ 11. Questa domanda è stata posta e ha risposto in un altro momento in cui la vita era dura e le gioie erano poche. Resta qui come inno agli eroi che vivevano e combattevano allora e per quelle povere anime che non sono ancora in grado di usare strumenti moderni.
Andreas Magnusson,

96

Boost offre un semplice set di funzioni di arrotondamento.

#include <boost/math/special_functions/round.hpp>

double a = boost::math::round(1.5); // Yields 2.0
int b = boost::math::iround(1.5); // Yields 2 as an integer

Per ulteriori informazioni, consultare la documentazione di Boost .

Edit : Dato che C ++ 11, ci sono std::round, std::lroundestd::llround .


2
Stavo già usando boost nel mio progetto, +1 per questo, molto meglio che usare l' floor(value + 0.5)approccio ingenuo !
Gustavo Maciel,

@GustavoMaciel So di essere un po 'in ritardo per il gioco, ma lo è di più floor(value + 0.5).
n. 'pronomi' m.

In realtà non lo è: github.com/boostorg/math/blob/develop/include/boost/math/… 4 anni dopo, vorrei anche dire che floor(value + 0.5)non è affatto ingenuo, ma piuttosto dipende dal contesto e dalla natura di valori che vuoi arrotondare!
Gustavo Maciel il

84

Lo standard C ++ 03 si basa sullo standard C90 per quello che lo standard chiama la libreria C standard che è coperta nella bozza della norma C ++ 03 (la bozza più vicina alla bozza della norma C ++ 03 è N1804 ) 1.2 Riferimenti normativi :

La libreria descritta nella clausola 7 della ISO / IEC 9899: 1990 e nella clausola 7 della ISO / IEC 9899 / Amd.1: 1995 è di seguito denominata Standard C Library. 1)

Se andiamo alla documentazione C per round, lround, llround su cppreference possiamo vedere che round e le relative funzioni fanno parte di C99 e quindi non saranno disponibili in C ++ 03 o precedenti.

In C ++ 11 questo cambia poiché C ++ 11 si basa sulla bozza standard C99 per la libreria standard C e quindi fornisce std :: round e per i tipi di ritorno integrale std :: lround, std :: llround :

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::round( 0.4 ) << " " << std::lround( 0.4 ) << " " << std::llround( 0.4 ) << std::endl ;
    std::cout << std::round( 0.5 ) << " " << std::lround( 0.5 ) << " " << std::llround( 0.5 ) << std::endl ;
    std::cout << std::round( 0.6 ) << " " << std::lround( 0.6 ) << " " << std::llround( 0.6 ) << std::endl ;
}

Un'altra opzione anche da C99 sarebbe std :: trunc che:

Calcola il numero intero più vicino non più grande dell'arg.

#include <iostream>
#include <cmath>

int main()
{
    std::cout << std::trunc( 0.4 ) << std::endl ;
    std::cout << std::trunc( 0.9 ) << std::endl ;
    std::cout << std::trunc( 1.1 ) << std::endl ;

}

Se è necessario supportare applicazioni non C ++ 11, la soluzione migliore sarebbe quella di utilizzare boost round, iround, lround, llround o boost trunc .

Rotolare la tua versione di round è difficile

Arrotolare il tuo probabilmente non vale la pena più difficile di quanto sembri: arrotondare float al numero intero più vicino, parte 1 , arrotondare float al numero intero più vicino, parte 2 e arrotondare float al numero intero più vicino, parte 3 spiega:

Ad esempio, un roll comune che l'implementazione utilizza std::floore aggiunge 0.5non funziona per tutti gli input:

double myround(double d)
{
  return std::floor(d + 0.5);
}

Un input per cui questo fallirà è 0.49999999999999994, ( vederlo dal vivo ).

Un'altra implementazione comune prevede il cast di un tipo a virgola mobile in un tipo integrale, che può invocare un comportamento indefinito nel caso in cui la parte integrale non possa essere rappresentata nel tipo di destinazione. Possiamo vederlo dalla bozza della sezione standard C ++ 4.9 Conversioni floating-integral che dice ( enfasi mia ):

Un valore di un tipo a virgola mobile può essere convertito in un valore di un tipo intero. La conversione tronca; cioè, la parte frazionaria viene scartata. Il comportamento non è definito se il valore troncato non può essere rappresentato nel tipo di destinazione. [...]

Per esempio:

float myround(float f)
{
  return static_cast<float>( static_cast<unsigned int>( f ) ) ;
}

Data std::numeric_limits<unsigned int>::max()è 4294967295quindi la seguente chiamata:

myround( 4294967296.5f ) 

causerà overflow, ( vederlo dal vivo ).

Possiamo vedere quanto sia davvero difficile osservando questa risposta al modo conciso di implementare round () in C? quale riferimento alla versione newlibs del galleggiante di precisione singolo rotondo. È una funzione molto lunga per qualcosa che sembra semplice. Sembra improbabile che chiunque non abbia una conoscenza intima delle implementazioni in virgola mobile possa implementare correttamente questa funzione:

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

D'altra parte, se nessuna delle altre soluzioni è utilizzabile, newlib potrebbe potenzialmente essere un'opzione poiché è un'implementazione ben collaudata.


5
@downvoter, per favore, spiega cosa può essere migliorato? La stragrande maggioranza della risposta qui è semplicemente sbagliata dal momento che tentano di girare il proprio round, che falliscono tutti in una forma o nell'altra. Se manca qualcosa nella mia spiegazione, per favore fatemelo sapere.
Shafik Yaghmour,

1
Bella risposta completa, in particolare la parte appena sotto 0,5. Un'altra nicchia: round(-0.0). La specifica C non sembra specificare. Mi aspetterei -0.0di conseguenza.
chux - Ripristina Monica il

3
@chux interessante, e lo standard IEEE 754-2008 specifica che l'arrotondamento conserva segni di zeri e infiniti (vedi 5.9).
Ruslan,

1
@Shafik questa è un'ottima risposta. Non ho mai pensato che anche l'arrotondamento fosse un'operazione non banale.
Ruslan,

1
Forse vale la pena ricordare che std::rint()è spesso preferibile std::round()quando C ++ 11 è disponibile per motivi numerici e di prestazione. Usa l'attuale modalità di arrotondamento, diversamente dalla round()modalità speciale. Può essere molto più efficiente su x86, dove rintpuò essere integrato in una singola istruzione. (gcc e clang lo fanno anche senza -ffast-math godbolt.org/g/5UsL2e , mentre solo clang incorpora il quasi equivalente nearbyint()) ARM ha il supporto per istruzioni singole round(), ma su x86 può solo essere in linea con più istruzioni e solo con-ffast-math
Peter Cordes

71

Vale la pena notare che se si desidera un risultato intero dall'arrotondamento, non è necessario passarlo attraverso il soffitto o il pavimento. Vale a dire,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}

3
Tuttavia, non dà il risultato atteso per 0.49999999999999994 (beh, a seconda di cosa ti aspetti ovviamente, ma 0 mi sembra più ragionevole di 1)
stijn

@stijn Buona cattura. Ho scoperto che l'aggiunta del lungo suffisso letterale doppio alle mie costanti ha risolto il problema del tuo esempio, ma non so se ci sono altri esempi di precisione che non sarebbero stati individuati.
kalaxy,

1
tra l'altro se aggiungi 0,49999999999999994 invece di 0,5, funziona bene sia per 0,4999999999999999994 che 5000000000000001.0 come input. Non sono sicuro che sia ok per tutti i valori, e non sono riuscito a trovare alcun riferimento che affermi che questa è la soluzione definitiva.
stijn

1
@stijn Va bene per tutti i valori, se non ti interessa in quale direzione vengono arrotondati i valori esattamente tra due numeri interi. Senza pensare, lo dimostrerei con l'analisi dei casi con i seguenti casi: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. Ho anche testato esaurientemente la custodia a precisione singola.
Pascal Cuoq,

3
Per 4.9 [conv.fpint], "Il comportamento non è definito se il valore troncato non può essere rappresentato nel tipo di destinazione." , quindi questo è un po 'pericoloso. Altre risposte SO descrivono come farlo in modo robusto.
Tony Delroy,

41

È disponibile da C ++ 11 in cmath (secondo http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf )

#include <cmath>
#include <iostream>

int main(int argc, char** argv) {
  std::cout << "round(0.5):\t" << round(0.5) << std::endl;
  std::cout << "round(-0.5):\t" << round(-0.5) << std::endl;
  std::cout << "round(1.4):\t" << round(1.4) << std::endl;
  std::cout << "round(-1.4):\t" << round(-1.4) << std::endl;
  std::cout << "round(1.6):\t" << round(1.6) << std::endl;
  std::cout << "round(-1.6):\t" << round(-1.6) << std::endl;
  return 0;
}

Produzione:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2

1
ci sono anche lrounde llroundper i risultati integrali
sp2danny,

@ sp2danny: o meglio, lrintper usare l'attuale modalità di arrotondamento invece del roundfunky tie-away-from-zero tiebreak.
Peter Cordes,

27

Di solito è implementato come floor(value + 0.5).

Modifica: e probabilmente non è chiamato round poiché ci sono almeno tre algoritmi di arrotondamento che conosco: round a zero, round al numero intero più vicino e arrotondamento del banco. Stai chiedendo il numero intero tondo o più vicino.


1
È utile fare una distinzione tra le diverse versioni di 'round'. È bene sapere anche quando scegliere quale.
xtofl,

5
Esistono infatti diversi algoritmi di arrotondamento che possono tutti pretendere ragionevolmente di essere "corretti". Tuttavia floor (valore + 0,5) non è uno di questi. Per alcuni valori, come 0.49999997f o il doppio equivalente, la risposta è sbagliata: verrà arrotondata a 1.0 quando tutti concordano sul fatto che dovrebbe essere zero. Vedi questo post per i dettagli: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson

14

Ci sono 2 problemi che stiamo esaminando:

  1. conversioni di arrotondamento
  2. conversione di tipo.

Le conversioni di arrotondamento significano arrotondamento ± float / double al pavimento / float / double più vicino. Potrebbe essere il tuo problema finisce qui. Ma se si prevede di restituire Int / Long, è necessario eseguire la conversione del tipo e quindi il problema "Overflow" potrebbe colpire la soluzione. Quindi, controlla se ci sono errori nella tua funzione

long round(double x) {
   assert(x >= LONG_MIN-0.5);
   assert(x <= LONG_MAX+0.5);
   if (x >= 0)
      return (long) (x+0.5);
   return (long) (x-0.5);
}

#define round(x) ((x) < LONG_MIN-0.5 || (x) > LONG_MAX+0.5 ?\
      error() : ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))

da: http://www.cs.tut.fi/~jkorpela/round.html


L'uso LONG_MIN-0.5e l' LONG_MAX+0.5 introduzione di complicazioni in quanto la matematica potrebbe non essere esatta. LONG_MAXpuò superare la doubleprecisione per la conversione esatta. Probabilmente vogliono ulteriori assert(x < LONG_MAX+0.5); (<vs <=) poiché LONG_MAX+0.5possono essere esattamente rappresentabili e (x)+0.5possono avere un risultato esatto del LONG_MAX+1quale fallisce il longcast. Anche altri problemi d'angolo.
chux - Ripristina Monica il

Non chiamare la tua funzione round(double), esiste già una funzione di libreria matematica standard con quel nome (in C ++ 11), quindi è confusa. Utilizzare std::lrint(x)se è disponibile.
Peter Cordes,

11

Un certo tipo di arrotondamento è implementato anche in Boost:

#include <iostream>

#include <boost/numeric/conversion/converter.hpp>

template<typename T, typename S> T round2(const S& x) {
  typedef boost::numeric::conversion_traits<T, S> Traits;
  typedef boost::numeric::def_overflow_handler OverflowHandler;
  typedef boost::numeric::RoundEven<typename Traits::source_type> Rounder;
  typedef boost::numeric::converter<T, S, Traits, OverflowHandler, Rounder> Converter;
  return Converter::convert(x);
}

int main() {
  std::cout << round2<int, double>(0.1) << ' ' << round2<int, double>(-0.1) << ' ' << round2<int, double>(-0.9) << std::endl;
}

Nota che funziona solo se esegui una conversione in numero intero.


2
Boost offre anche una serie di semplici funzioni di arrotondamento; vedi la mia risposta.
Daniel Wolf,

Puoi anche usare boost:numeric::RoundEven< double >::nearbyintdirettamente se non vuoi numeri interi. @DanielWolf nota che la semplice funzione è implementata usando +0.5 che ha problemi come indicato da aka.nice
stijn

6

Puoi arrotondare ad una precisione di n cifre con:

double round( double x )
{
const double sd = 1000; //for accuracy to 3 decimal places
return int(x*sd + (x<0? -0.5 : 0.5))/sd;
}

4
A meno che le dimensioni int del tuo compilatore non siano predefinite a 1024 bit, questo non sarà accurato per l'enorme doppio ...
aka.nice

Penso che sia accettabile dato quando verrà usato: se il tuo doppio valore è 1,0 e + 19, arrotondare per 3 posti non ha senso.
Carl,

3
certo, ma la domanda è per un round generico e non puoi controllare come verrà usato. Non c'è motivo per cui il round fallisca dove il soffitto e il pavimento non lo farebbero.
aka.nice

Questo ha un comportamento indefinito per args al di fuori dell'intervallo di int. (In pratica x86, fuori-di-intervallo valori FP farà CVTTSD2SIprodurre0x80000000 la sequenza di bit numero intero, cioè INT_MIN, che sarà poi indietro convertita double.
Peter Cordes

5

In questi giorni non dovrebbe essere un problema usare un compilatore C ++ 11 che include una libreria matematica C99 / C ++ 11. Ma poi la domanda diventa: quale funzione di arrotondamento scegli?

C99 / C ++ 11 round()spesso non è in realtà la funzione di arrotondamento desiderata . Utilizza una modalità di arrotondamento funky che arrotonda a zero come un pareggio su casi a metà strada ( +-xxx.5000). Se vuoi specificamente quella modalità di arrotondamento o stai prendendo di mira un'implementazione C ++ dove round()è più veloce di rint(), allora usala (o emula il suo comportamento con una delle altre risposte su questa domanda che l'ha presa al valore nominale e riprodotta attentamente quella specifica comportamento di arrotondamento).

round()L'arrotondamento è diverso dal round predefinito IEEE754 alla modalità più vicina anche come pareggio . Anche il più vicino evita il bias statistico nella grandezza media dei numeri, ma tende al bias verso i numeri pari.

Esistono due funzioni di arrotondamento della libreria matematica che utilizzano l'attuale modalità di arrotondamento predefinita: std::nearbyint()e std::rint(), entrambe aggiunte in C99 / C ++ 11, quindi sono disponibili in qualsiasi momento std::round(). L'unica differenza è che nearbyintnon genera mai FE_INEXACT.

Preferisci rint()per motivi di prestazioni : gcc e clang lo incorporano entrambi più facilmente, ma gcc non si allinea mai nearbyint()(anche con -ffast-math)


gcc / clang per x86-64 e AArch64

Ho messo alcune funzioni di test su Compiler Explorer di Matt Godbolt , dove puoi vedere l'output source + asm (per più compilatori). Per ulteriori informazioni sulla lettura dell'output del compilatore, consulta le Domande e risposte e il discorso di Matt CppCon2017: “Cosa ha fatto di recente il mio compilatore per me? Liberare il coperchio del compilatore " ,

Nel codice FP, di solito è una grande vittoria incorporare piccole funzioni. Soprattutto su non Windows, dove la convenzione di chiamata standard non ha registri conservati, quindi il compilatore non può mantenere alcun valore FP nei registri XMM attraverso uncall . Quindi, anche se non si conosce davvero l'asm, è comunque possibile vedere facilmente se si tratta solo di una chiamata in coda alla funzione di libreria o se è in linea con una o due istruzioni matematiche. Tutto ciò che è in linea con una o due istruzioni è meglio di una chiamata di funzione (per questo particolare compito su x86 o ARM).

Su x86, tutto ciò che è in linea con SSE4.1 roundsdpuò auto-vettorializzare con SSE4.1 roundpd(o AVX vroundpd). (FP-> conversioni di numeri interi sono disponibili anche in formato SIMD compresso, ad eccezione di FP-> 64-bit intero che richiede AVX512.)

  • std::nearbyint():

    • x86 clang: allinea a un singolo insn con -msse4.1 .
    • x86 gcc: si allinea a un singolo insn solo con -msse4.1 -ffast-math, e solo su gcc 5.4 e precedenti . Successivamente gcc non lo incorpora mai (forse non si sono resi conto che uno dei bit immediati può sopprimere l'eccezione inesatta? Questo è ciò che usa clang, ma gcc più vecchio usa lo stesso immediato di rintquando lo incorpora)
    • AArch64 gcc6.3: allinea a una singola insn per impostazione predefinita.
  • std::rint:

    • x86 clang: allinea a un singolo insn con -msse4.1
    • x86 gcc7: si allinea a un singolo insn con -msse4.1. (Senza SSE4.1, in linea con diverse istruzioni)
    • x86 gcc6.x e precedenti: allinea a un singolo insn con -ffast-math -msse4.1.
    • AArch64 gcc: allinea a una singola insn per impostazione predefinita
  • std::round:

    • x86 clang: non in linea
    • x86 gcc: allinea a più istruzioni con -ffast-math -msse4.1 , richiede due costanti vettoriali.
    • AArch64 gcc: si allinea a una singola istruzione (supporto HW per questa modalità di arrotondamento, impostazione predefinita IEEE e molti altri).
  • std::floor/ std::ceil/std::trunc

    • x86 clang: allinea a un singolo insn con -msse4.1
    • x86 gcc7.x: allinea a un singolo insn con -msse4.1
    • x86 gcc6.x e precedenti: allinea a un singolo insn con -ffast-math -msse4.1
    • AArch64 gcc: allinea per impostazione predefinita a una singola istruzione

Arrotondamento a int/ long/ long long:

Hai due opzioni qui: usa lrint(come rintma restituisce long, o long longper llrint), oppure usa una funzione di arrotondamento FP-> FP e poi converti in un tipo intero nel modo normale (con troncamento). Alcuni compilatori ottimizzano in un modo meglio dell'altro.

long l = lrint(x);

int  i = (int)rint(x);

Nota che prima int i = lrint(x)converte floato double-> long, quindi tronca l'interoint . Questo fa la differenza per gli interi fuori range: comportamento indefinito in C ++, ma ben definito per le istruzioni x86 FP -> int (che il compilatore emetterà a meno che non veda l'UB al momento della compilazione mentre fa una propagazione costante, quindi è permesso di creare codice che si interrompe se mai eseguito).

Su x86, viene prodotta una conversione FP-> numero intero che trabocca l'intero INT_MINo LLONG_MIN(un modello di bit 0x8000000o l'equivalente a 64 bit, con solo il set di bit di segno). Intel chiama questo valore "intero indefinito". (Vedi l' cvttsd2siinserimento manuale , l'istruzione SSE2 che converte (con troncamento) il doppio scalare in intero con segno. È disponibile con destinazione intera a 32 o 64 bit (solo in modalità 64 bit). C'è anche un cvtsd2si(converti con arrotondamento corrente mode), che è ciò che vorremmo che il compilatore emettesse, ma sfortunatamente gcc e clang non lo faranno senza -ffast-math.

Attenzione anche che FP a / da unsignedint / long è meno efficiente su x86 (senza AVX512). La conversione a 32 bit senza segno su una macchina a 64 bit è piuttosto economica; converti in 64 bit con segno e troncalo. Ma per il resto è significativamente più lento.

  • x86 clang con / senza -ffast-math -msse4.1: (int/long)rintallinea a roundsd/ cvttsd2si. (mancata ottimizzazione a cvtsd2si). lrintnon è in linea affatto.

  • x86 gcc6.x e precedenti senza -ffast-math: nessuna delle due linee

  • x86 gcc7 senza -ffast-math: (int/long)rintarrotonda e converte separatamente (con 2 istruzioni totali di SSE4.1 è abilitato, altrimenti con un gruppo di codice in linea per rintsenza roundsd). lrintnon in linea.
  • x86 gcc con -ffast-math : tutti i modi in linea a cvtsd2si(ottimale) , non è necessario SSE4.1.

  • AArch64 gcc6.3 senza -ffast-math: (int/long)rintallinea a 2 istruzioni. lrintnon in linea

  • AArch64 gcc6.3 con -ffast-math: (int/long)rintcompila in una chiamata a lrint. lrintnon in linea. Questa potrebbe essere una mancata ottimizzazione a meno che le due istruzioni senza le -ffast-mathquali non siano molto lente.

TODO: ICC e MSVC sono disponibili anche su Godbolt, ma non ho esaminato il loro output per questo. modifiche benvenute ... Inoltre: sarebbe più utile scomporre prima per compilatore / versione e poi per funzione all'interno di quello? Molte persone non cambieranno compilatori in base alla loro capacità di compilare arrotondamenti interi FP-> FP o FP->.
Peter Cordes,

2
+1 per raccomandare rint()dove è una scelta fattibile, che di solito è il caso. Immagino che il nome round()implichi ad alcuni programmatori che questo è quello che vogliono, mentre rint()sembra misterioso. Si noti che round()non viene utilizzata una modalità di arrotondamento "funky": arrotondare al punto più vicino è una modalità di arrotondamento IEEE-754 (2008) ufficiale. È curioso che nearbyint()non sia allineato, dato che è in gran parte uguale rint()e dovrebbe essere identico in -ffast-mathcondizioni. Mi sembra un bug-ish.
njuffa,

4

Attenzione floor(x+0.5). Ecco cosa può accadere per i numeri dispari nell'intervallo [2 ^ 52,2 ^ 53]:

-bash-3.2$ cat >test-round.c <<END

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

int main() {
    double x=5000000000000001.0;
    double y=round(x);
    double z=floor(x+0.5);
    printf("      x     =%f\n",x);
    printf("round(x)    =%f\n",y);
    printf("floor(x+0.5)=%f\n",z);
    return 0;
}
END

-bash-3.2$ gcc test-round.c
-bash-3.2$ ./a.out
      x     =5000000000000001.000000
round(x)    =5000000000000001.000000
floor(x+0.5)=5000000000000002.000000

Questo è http://bugs.squeak.org/view.php?id=7134 . Usa una soluzione come quella di @konik.

La mia versione robusta sarebbe simile a:

double round(double x)
{
    double truncated,roundedFraction;
    double fraction = modf(x, &truncated);
    modf(2.0*fraction, &roundedFraction);
    return truncated + roundedFraction;
}

Un altro motivo per evitare il pavimento (x + 0,5) è indicato qui .


2
Sono interessato a conoscere i voti negativi. È perché il pareggio è stato risolto da zero piuttosto che al più vicino pari?
aka.nice

1
Nota: la specifica C dice "Arrotondamento di casi a metà strada da zero, indipendentemente dalla direzione di arrotondamento corrente", quindi l'arrotondamento senza riguardo a pari / dispari è conforme.
chux - Ripristina Monica il

4

Se alla fine desideri convertire l' doubleoutput della tua round()funzione in un int, le soluzioni accettate di questa domanda saranno simili a:

int roundint(double r) {
  return (int)((r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5));
}

Questo si attesta a circa 8,88 ns sulla mia macchina quando viene passato in valori uniformemente casuali.

Quanto segue è funzionalmente equivalente, per quanto posso dire, ma cronometra a 2.48 ns sulla mia macchina, per un significativo vantaggio in termini di prestazioni:

int roundint (double r) {
  int tmp = static_cast<int> (r);
  tmp += (r-tmp>=.5) - (r-tmp<=-.5);
  return tmp;
}

Tra i motivi della migliore prestazione c'è la diramazione saltata.


Questo ha un comportamento indefinito per args al di fuori dell'intervallo di int. (In pratica x86, fuori-di-intervallo valori FP farà CVTTSD2SIprodurre0x80000000 la sequenza di bit numero intero, cioè INT_MIN, che sarà poi indietro convertita double.
Peter Cordes

2

Non è necessario implementare nulla, quindi non sono sicuro del motivo per cui così tante risposte implicano definizioni, funzioni o metodi.

Nel C99

Abbiamo i seguenti ee intestazione <tgmath.h> per le macro di tipo generico.

#include <math.h>
double round (double x);
float roundf (float x);
long double roundl (long double x);

Se non puoi compilarlo, probabilmente hai tralasciato la libreria matematica. Un comando simile a questo funziona su ogni compilatore C che ho (diversi).

gcc -lm -std=c99 ...

In C ++ 11

Abbiamo i seguenti e ulteriori sovraccarichi in #include <cmath> che si basano su virgola mobile IEEE a precisione doppia.

#include <math.h>
double round (double x);
float round (float x);
long double round (long double x);
double round (T x);

Esistono equivalenti nello spazio dei nomi std .

Se non è possibile compilare questo, è possibile che si stia utilizzando la compilazione C anziché C ++. Il seguente comando di base non produce né errori né avvisi con g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 e Visual C ++ 2015 Community.

g++ -std=c++11 -Wall

Con la divisione ordinale

Quando si dividono due numeri ordinali, dove T è breve, int, lungo o un altro ordinale, l'espressione arrotondata è questa.

T roundedQuotient = (2 * integerNumerator + 1)
    / (2 * integerDenominator);

Precisione

Non vi è dubbio che inesattezze in virgola mobile appaiono inesattezze dall'aspetto strano, ma questo è solo quando compaiono i numeri e ha poco a che fare con l'arrotondamento.

La fonte non è solo il numero di cifre significative nella mantissa della rappresentazione IEEE di un numero in virgola mobile, è correlato al nostro pensiero decimale come esseri umani.

Dieci è il prodotto di cinque e due e 5 e 2 sono relativamente primi. Pertanto, gli standard IEEE in virgola mobile non possono essere rappresentati perfettamente come numeri decimali per tutte le rappresentazioni digitali binarie.

Questo non è un problema con gli algoritmi di arrotondamento. È la realtà matematica che dovrebbe essere presa in considerazione durante la selezione dei tipi e la progettazione di calcoli, immissione di dati e visualizzazione di numeri. Se un'applicazione visualizza le cifre che mostrano questi problemi di conversione decimale-binaria, l'applicazione esprime visivamente l'accuratezza che non esiste nella realtà digitale e dovrebbe essere modificata.


1
"Non sono sicuro del motivo per cui così tante risposte coinvolgono definizioni, funzioni o metodi." Dai un'occhiata a quando è stato chiesto: C ++ 11 non era ancora uscito. ;)
jaggedSpire

@jaggedSpire, allora dammi un pollice in alto, se lo ritieni opportuno, perché tutte le risposte con un punteggio elevato sono obsolete e fuorvianti nel contesto dei compilatori più comunemente utilizzati oggi.
FauChristian

2

Funzione double round(double)con l'uso della modffunzione:

double round(double x)
{
    using namespace std;

    if ((numeric_limits<double>::max() - 0.5) <= x)
        return numeric_limits<double>::max();

    if ((-1*std::numeric_limits<double>::max() + 0.5) > x)
        return (-1*std::numeric_limits<double>::max());

    double intpart;
    double fractpart = modf(x, &intpart);

    if (fractpart >= 0.5)
        return (intpart + 1);
    else if (fractpart >= -0.5)
        return intpart;
    else
        return (intpart - 1);
    }

Per essere compilato pulito, sono necessari "math.h" e "limiti". La funzione funziona secondo uno schema di arrotondamento seguente:

  • il giro di 5.0 è 5.0
  • il giro di 3.8 è 4.0
  • il giro di 2.3 è 2.0
  • il giro di 1.5 è 2.0
  • il giro di 0.501 è 1.0
  • il giro di 0,5 è 1,0
  • il giro di 0.499 è 0.0
  • il giro di 0,01 è 0,0
  • il giro di 0,0 è 0,0
  • il giro di -0,01 è -0,0
  • il giro di -0,499 è -0,0
  • il giro di -0,5 è -0,0
  • il giro di -0.501 è -1.0
  • il giro di -1.5 è -1.0
  • il giro di -2.3 è -2.0
  • il giro di -3,8 è -4,0
  • il giro di -5,0 è -5,0

2
Questa è una buona soluzione Non sono sicuro che l'arrotondamento da -1,5 a -1,0 sia standard, tuttavia mi aspetterei -2,0 dalla simmetria. Inoltre non vedo il punto della guardia principale, i primi due se potessero essere rimossi.
aka.nice

2
Ho verificato lo standard ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf e dall'appendice B.5.2.4, la funzione di arrotondamento deve essere effettivamente simmetrica, rounding_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice

Questo sarà lento rispetto a C ++ 11 rint()o nearbyint(), ma se davvero non puoi usare un compilatore che fornisce una corretta funzione di arrotondamento e hai bisogno di precisione più delle prestazioni ...
Peter Cordes

1

Se devi essere in grado di compilare il codice in ambienti che supportano lo standard C ++ 11, ma devi anche essere in grado di compilare lo stesso codice in ambienti che non lo supportano, puoi usare una macro di funzione per scegliere tra std :: round () e una funzione personalizzata per ciascun sistema. Basta passare -DCPP11o /DCPP11al compilatore conforme a C ++ 11 (o utilizzare le macro della versione integrata) e creare un'intestazione come questa:

// File: rounding.h
#include <cmath>

#ifdef CPP11
    #define ROUND(x) std::round(x)
#else    /* CPP11 */
    inline double myRound(double x) {
        return (x >= 0.0 ? std::floor(x + 0.5) : std::ceil(x - 0.5));
    }

    #define ROUND(x) myRound(x)
#endif   /* CPP11 */

Per un rapido esempio, consultare http://ideone.com/zal709 .

Questo si avvicina a std :: round () in ambienti non conformi a C ++ 11, inclusa la conservazione del bit di segno per -0.0. Tuttavia, potrebbe causare un leggero calo delle prestazioni e probabilmente avere problemi con l'arrotondamento di determinati valori "a problema" in virgola mobile noti come 0,4999999999999999994 o valori simili.

In alternativa, se hai accesso a un compilatore conforme a C ++ 11, puoi semplicemente prendere std :: round () dalla sua <cmath>intestazione e usarlo per creare la tua intestazione che definisce la funzione se non è già definita. Si noti che questa potrebbe non essere una soluzione ottimale, tuttavia, soprattutto se è necessario compilare per più piattaforme.


1

Sulla base della risposta di Kalaxy, la seguente è una soluzione basata su modelli che arrotonda qualsiasi numero in virgola mobile al tipo intero più vicino in base all'arrotondamento naturale. Inoltre, genera un errore in modalità debug se il valore è al di fuori dell'intervallo del tipo intero, fungendo quindi approssimativamente da una funzione di libreria valida.

    // round a floating point number to the nearest integer
    template <typename Arg>
    int Round(Arg arg)
    {
#ifndef NDEBUG
        // check that the argument can be rounded given the return type:
        if (
            (Arg)std::numeric_limits<int>::max() < arg + (Arg) 0.5) ||
            (Arg)std::numeric_limits<int>::lowest() > arg - (Arg) 0.5)
            )
        {
            throw std::overflow_error("out of bounds");
        }
#endif

        return (arg > (Arg) 0.0) ? (int)(r + (Arg) 0.5) : (int)(r - (Arg) 0.5);
    }

1
Come ho sottolineato nella mia risposta, l' aggiunta 0.5non funziona in tutti i casi. Sebbene almeno tu affronti il ​​problema dell'overflow in modo da evitare comportamenti indefiniti.
Shafik Yaghmour,

1

Come sottolineato nei commenti e in altre risposte, la libreria standard ISO C ++ non è stata aggiunta round()fino a ISO C ++ 11, quando questa funzione è stata inserita facendo riferimento alla libreria matematica standard ISO C99.

Per operandi positivi in ​​[½, ub ] round(x) == floor (x + 0.5), dove ub è 2 23 per floatquando mappato a IEEE-754 (2008) binary32e 2 52 per doublequando è mappato su IEEE-754 (2008) binary64. I numeri 23 e 52 corrispondono al numero di bit di mantissa memorizzati in questi due formati a virgola mobile. Per operandi positivi in ​​[+0, ½) round(x) == 0e per operandi positivi in ​​( ub , + ∞] round(x) == x. Poiché la funzione è simmetrica rispetto all'asse x, gli argomenti negativi xpossono essere gestiti in base around(-x) == -round(x) .

Questo porta al codice compatto di seguito. Si compila in un numero ragionevole di istruzioni della macchina su varie piattaforme. Ho osservato il codice più compatto sulle GPU, dove sono my_roundf()necessarie circa una dozzina di istruzioni. A seconda dell'architettura del processore e della toolchain, questo approccio basato su virgola mobile potrebbe essere più veloce o più lento dell'implementazione basata su numeri interi di newlib a cui si fa riferimento in una risposta diversa .

Ho testato my_roundf()esaurientemente l' roundf()implementazione di newlib usando il compilatore Intel versione 13, con entrambi /fp:stricte /fp:fast. Ho anche verificato che la versione di newlib corrisponda roundf()a nella mathimflibreria del compilatore Intel. Test approfonditi non sono possibili per la doppia precisione round(), tuttavia il codice è strutturalmente identico all'implementazione a precisione singola.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

float my_roundf (float x)
{
    const float half = 0.5f;
    const float one = 2 * half;
    const float lbound = half;
    const float ubound = 1L << 23;
    float a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floorf (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

double my_round (double x)
{
    const double half = 0.5;
    const double one = 2 * half;
    const double lbound = half;
    const double ubound = 1ULL << 52;
    double a, f, r, s, t;
    s = (x < 0) ? (-one) : one;
    a = x * s;
    t = (a < lbound) ? x : s;
    f = (a < lbound) ? 0 : floor (a + half);
    r = (a > ubound) ? x : (t * f);
    return r;
}

uint32_t float_as_uint (float a)
{
    uint32_t r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float uint_as_float (uint32_t a)
{
    float r;
    memcpy (&r, &a, sizeof(r));
    return r;
}

float newlib_roundf (float x)
{
    uint32_t w;
    int exponent_less_127;

    w = float_as_uint(x);
    /* Extract exponent field. */
    exponent_less_127 = (int)((w & 0x7f800000) >> 23) - 127;
    if (exponent_less_127 < 23) {
        if (exponent_less_127 < 0) {
            /* Extract sign bit. */
            w &= 0x80000000;
            if (exponent_less_127 == -1) {
                /* Result is +1.0 or -1.0. */
                w |= ((uint32_t)127 << 23);
            }
        } else {
            uint32_t 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 so raise FE_INVALID by adding */
            return x + x;
        } else {
            return x;
        }
    }
    x = uint_as_float (w);
    return x;
}

int main (void)
{
    uint32_t argi, resi, refi;
    float arg, res, ref;

    argi = 0;
    do {
        arg = uint_as_float (argi);
        ref = newlib_roundf (arg);
        res = my_roundf (arg);
        resi = float_as_uint (res);
        refi = float_as_uint (ref);
        if (resi != refi) { // check for identical bit pattern
            printf ("!!!! arg=%08x  res=%08x  ref=%08x\n", argi, resi, refi);
            return EXIT_FAILURE;
        }
        argi++;
    } while (argi);
    return EXIT_SUCCESS;
}

Ho fatto una modifica per evitare di supporre che intsia larga più di 16 bit. Si presume ovviamente che floatsia IEEE754 binario32 a 4 byte. Un C ++ 11 static_asserto forse una macro #ifdef/ #errorpotrebbe verificarlo. (Ma ovviamente se è disponibile C ++ 11, dovresti usare std::round, o per l'attuale modalità di arrotondamento, std::rintche si adatta perfettamente a gcc e clang).
Peter Cordes,

BTW, gcc -ffast-math -msse4.1inline std::round()a una add( AND(x, L1), OR(x,L2), e poi una roundsd. cioè implementa abbastanza efficientemente roundin termini di rint. Ma non c'è motivo di farlo manualmente nel sorgente C ++, perché se hai std::rint()o std::nearbyint()hai anche std::round(). Vedi la mia risposta per un link godbolt e una carrellata di ciò che è in linea o meno con diverse versioni di gcc / clang.
Peter Cordes,

@PeterCordes Sono ben consapevole di come implementare in modo round()efficiente in termini di rint()(quando quest'ultimo opera in modalità round-to-più-vicino-o-pari): l'ho implementato per la libreria matematica standard CUDA. Tuttavia, questa domanda sembrava chiedersi come implementare round()con C ++ prima di C ++ 11, quindi rint()non sarebbe disponibile neanche, solo floor()e ceil().
njuffa,

@PeterCordes Siamo spiacenti, ho sbagliato a parlare. round()è facilmente sintetizzati da rint()in tutto a zero modalità, alias trunc(). Non avrei dovuto rispondere prima del primo caffè.
njuffa,

1
@PeterCordes Sono d'accordo che è probabile che OP non abbia bisogno del comportamento di arrotondamento specifico di round(); la maggior parte dei programmatori semplicemente non è a conoscenza della distinzione tra round()vs rint()con round-to-più vicino-pari, in cui quest'ultimo viene solitamente fornito direttamente dall'hardware e quindi più efficiente; L'ho spiegato nella Guida alla programmazione CUDA per rendere consapevoli i programmatori: "Il modo raccomandato per arrotondare un operando a virgola mobile a precisione singola su un numero intero, con il risultato che è un numero a virgola mobile a precisione singola rintf(), non è roundf()".
njuffa,

0

Uso la seguente implementazione di round in asm per l'architettura x86 e il C ++ specifico per MS VS:

__forceinline int Round(const double v)
{
    int r;
    __asm
    {
        FLD     v
        FISTP   r
        FWAIT
    };
    return r;
}

UPD: per restituire un doppio valore

__forceinline double dround(const double v)
{
    double r;
    __asm
    {
        FLD     v
        FRNDINT
        FSTP    r
        FWAIT
    };
    return r;
}

Produzione:

dround(0.1): 0.000000000000000
dround(-0.1): -0.000000000000000
dround(0.9): 1.000000000000000
dround(-0.9): -1.000000000000000
dround(1.1): 1.000000000000000
dround(-1.1): -1.000000000000000
dround(0.49999999999999994): 0.000000000000000
dround(-0.49999999999999994): -0.000000000000000
dround(0.5): 0.000000000000000
dround(-0.5): -0.000000000000000

Il valore del risultato deve essere in virgola mobile con doppia precisione.
Cercatore della verità,

@ truthseeker: Sì, ho dovuto vedere il tipo richiesto di valore di ritorno. OK, vedi "UPD".
Aleksey F.

Si spera che il compilatore sia in linea rint()o nearbyint()verso un'istruzione SSE4.1 roundsdo un'istruzione x87 frndint, che sarà molto più veloce dei due round trip di memorizzazione / ricarica necessari per utilizzare questo asm in linea sui dati in un registro. MSVC inline asm fa molto schifo per il wrapping di singole istruzioni come frndintperché non c'è modo di ottenere l'input in un registro. Usarlo alla fine di una funzione con il risultato st(0)potrebbe essere affidabile come modo per restituire l'output; apparentemente è sicuro per gli eaxinteri, anche quando incorpora la funzione contenente l'asm.
Peter Cordes,

@PeterCordes Le ottimizzazioni moderne sono benvenute. Tuttavia non sono stato in grado di utilizzare SSE4.1 in quanto non esisteva in quel momento. Il mio scopo era quello di fornire l'implementazione minima di round che potesse funzionare anche su vecchie famiglie Intel P3 o P4 degli anni 2000.
Aleksey F.,

P3 non ha nemmeno SSE2, quindi il compilatore sarà già utilizzando x87 per double, e quindi dovrebbe essere in grado di emettere frndintstessa per rint(). Se il tuo compilatore utilizza SSE2, doublenon vale la pena rimbalzare da un registro XMM a x87 e viceversa.
Peter Cordes,

0

Il modo migliore per arrotondare un valore variabile con "n" posizioni decimali è il seguente con O (1):

Dobbiamo arrotondare il valore di 3 posti, ovvero n = 3. Quindi,

float a=47.8732355;
printf("%.3f",a);

-4
// Convert the float to a string
// We might use stringstream, but it looks like it truncates the float to only
//5 decimal points (maybe that's what you want anyway =P)

float MyFloat = 5.11133333311111333;
float NewConvertedFloat = 0.0;
string FirstString = " ";
string SecondString = " ";
stringstream ss (stringstream::in | stringstream::out);
ss << MyFloat;
FirstString = ss.str();

// Take out how ever many decimal places you want
// (this is a string it includes the point)
SecondString = FirstString.substr(0,5);
//whatever precision decimal place you want

// Convert it back to a float
stringstream(SecondString) >> NewConvertedFloat;
cout << NewConvertedFloat;
system("pause");

Potrebbe essere un modo sporco inefficiente di conversione ma diamine, funziona lol. Ed è buono, perché si applica al float reale. Non influisce solo visivamente sull'output.


Questo è esilarantemente inefficiente, e inoltre si tronca (scartando sempre le cifre finali) invece di arrotondare al più vicino.
Peter Cordes,

-6

L'ho fatto:

#include <cmath.h>

using namespace std;

double roundh(double number, int place){

    /* place = decimal point. Putting in 0 will make it round to whole
                              number. putting in 1 will round to the
                              tenths digit.
    */

    number *= 10^place;
    int istack = (int)floor(number);
    int out = number-istack;
    if (out < 0.5){
        floor(number);
        number /= 10^place;
        return number;
    }
    if (out > 0.4) {
        ceil(number);
        number /= 10^place;
        return number;
    }
}

3
Non intendevi pow (10, posto) piuttosto che l'operatore binario ^ in 10 ^ posto? 10 ^ 2 sulla mia macchina mi dà 8 !! Tuttavia sul mio Mac 10.7.4 e gcc, il codice non funziona, restituendo il valore originale.
Pete855217,
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.