round () pour float en C ++

232

J'ai besoin d'une simple fonction d'arrondi à virgule flottante, donc:

double round(double);

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

Je peux trouver ceil()et floor()dans le math.h - mais pas round().

Est-il présent dans la bibliothèque C ++ standard sous un autre nom, ou est-il manquant ??

Roddy
la source
1
Si vous voulez simplement sortir le nombre sous forme de nombre arrondi, il semble que vous puissiez le faire std::cout << std::fixed << std::setprecision(0) << -0.9, par exemple.
Frank
44
Protéger cela ... Les nouveaux utilisateurs avec de nouveaux schémas d'arrondi brillants devraient d'abord lire les réponses existantes.
Shog9
12
roundest disponible depuis C ++ 11 dans <cmath>. Malheureusement, si vous êtes dans Microsoft Visual Studio, il manque toujours: connect.microsoft.com/VisualStudio/feedback/details/775474/…
Alessandro Jacopson
3
Comme je le note dans ma réponse, rouler le vôtre roundcomporte de nombreuses mises en garde. Avant C ++ 11, la norme s'appuyait sur C90 qui ne comprenait pas round. C ++ 11 s'appuie sur C99 qui a roundmais aussi comme je l'ai noté inclut truncqui a des propriétés différentes et peut être plus approprié en fonction de l'application. La plupart des réponses semblent également ignorer qu'un utilisateur peut souhaiter retourner un type intégral qui a encore plus de problèmes.
Shafik Yaghmour
2
@uvts_cvs cela ne semble pas être un problème avec la dernière version de visual studio, voyez-le en direct .
Shafik Yaghmour

Réponses:

144

Il n'y a pas de round () dans la bibliothèque standard C ++ 98. Vous pouvez cependant en écrire un vous-même. Ce qui suit est une mise en œuvre de demi-tour :

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

La raison probable qu'il n'y a pas de fonction ronde dans la bibliothèque standard C ++ 98 est qu'elle peut en fait être implémentée de différentes manières. Ce qui précède est un moyen courant, mais il y en a d'autres tels que l' arrondi au pair , qui est moins biaisé et généralement meilleur si vous allez faire beaucoup d'arrondis; c'est un peu plus complexe à implémenter.

Andreas Magnusson
la source
53
Cela ne gère pas correctement les nombres négatifs. La réponse de litb est correcte.
Utilisateur enregistré
39
@InnerJoin: Oui, il gère les nombres négatifs différemment de la réponse de litb, mais cela ne le rend pas "incorrect".
Roddy
39
L'ajout de 0,5 avant de tronquer ne parvient pas à arrondir à l'entier le plus proche pour plusieurs entrées, dont 0,49999999999999994. Voir blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Pascal Cuoq
10
@ Sergi0: Il n'y a pas de "correct" et "incorrect" car il existe plusieurs définitions d'arrondi qui décident de ce qui se passe à mi-parcours. Vérifiez vos faits avant de porter un jugement.
Jon
16
@MuhammadAnnaqeeb: Vous avez raison, les choses se sont énormément améliorées depuis la sortie de C ++ 11. Cette question a été posée et répondue à une autre époque où la vie était difficile et les joies rares. Il reste ici comme une ode aux héros qui ont vécu et combattu à l'époque et à ces pauvres âmes qui ne peuvent toujours pas utiliser les outils modernes.
Andreas Magnusson
96

Boost propose un ensemble simple de fonctions d'arrondi.

#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

Pour plus d'informations, consultez la documentation Boost .

Edition : Depuis C ++ 11, il y a std::round, std::lroundetstd::llround .

Daniel Wolf
la source
2
J'utilisais déjà boost dans mon projet, +1 pour cela, bien mieux que d'utiliser l' floor(value + 0.5)approche naïve !
Gustavo Maciel
@GustavoMaciel Je sais que je suis un peu en retard dans le jeu, mais la mise en œuvre de boost l'est floor(value + 0.5).
n. «pronoms» m.
En fait, ce n'est pas le cas: github.com/boostorg/math/blob/develop/include/boost/math/… 4 ans plus tard, je voudrais également dire que ce floor(value + 0.5)n'est pas du tout naïf, mais plutôt dépend du contexte et de la nature des valeurs que vous souhaitez arrondir!
Gustavo Maciel
84

La norme C ++ 03 s'appuie sur la norme C90 pour ce que la norme appelle la bibliothèque C standard qui est couverte dans le projet de norme C ++ 03 (le projet de norme le plus proche du C ++ 03 est N1804 ). 1.2 Références normatives :

La bibliothèque décrite à l'article 7 de l'ISO / CEI 9899: 1990 et à l'article 7 de l'ISO / CEI 9899 / Amd.1: 1995 est ci-après appelée la bibliothèque C standard. 1)

Si nous allons dans la documentation C pour round, lround, llround sur cppreference, nous pouvons voir que round et les fonctions associées font partie de C99 et ne seront donc pas disponibles en C ++ 03 ou avant.

En C ++ 11, cela change car C ++ 11 s'appuie sur le projet de norme C99 pour la bibliothèque de normes C et fournit donc std :: round et pour les types de retour intégrés 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 ;
}

Une autre option également de C99 serait std :: trunc qui:

Calcule l'entier le plus proche dont la magnitude n'est pas supérieure à 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 ;

}

Si vous devez prendre en charge des applications non C ++ 11, votre meilleur pari serait d'utiliser le boost round, iround, lround, llround ou boost trunc .

Rouler sa propre version du tour est difficile

Faire rouler le vôtre ne vaut probablement pas la peine car plus difficile qu'il n'y paraît: arrondir le flottant à l'entier le plus proche, partie 1 , arrondir le flotteur à l'entier le plus proche, partie 2 et arrondir le flotteur à l'entier le plus proche, partie 3 explique:

Par exemple, un roll commun que votre implémentation utilise std::flooret ajoute 0.5ne fonctionne pas pour toutes les entrées:

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

Une entrée pour laquelle cela échouera est 0.49999999999999994 ( voir en direct ).

Une autre implémentation courante consiste à convertir un type à virgule flottante en un type intégral, qui peut invoquer un comportement indéfini dans le cas où la partie intégrale ne peut pas être représentée dans le type de destination. Nous pouvons le voir dans le projet de section standard C ++ 4.9 Conversions intégrale flottante qui dit (c'est moi qui souligne ):

Une valeur d'un type à virgule flottante peut être convertie en une valeur d'un type entier. La conversion tronque; c'est-à-dire que la partie fractionnaire est jetée.Le comportement n'est pas défini si la valeur tronquée ne peut pas être représentée dans le type de destination. [...]

Par exemple:

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

Étant donné std::numeric_limits<unsigned int>::max()est4294967295 alors l'appel suivant:

myround( 4294967296.5f ) 

provoquera un débordement, ( voir en direct ).

Nous pouvons voir à quel point cela est vraiment difficile en regardant cette réponse à la manière concise d'implémenter round () en C? qui fait référence à newlibs version du flotteur simple précision. C'est une fonction très longue pour quelque chose qui semble simple. Il semble peu probable que quiconque sans connaissance intime des implémentations en virgule flottante puisse correctement implémenter cette fonction:

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'un autre côté, si aucune des autres solutions n'est utilisable, newlib pourrait potentiellement être une option car c'est une implémentation bien testée.

Shafik Yaghmour
la source
5
@downvoter veuillez expliquer ce qui peut être amélioré? La grande majorité de la réponse ici est tout simplement erronée car ils tentent de lancer leur propre tour qui échouent tous sous une forme ou une autre. S'il manque quelque chose dans mon explication, veuillez me le faire savoir.
Shafik Yaghmour du
1
Belle réponse complète - en particulier la partie juste en dessous de 0,5. Une autre niche: round(-0.0). La spécification C ne semble pas spécifier. Je m'attendrais -0.0en conséquence.
chux
3
@chux intéressant, et la norme IEEE 754-2008 spécifie que l'arrondi préserve les signes de zéros et d'infinis (voir 5.9).
Ruslan
1
@Shafik c'est une excellente réponse. Je n'ai jamais pensé que même l'arrondi est une opération non triviale.
Ruslan
1
Il vaut peut-être la peine de mentionner que std::rint()c'est souvent préférable std::round()lorsque C ++ 11 est disponible pour des raisons numériques et de performances. Il utilise le mode d'arrondi actuel, contrairement round()au mode spécial de. Il peut être beaucoup plus efficace sur x86, où rintpeut s'aligner sur une seule instruction. (gcc et clang le font même sans -ffast-math godbolt.org/g/5UsL2e , alors que seul clang insère le presque équivalent nearbyint()) ARM a un support à instruction unique pour round(), mais sur x86 il ne peut être en ligne qu'avec plusieurs instructions, et seulement avec-ffast-math
Peter Cordes
71

Il convient de noter que si vous voulez un résultat entier de l'arrondi, vous n'avez pas besoin de le faire passer par le plafond ou le sol. C'est à dire,

int round_int( double r ) {
    return (r > 0.0) ? (r + 0.5) : (r - 0.5); 
}
kalaxy
la source
3
Ne donne cependant pas le résultat attendu pour 0.4999999999999999994 (enfin, en fonction de ce que vous attendez bien sûr, mais 0 me semble plus raisonnable que 1)
stijn
@stijn Bonne prise. J'ai trouvé que l'ajout du long suffixe double littéral à mes constantes a résolu votre problème d'exemple, mais je ne sais pas s'il existe d'autres exemples de précision qu'il ne pourrait pas intercepter.
kalaxy
1
btw si vous ajoutez 0.4999999999999999994 au lieu de 0.5, cela fonctionne bien pour 0.49999999999999994 et 5000000000000001.0 en entrée. Je ne sais pas si c'est correct pour toutes les valeurs, et je n'ai trouvé aucune référence indiquant que c'est la solution ultime.
stijn
1
@stijn C'est correct pour toutes les valeurs, si vous ne vous souciez pas dans quelle direction les valeurs exactement entre deux entiers sont arrondies. Sans réfléchir, je le prouverais par analyse de cas avec les cas suivants: 0 <= d <0,5, 0,5 <= d <1,5, 1,5 <= d <2 ^ 52, d> = 2 ^ 52. J'ai également testé de manière exhaustive le boîtier simple précision.
Pascal Cuoq
3
Par 4.9 [conv.fpint], "Le comportement n'est pas défini si la valeur tronquée ne peut pas être représentée dans le type de destination." , c'est donc un peu dangereux. D'autres réponses SO décrivent comment procéder de manière robuste.
Tony Delroy
41

Il est disponible depuis C ++ 11 dans cmath (selon 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;
}

Production:

round(0.5):  1
round(-0.5): -1
round(1.4):  1
round(-1.4): -1
round(1.6):  2
round(-1.6): -2
schibum
la source
1
il y a aussi lroundet llroundpour des résultats intégraux
sp2danny
@ sp2danny: ou mieux, lrintpour utiliser le mode d'arrondi actuel au lieu du roundbris d'égalité génial loin de zéro.
Peter Cordes
27

Il est généralement implémenté en tant que floor(value + 0.5).

Edit: et ce n'est probablement pas appelé arrondi car il y a au moins trois algorithmes d'arrondi que je connais: arrondi à zéro, arrondi à l'entier le plus proche et arrondi du banquier. Vous demandez l'arrondi à l'entier le plus proche.

MSN
la source
1
Il est bon de faire la distinction entre les différentes versions de «rond». Il est bon de savoir quand choisir lequel aussi.
xtofl
5
Il existe en effet différents algorithmes d'arrondi qui peuvent tous raisonnablement prétendre être «corrects». Cependant le plancher (valeur + 0,5) n'en fait pas partie. Pour certaines valeurs, telles que 0,49999997f ou le double équivalent, la réponse est tout simplement erronée - elle sera arrondie à 1,0 lorsque tous s'accorderont à dire qu'elle devrait être nulle. Voir cet article pour plus de détails: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
Bruce Dawson
14

Nous examinons 2 problèmes:

  1. arrondir les conversions
  2. conversion de type.

Les conversions d'arrondi signifient l'arrondissement ± flottant / double au plancher / plafond le plus proche flottant / double. Peut-être que votre problème se termine ici. Mais si vous êtes censé retourner Int / Long, vous devez effectuer une conversion de type, et donc le problème de «débordement» peut frapper votre solution. SO, vérifiez une erreur dans votre fonction

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

à partir de: http://www.cs.tut.fi/~jkorpela/round.html

Sangeet
la source
Utiliser LONG_MIN-0.5et LONG_MAX+0.5 introduire des complications car les mathématiques peuvent ne pas être exactes. LONG_MAXpeut dépasser la doubleprécision pour une conversion exacte. De plus, vous souhaiterez probablement assert(x < LONG_MAX+0.5); (<vs <=) car ils LONG_MAX+0.5peuvent être exactement représentables et (x)+0.5peuvent avoir un résultat exact LONG_MAX+1dont la conversion échoue long. D'autres problèmes de coin aussi.
chux
N'appelez pas votre fonction round(double), il existe déjà une fonction de bibliothèque mathématique standard de ce nom (en C ++ 11), donc c'est déroutant. À utiliser std::lrint(x)s'il est disponible.
Peter Cordes
11

Un certain type d'arrondi est également implémenté dans 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;
}

Notez que cela ne fonctionne que si vous effectuez une conversion en nombre entier.

Philipp
la source
2
Boost propose également un ensemble de fonctions d'arrondi simples; voir ma réponse.
Daniel Wolf,
Vous pouvez également utiliser boost:numeric::RoundEven< double >::nearbyintdirectement si vous ne voulez pas de nombre entier. @DanielWolf note que la fonction simple est implémentée en utilisant +0.5 qui a des problèmes comme indiqué par aka.nice
stijn
6

Vous pouvez arrondir à une précision à n chiffres avec:

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;
}
Carl
la source
4
À moins que la taille int de votre compilateur soit de 1024 bits par défaut, cela ne sera pas précis pour un énorme double ...
aka.nice
Je pense que c'est acceptable étant donné quand il sera utilisé: Si votre double valeur est 1.0 e + 19, arrondir à 3 endroits n'a pas de sens.
Carl
3
bien sûr, mais la question est pour un tour générique, et vous ne pouvez pas contrôler comment il sera utilisé. Il n'y a aucune raison pour que le rond échoue là où le plafond et le plancher ne le seraient pas.
aka.nice
Cela a un comportement indéfini pour les arguments en dehors de la plage de int. (En pratique sur x86, les valeurs FP hors plage feront que le CVTTSD2SIproduit sera0x80000000 le motif de bits entier, c'est INT_MIN-à- dire qui sera ensuite reconverti en double.
Peter Cordes
5

De nos jours, l'utilisation d'un compilateur C ++ 11 qui inclut une bibliothèque mathématique C99 / C ++ 11 ne devrait pas poser de problème. Mais alors la question devient: quelle fonction d'arrondi choisissez-vous?

C99 / C ++ 11 round()n'est souvent pas la fonction d'arrondi souhaitée . Il utilise un mode d'arrondi génial qui arrondit à partir de 0 comme bris d'égalité sur les cas à mi-chemin ( +-xxx.5000). Si vous voulez spécifiquement ce mode d'arrondi, ou si vous ciblez une implémentation C ++ où round()est plus rapide que rint(), alors utilisez-le (ou émulez son comportement avec l'une des autres réponses à cette question qui l'a pris au pied de la lettre et reproduit soigneusement ce spécifique comportement d'arrondi.)

round()L'arrondi est différent de l'arrondi par défaut IEEE754 au mode le plus proche, même en cas d'égalité . Le plus proche pair évite un biais statistique dans la magnitude moyenne des nombres, mais fait un biais vers les nombres pairs.

Il existe deux fonctions d'arrondi de bibliothèque mathématique qui utilisent le mode d'arrondi par défaut actuel: std::nearbyint()et std::rint(), toutes deux ajoutées en C99 / C ++ 11, elles sont donc disponibles à tout moment std::round(). La seule différence est que nearbyintne soulève jamais FE_INEXACT.

Préférez rint()pour des raisons de performances : gcc et clang l'inline plus facilement, mais gcc ne s'aligne jamais nearbyint()(même avec -ffast-math)


gcc / clang pour x86-64 et AArch64

J'ai mis quelques fonctions de test sur l'explorateur de compilateur de Matt Godbolt , où vous pouvez voir la sortie source + asm (pour plusieurs compilateurs). Pour en savoir plus sur la lecture de la sortie du compilateur, consultez ce Q&R et la conférence CppCon2017 de Matt: «Qu'est-ce que mon compilateur a fait pour moi récemment? Déboulonnage du couvercle du compilateur » ,

Dans le code FP, c'est généralement une grande victoire pour intégrer de petites fonctions. Surtout sur non-Windows, où la convention d'appel standard n'a pas de registres préservés, donc le compilateur ne peut pas conserver de valeurs FP dans les registres XMM à travers a call. Ainsi, même si vous ne connaissez pas vraiment asm, vous pouvez toujours voir facilement s'il s'agit simplement d'un appel à la fonction de bibliothèque ou s'il est aligné sur une ou deux instructions mathématiques. Tout ce qui s'aligne sur une ou deux instructions est meilleur qu'un appel de fonction (pour cette tâche particulière sur x86 ou ARM).

Sur x86, tout ce qui s'aligne sur SSE4.1 roundsdpeut se vectoriser automatiquement avec SSE4.1 roundpd(ou AVX vroundpd). (Les conversions FP-> integer sont également disponibles sous forme SIMD compactée, sauf pour FP-> 64-bit integer qui nécessite AVX512.)

  • std::nearbyint():

    • x86 clang: s'aligne sur une seule insn avec -msse4.1.
    • x86 gcc: s'aligne sur une seule insn uniquement avec -msse4.1 -ffast-mathet uniquement sur gcc 5.4 et versions antérieures . Plus tard, gcc ne l'inline jamais (peut-être qu'ils ne se sont pas rendu compte que l'un des bits immédiats peut supprimer l'exception inexacte? C'est ce que clang utilise, mais l'ancien gcc utilise le même immédiat que rintlorsqu'il le fait en ligne)
    • AArch64 gcc6.3: s'aligne sur une seule insn par défaut.
  • std::rint:

    • x86 clang: s'aligne sur une seule insn avec -msse4.1
    • x86 gcc7: en ligne avec un seul insn avec -msse4.1. (Sans SSE4.1, s'aligne sur plusieurs instructions)
    • x86 gcc6.x et versions antérieures: s'aligne sur une seule insn avec -ffast-math -msse4.1.
    • AArch64 gcc: s'aligne sur une seule insn par défaut
  • std::round:

    • x86 clang: ne s'aligne pas
    • x86 gcc: s'aligne sur plusieurs instructions avec -ffast-math -msse4.1, nécessitant deux constantes vectorielles.
    • AArch64 gcc: s'aligne sur une seule instruction (prise en charge matérielle pour ce mode d'arrondi ainsi que par défaut IEEE et la plupart des autres.)
  • std::floor/ std::ceil/std::trunc

    • x86 clang: s'aligne sur une seule insn avec -msse4.1
    • x86 gcc7.x: s'aligne sur une seule insn avec -msse4.1
    • x86 gcc6.x et versions antérieures: s'aligne sur une seule insn avec -ffast-math -msse4.1
    • AArch64 gcc: s'aligne par défaut sur une seule instruction

Arrondi à int/ long/ long long:

Vous avez deux options ici: utiliser lrint(comme rintmais renvoie long, ou long longpour llrint), ou utiliser une fonction d'arrondi FP-> FP, puis convertir en type entier de la manière normale (avec troncature). Certains compilateurs optimisent dans un sens mieux que dans l'autre.

long l = lrint(x);

int  i = (int)rint(x);

Notez que int i = lrint(x)convertit floatou double-> d' longabord, puis tronque l'entier en int. Cela fait une différence pour les entiers hors plage: Comportement non défini en C ++, mais bien défini pour les instructions x86 FP -> int (que le compilateur émettra à moins qu'il ne voit l'UB au moment de la compilation tout en faisant une propagation constante, alors c'est autorisé à faire du code qui se casse s'il est déjà exécuté).

Sur x86, une conversion FP-> entier qui déborde de l'entier produit INT_MINou LLONG_MIN(un modèle de bits de 0x8000000ou l'équivalent 64 bits, avec juste le bit de signe défini). Intel appelle cela la valeur "indéfinie entière". (Voir l' cvttsd2sientrée manuelle , l'instruction SSE2 qui convertit (avec troncature) scalaire double en entier signé. Elle est disponible avec une destination entière 32 bits ou 64 bits (en mode 64 bits uniquement). Il y a aussi un cvtsd2si(convertir avec l'arrondi actuel mode), ce que nous aimerions que le compilateur émette, mais malheureusement gcc et clang ne le feront pas sans -ffast-math.

Sachez également que FP vers / depuis unsignedint / long est moins efficace sur x86 (sans AVX512). La conversion en 32 bits non signé sur une machine 64 bits est assez bon marché; il suffit de convertir en 64 bits signé et de tronquer. Mais sinon, c'est beaucoup plus lent.

  • x86 clang avec / sans -ffast-math -msse4.1: en (int/long)rintligne vers roundsd/ cvttsd2si. (optimisation manquée pour cvtsd2si). lrintne s'aligne pas du tout.

  • x86 gcc6.x et versions antérieures sans -ffast-math: aucune façon ne s'aligne

  • x86 gcc7 sans -ffast-math: (int/long)rintarrondit et convertit séparément (avec 2 instructions totales de SSE4.1 est activé, sinon avec un tas de code en ligne pour rintsans roundsd). lrintne s'aligne pas.
  • x86 gcc avec -ffast-math : toutes les manières en ligne à cvtsd2si(optimal) , pas besoin de SSE4.1.

  • AArch64 gcc6.3 sans -ffast-math: (int/long)rints'aligne sur 2 instructions. lrintne s'aligne pas

  • AArch64 gcc6.3 avec -ffast-math: (int/long)rintcompile un appel à lrint. lrintne s'aligne pas. Il peut s'agir d'une optimisation manquée, à moins que les deux instructions dont nous sommes privés -ffast-mathsoient très lentes.
Peter Cordes
la source
TODO: ICC et MSVC sont également disponibles sur Godbolt, mais je n'ai pas regardé leur sortie pour cela. modifications bienvenues ... Aussi: serait-il plus utile de décomposer d'abord par compilateur / version et ensuite par fonction à l'intérieur de cela? La plupart des gens ne changeront pas de compilateur en fonction de la façon dont ils compilent FP-> FP ou FP-> arrondi entier.
Peter Cordes
2
+1 pour recommander rint()où c'est un choix faisable, ce qui est généralement le cas. Je suppose que le nom round()implique pour certains programmeurs que c'est ce qu'ils veulent, alors que cela rint()semble mystérieux. Notez que round()n'utilise pas un mode d'arrondi "génial": l'arrondi au plus proche est un mode d'arrondi officiel IEEE-754 (2008). C'est curieux qui nearbyint()ne soit pas aligné, étant donné qu'il est en grande partie identique à rint(), et devrait être identique sous certaines -ffast-mathconditions. Cela me semble bête.
njuffa
4

Méfiez-vous floor(x+0.5). Voici ce qui peut arriver pour les nombres impairs dans la plage [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

Il s'agit de http://bugs.squeak.org/view.php?id=7134 . Utilisez une solution comme celle de @konik.

Ma propre version robuste serait quelque chose comme:

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

Une autre raison pour éviter le plancher (x + 0,5) est donnée ici .

aka.nice
la source
2
Je suis curieux de connaître les downvotes. Est-ce parce que le lien est résolu loin de zéro plutôt que du plus proche pair?
aka.nice
1
Remarque: la spécification C indique "arrondir les demi-cas à l'écart de zéro, quelle que soit la direction d'arrondi actuelle.", Donc l'arrondi sans tenir compte des impairs / pairs est conforme.
chux
4

Si vous souhaitez finalement convertir la doublesortie de votre round()fonction en un int, les solutions acceptées de cette question ressembleront à:

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

Cela se produit à environ 8,88 ns sur ma machine lorsqu'il est transmis en valeurs uniformément aléatoires.

Pour autant que je sache, les fonctionnalités ci-dessous sont équivalentes, mais elles se cadencent à 2,48 ns sur ma machine, pour un avantage de performance significatif:

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

Parmi les raisons de la meilleure performance est la ramification ignorée.

dshin
la source
Cela a un comportement indéfini pour les arguments en dehors de la plage de int. (En pratique sur x86, les valeurs de FP hors plage feront que le CVTTSD2SIproduit sera0x80000000 le modèle de bits entier, c'est INT_MIN-à- dire qui sera ensuite reconverti en double.
Peter Cordes
2

Il n'est pas nécessaire d'implémenter quoi que ce soit, donc je ne sais pas pourquoi tant de réponses impliquent des définitions, des fonctions ou des méthodes.

En C99

Nous avons les en-têtes et et <tgmath.h> pour les macros de type générique.

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

Si vous ne pouvez pas compiler cela, vous avez probablement omis la bibliothèque mathématique. Une commande similaire à celle-ci fonctionne sur tous les compilateurs C que j'ai (plusieurs).

gcc -lm -std=c99 ...

En C ++ 11

Nous avons les surcharges suivantes et supplémentaires dans #include <cmath> qui reposent sur la virgule flottante double précision IEEE.

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

Il y a aussi des équivalents dans l'espace de noms std .

Si vous ne pouvez pas compiler cela, vous utilisez peut-être la compilation C au lieu de C ++. La commande de base suivante ne produit ni erreurs ni avertissements avec g ++ 6.3.1, x86_64-w64-mingw32-g ++ 6.3.0, clang-x86_64 ++ 3.8.0 et Visual C ++ 2015 Community.

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

Avec division ordinale

Lorsque vous divisez deux nombres ordinaux, où T est court, int, long ou un autre ordinal, l'expression d'arrondi est la suivante.

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

Précision

Il ne fait aucun doute que des imprécisions d'aspect étrange apparaissent dans les opérations en virgule flottante, mais ce n'est que lorsque les chiffres apparaissent, et n'a pas grand-chose à voir avec l'arrondi.

La source n'est pas seulement le nombre de chiffres significatifs dans la mantisse de la représentation IEEE d'un nombre à virgule flottante, elle est liée à notre pensée décimale en tant qu'êtres humains.

Dix est le produit de cinq et deux, et 5 et 2 sont relativement premiers. Par conséquent, les normes à virgule flottante IEEE ne peuvent pas être représentées parfaitement sous forme de nombres décimaux pour toutes les représentations numériques binaires.

Ce n'est pas un problème avec les algorithmes d'arrondi. C'est la réalité mathématique qui doit être prise en compte lors de la sélection des types et de la conception des calculs, de la saisie des données et de l'affichage des nombres. Si une application affiche les chiffres qui montrent ces problèmes de conversion décimal-binaire, alors l'application exprime visuellement une précision qui n'existe pas dans la réalité numérique et doit être modifiée.

FauChristian
la source
1
"Je ne sais pas pourquoi tant de réponses impliquent des définitions, des fonctions ou des méthodes." Jetez un oeil à quand il a été demandé - C ++ 11 n'était pas encore sorti. ;)
jaggedSpire
@jaggedSpire, alors donnez-moi un coup de pouce, si vous le jugez approprié, car toutes les réponses ayant un score élevé sont obsolètes et trompeuses dans le contexte des compilateurs les plus couramment utilisés aujourd'hui.
FauChristian
2

Fonction double round(double)avec l'utilisation de la modffonction:

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

Pour être propre à la compilation, il faut des "math.h" et des "limites". La fonction fonctionne selon un schéma d'arrondi suivant:

  • tour de 5,0 est 5,0
  • le tour de 3,8 est 4,0
  • tour de 2,3 est 2,0
  • la ronde de 1,5 est de 2,0
  • un tour de 0,501 est 1,0
  • 0,5 sur 1,0
  • un tour de 0.499 est 0.0
  • 0,01 est 0,0
  • 0,0 est 0,0
  • le tour de -0.01 est -0.0
  • le tour de -0,499 est -0,0
  • le tour de -0,5 est -0,0
  • le tour de -0.501 est -1.0
  • le tour de -1,5 est -1,0
  • le tour de -2,3 est -2,0
  • le tour de -3,8 est -4,0
  • le tour de -5,0 est -5,0
Konik
la source
2
C'est une bonne solution. Je ne suis pas sûr que l'arrondi de -1,5 à -1,0 soit standard, je m'attendrais à -2,0 par symétrie. De plus, je ne vois pas l'intérêt de la garde principale, les deux premiers pourraient être supprimés.
aka.nice
2
J'ai vérifié dans la norme ISO / IEC 10967-2, open-std.org/jtc1/sc22/wg11/docs/n462.pdf et depuis l'annexe B.5.2.4, la fonction d'arrondi doit en effet être symétrique, rounding_F (x) = neg_F (rounding_F (neg_F (x)))
aka.nice
Cela va être lent par rapport à C ++ 11 rint()ou nearbyint(), mais si vous ne pouvez vraiment pas utiliser un compilateur qui fournit une fonction d'arrondi appropriée et que vous avez besoin de précision plus que de performances ...
Peter Cordes
1

Si vous devez pouvoir compiler du code dans des environnements qui prennent en charge la norme C ++ 11, mais devez également pouvoir compiler ce même code dans des environnements qui ne le prennent pas en charge, vous pouvez utiliser une macro de fonction pour choisir entre std :: round () et une fonction personnalisée pour chaque système. Passez simplement -DCPP11ou /DCPP11au compilateur compatible C ++ 11 (ou utilisez ses macros de version intégrées), et créez un en-tête comme celui-ci:

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

Pour un exemple rapide, voir http://ideone.com/zal709 .

Cela se rapproche de std :: round () dans des environnements qui ne sont pas conformes à C ++ 11, y compris la préservation du bit de signe pour -0.0. Cependant, il peut entraîner une légère baisse des performances et il est probable que des problèmes d'arrondi de certaines valeurs en virgule flottante "problème" connus, tels que 0,4999999999999999994 ou des valeurs similaires, aient des problèmes.

Alternativement, si vous avez accès à un compilateur compatible C ++ 11, vous pouvez simplement saisir std :: round () de son en- <cmath>tête et l'utiliser pour créer votre propre en-tête qui définit la fonction si elle n'est pas déjà définie. Notez que cela peut ne pas être une solution optimale, cependant, surtout si vous devez compiler pour plusieurs plates-formes.

Justin Time - Rétablir Monica
la source
1

Sur la base de la réponse de Kalaxy, la solution suivante est un modèle qui arrondit tout nombre à virgule flottante au type entier le plus proche en fonction de l'arrondi naturel. Il génère également une erreur en mode débogage si la valeur est hors de portée du type entier, servant ainsi à peu près comme une fonction de bibliothèque viable.

    // 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);
    }
quant
la source
1
Comme je l'ai souligné dans ma réponse, l' ajout 0.5ne fonctionne pas dans tous les cas. Bien qu'au moins vous traitez le problème de débordement, vous évitez ainsi un comportement indéfini.
Shafik Yaghmour
1

Comme indiqué dans les commentaires et autres réponses, la bibliothèque standard ISO C ++ n'a pas ajouté round()jusqu'à ISO C ++ 11, lorsque cette fonction a été interceptée par référence à la bibliothèque mathématique standard ISO C99.

Pour les opérandes positifs dans [½, ub ] round(x) == floor (x + 0.5), où ub est 2 23 pour floatlorsqu'il est mappé à IEEE-754 (2008) binary32et 2 52 pour doublequand il est mappé à IEEE-754 (2008) binary64. Les nombres 23 et 52 correspondent au nombre de bits de mantisse stockés dans ces deux formats à virgule flottante. Pour les opérandes positifs dans [+0, ½) round(x) == 0et pour les opérandes positifs dans ( ub , + ∞] round(x) == x. Comme la fonction est symétrique par rapport à l'axe des x, les arguments négatifs xpeuvent être traités selon round(-x) == -round(x).

Cela conduit au code compact ci-dessous. Il se compile en un nombre raisonnable d'instructions machine sur différentes plates-formes. J'ai observé le code le plus compact sur les GPU, où my_roundf()nécessite une dizaine d'instructions. Selon l'architecture du processeur et la chaîne d'outils, cette approche basée sur des virgules flottantes pourrait être plus rapide ou plus lente que l'implémentation basée sur des nombres entiers de newlib référencée dans une réponse différente .

J'ai testé my_roundf()exhaustivement l' roundf()implémentation de newlib en utilisant le compilateur Intel version 13, avec les deux /fp:strictet /fp:fast. J'ai également vérifié que la version de newlib correspond à celle roundf()de la mathimfbibliothèque du compilateur Intel. Des tests exhaustifs ne sont pas possibles pour la double précision round(), mais le code est structurellement identique à l'implémentation en simple précision.

#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;
}
njuffa
la source
J'ai fait un montage pour éviter de supposer intune largeur supérieure à 16 bits. Bien entendu, il suppose toujours qu'il floats'agit d'un binaire32 IEEE754 à 4 octets. Un C ++ 11 static_assertou peut-être une macro #ifdef/ #errorpourrait vérifier cela. (Mais bien sûr, si C ++ 11 est disponible, vous devez utiliser std::round, ou pour le mode d'arrondi actuel utiliser std::rintqui s'harmonise bien avec gcc et clang).
Peter Cordes
BTW, gcc -ffast-math -msse4.1s'aligne std::round()sur un add( AND(x, L1), OR(x,L2), puis sur a roundsd. c'est-à-dire qu'il met rounden œuvre assez efficacement en termes de rint. Mais il n'y a aucune raison de le faire manuellement dans la source C ++, car si vous avez std::rint()ou que std::nearbyint()vous avez également std::round(). Voir ma réponse pour un lien Godbolt et un aperçu de ce qui est en ligne ou non avec différentes versions de gcc / clang.
Peter Cordes
@PeterCordes Je sais très bien comment implémenter round()efficacement en termes de rint()(lorsque ce dernier fonctionne en mode arrondi au plus proche ou même): j'ai implémenté cela pour la bibliothèque mathématique standard CUDA. Cependant, cette question semblait demander comment implémenter round()avec C ++ avant C ++ 11, donc rint()ne serait pas disponible non plus, seulement floor()et ceil().
njuffa
@PeterCordes Désolé, j'ai mal parlé. round()est facilement synthétisé à partir rint()du mode arrondi à zéro , alias trunc(). N'aurait pas dû répondre avant le premier café.
njuffa
1
@PeterCordes Je suis d'accord qu'il est probable qu'OP n'ait pas besoin du comportement d'arrondi spécifique de round(); la plupart des programmeurs ne sont tout simplement pas conscients de la distinction entre round()vs rint()avec arrondi au plus proche pair, où ce dernier est généralement fourni directement par le matériel et donc plus efficace; J'ai énoncé cela dans le Guide de programmation CUDA pour informer les programmeurs: «La méthode recommandée pour arrondir un opérande à virgule flottante simple précision en un entier, avec pour résultat un nombre à virgule flottante simple précision rintf(), n'est pas roundf()».
njuffa
0

J'utilise l'implémentation suivante de round in asm pour l'architecture x86 et C ++ spécifique à MS VS:

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

UPD: pour retourner une valeur double

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

Production:

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
Aleksey F.
la source
La valeur de résultat doit être une valeur à virgule flottante avec une double précision.
consulter la vérité le
@ véritéseeker: Oui, je devais voir le type de valeur de retour requis. OK, voir "UPD".
Aleksey F.
Nous espérons que le compilateur sera en ligne rint()ou nearbyint()vers une roundsdinstruction SSE4.1 ou une frndintinstruction x87 , ce qui sera beaucoup plus rapide que les deux allers-retours de stockage / rechargement nécessaires pour utiliser cet asm en ligne sur les données d'un registre. MSVC inline asm aspire beaucoup pour encapsuler des instructions uniques comme frndintparce qu'il n'y a aucun moyen d'obtenir l'entrée dans un registre. L'utiliser à la fin d'une fonction avec le résultat dans st(0)pourrait être fiable comme moyen de renvoyer la sortie; apparemment, cela est sans danger pour les eaxentiers, même lorsqu'il insère la fonction contenant l'asm.
Peter Cordes
@PeterCordes Les optimisations modernes sont les bienvenues. Cependant, je n'ai pas pu utiliser SSE4.1 car il n'existait pas à ce moment-là. Mon but était de fournir une implémentation minimale de round qui pourrait fonctionner même sur les anciennes familles Intel P3 ou P4 des années 2000.
Aleksey F.19
P3 n'a même pas SSE2, donc le compilateur utilisera déjà x87 pour double, et devrait donc pouvoir s'émettre frndintpour rint(). Si votre compilateur utilise SSE2, faire rebondir un à doublepartir d'un registre XMM vers x87 et inversement peut ne pas en valoir la peine.
Peter Cordes
0

La meilleure façon d'arrondir une valeur flottante par "n" décimales, est la suivante avec en temps O (1): -

Nous devons arrondir la valeur de 3 places, c'est-à-dire n = 3.

float a=47.8732355;
printf("%.3f",a);
Parveen Kumar
la source
-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");

C'est peut-être un moyen de conversion sale inefficace, mais bon sang, ça marche lol. Et c'est bien, car cela s'applique au flotteur réel. N'affecte pas seulement visuellement la sortie.

Brad
la source
Ceci est hilarante inefficace, et il tronque également (en jetant toujours les chiffres de fin) au lieu d'être arrondi au plus proche.
Peter Cordes
-6

J'ai fait ça:

#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;
    }
}
Peter Mortensen
la source
3
Vous ne vouliez pas dire pow (10, place) plutôt que l'opérateur binaire ^ à 10 ^ place? 10 ^ 2 sur ma machine me donne 8 !! Néanmoins, sur mon Mac 10.7.4 et gcc, le code ne fonctionne pas, renvoyant la valeur d'origine.
Pete855217