web-dev-qa-db-fra.com

round () pour float en C ++

J'ai besoin d'une simple fonction d'arrondissement en virgule flottante, ainsi:

double round(double);

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

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

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

226
Roddy

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 implémentation de round-half-up :

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

La raison probable pour laquelle il n’ya pas de fonction round 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 une méthode courante, mais il en existe d'autres telles que arrondi au même , qui est moins biaisé et généralement préférable si vous allez effectuer beaucoup d'arrondissements; C'est un peu plus complexe à mettre en œuvre cependant.

139
Andreas Magnusson

Boost offre 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, voir le documentation Boost .

Edit : Depuis C++ 11, il existe std::round_, _std::lround_ ET _std::llround .

94
Daniel Wolf

La norme C++ 03 s’appuie sur la norme C90 pour ce qu’elle appelle la bibliothèque Standard C qui est traitée dans le projet de norme C++ 03 (Le projet de norme public le plus proche de C++ 03 est N1804) section 1.2 Références normatives :

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

Si nous allons à la documentation C pour round, lround, llround on cppreference nous pouvons voir que round et les fonctions associées font partie de C99 et ne sera donc pas disponible en C++ 03 ou antérieur.

En C++ 11, cela change car C++ 11 s'appuie sur le projet de norme C99 pour la bibliothèque standard C et fournit donc std :: round et pour les types de retour intégral 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 à partir 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 avez besoin de prendre en charge des applications non C++ 11, votre meilleur choix serait d'utiliser boost round, iround, lround, llround ou boost trunc .

Rouler sa propre version du tour est difficile

Rouler soi-même 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 flottant à l'entier le plus proche, partie 2 et Arrondir le flottant à l'entier le plus proche, partie expliquer:

Par exemple, une mise en œuvre courante utilisant std::floor et ajoutant 0.5 ne 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, (le voir en direct ​​).

Une autre implémentation courante consiste à transformer 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 indique ( l'emphase mine ):

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 décimale est rejeté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é que std::numeric_limits<unsigned int>::max() est 4294967295, alors l'appel suivant:

myround( 4294967296.5f ) 

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

Nous pouvons voir à quel point c'est difficile en regardant cette réponse à Méthode concise d'implémenter round () en C? quelle version de référence newlibs de float round à simple précision . C'est une très longue fonction pour quelque chose qui semble simple. Il semble peu probable qu'une personne 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'autre part, si aucune des autres solutions ne sont utilisables , newlib pourrait potentiellement être une option puisqu'il s'agit d'une implémentation bien testée.

79
Shafik Yaghmour

Il peut être intéressant de noter que si vous voulez obtenir un résultat entier de l'arrondi, vous n'avez pas besoin de le transmettre par le plafond ou le plancher. C'est à dire.,

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

Il est disponible depuis C++ 11 en 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;
}

Sortie:

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

Il est généralement implémenté sous la forme floor(value + 0.5).

Edit: et ce n’est probablement pas appelé round car il ya au moins trois algorithmes d’arrondi que je connaisse: round à zéro, round au nombre entier le plus proche et arrondi de banker. Vous demandez de arrondir à l'entier le plus proche.

27
MSN

Nous examinons 2 problèmes:

  1. arrondi des conversions
  2. conversion de type.

Les conversions par arrondi signifient arrondir ± float/double jusqu'au sol/plafond le plus proche float/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 le problème de "débordement" pourrait alors toucher votre solution. SO, faites une vérification d'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

13
Sangeet

Un certain type d'arrondissement 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 entier.

11
Philipp

Vous pouvez arrondir à n chiffres avec la précision suivante:

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

Si vous souhaitez finalement convertir la sortie double de votre fonction round() en int, les solutions acceptées de cette question ressemblent à ceci:

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

Cette horloge arrive à environ 8.88 ns sur ma machine quand elle est passée dans des valeurs uniformément aléatoires.

Les informations ci-dessous sont fonctionnellement équivalentes, pour autant que je sache, mais cadrent à 2,48 ns sur ma machine, pour un avantage de performances significatif:

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

Parmi les raisons de cette meilleure performance, on peut citer les branches ignorées.

5
dshin

À l’heure actuelle, l’utilisation d’un compilateur C++ 11, qui comprend 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 funky qui arrondit à 0 comme point de départ dans les cas à mi-parcours (+-xxx.5000). Si vous souhaitez spécifiquement ce mode d'arrondi ou si vous ciblez une implémentation C++ où round() est plus rapide que rint(), utilisez-le (ou émulez son comportement avec l'une des autres réponses à cette question qui l'a pris tel quel et reproduit avec soin ce comportement d'arrondi spécifique.)

L'arrondi de round() est différent de la valeur par défaut IEEE754 arrondit au mode le plus proche avec même comme tie-break . Le plus proche évite même un biais statistique dans la magnitude moyenne des nombres, mais un biais vers les nombres pairs.

Deux fonctions d'arrondi de la bibliothèque mathématique utilisent le mode d'arrondi par défaut actuel: std::nearbyint() et std::rint() , tous deux ajoutés à C99/C +. +11, donc ils sont disponibles à tout moment. std::round() est. La seule différence est que nearbyint ne soulève jamais FE_INEXACT.

Préférer rint() pour des raisons de performances : gcc et clang les alignent 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 le résultat source + asm ( pour plusieurs compilateurs). Pour en savoir plus sur la lecture de la sortie du compilateur, voir this Q & A , et la conférence CppCon2017 de Matt: "Qu'est-ce que mon compilateur a récemment fait? Déverrouiller le couvercle du compilateur" ,

Dans le code FP, les petites fonctions intégrées sont généralement très rentables. Particulièrement sur les systèmes autres que Windows, où la convention d'appel standard ne comporte aucun registre préservé, donc le compilateur ne peut conserver aucune valeur FP dans les registres XMM sur un call. Ainsi, même si vous ne connaissez pas vraiment asm, vous pouvez toujours facilement savoir s’il s’agit d’un simple appel de la fonction de bibliothèque ou s’il est intégré à une ou deux instructions mathématiques. Tout ce qui est inséré dans une ou deux instructions est préférable à un appel de fonction (pour cette tâche particulière sur x86 ou ARM).

Sur x86, tout ce qui est en ligne avec SSE4.1 roundsd peut être vectorisé automatiquement avec SSE4.1 roundpd (ou AVX vroundpd). (Les conversions FP-> entiers sont également disponibles sous forme SIMD compactée, à l'exception du FP-> entier 64 bits qui nécessite AVX512.)

  • std::nearbyint():

    • x86 clang: insère une seule insn avec -msse4.1.
    • x86 gcc: insère dans un seul insn uniquement avec -msse4.1 -ffast-math, et uniquement sur gcc 5.4 et plus tôt . Plus tard, gcc ne l’aligne jamais (peut-être ne se sont-ils pas rendus compte que l’un des bits immédiats pouvait supprimer l’exception inexacte? C’est ce que clang utilise, mais un ancien gcc utilise le même que pour rint quand il le fait en ligne)
    • AArch64 gcc6.3: insère une seule insn par défaut.
  • std::rint:

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

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

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

Arrondir à int/long/long long:

Vous avez deux options ici: utilisez lrint (comme rint mais renvoie long ou long long pour llrint), ou utilisez une fonction d'arrondi FP-> FP, puis convertissez-la de manière normale (avec troncature). Certains compilateurs optimisent un chemin mieux que l’autre.

long l = lrint(x);

int  i = (int)rint(x);

Notez que int i = lrint(x) convertit d'abord float ou double -> long, puis tronque l'entier en int. Cela fait une différence pour les entiers hors limites: Comportement indéfini en C++, mais bien défini pour les instructions x86 FP -> int (que le compilateur émettra à moins de voir l'UB au moment de la compilation faire une propagation constante, alors il est permis de faire du code qui casse s'il est jamais exécuté).

Sur x86, une conversion d’entier FP-> qui déborde du nombre entier produit INT_MIN ou LLONG_MIN (un motif binaire de 0x8000000 ou l’équivalent 64 bits, avec uniquement le bit de signe défini ) Intel appelle cela la valeur "entier indéfini". (Voir le cvttsd2si entrée manuelle , l'instruction SSE2 qui convertit (avec troncature) le scalaire double en entier signé. Il est disponible avec une destination entière sur 32 ou 64 bits. (en mode 64 bits uniquement). Il existe également un cvtsd2si (conversion avec le mode d'arrondi actuel), ce que nous aimerions que le compilateur émette, mais malheureusement, gcc et clang ne le feront pas sans -ffast-math.

Notez également que FP to/from unsigned int/long est moins efficace sur x86 (sans AVX512). La conversion en 32 bits non signés sur une machine 64 bits est relativement peu coûteuse; il suffit de convertir en 64 bits signé et tronqué. Mais sinon, c'est beaucoup plus lent.

  • x86 clang avec/sans -ffast-math -msse4.1: (int/long)rint inséré dans roundsd/cvttsd2si. (optimisation manquée sur cvtsd2si). lrint n'est pas en ligne du tout.

  • x86 gcc6.x et versions antérieures sans -ffast-math: aucun sens

  • x86 gcc7 sans -ffast-math: (int/long)rint arrondit et convertit séparément (avec 2 instructions totales de SSE4.1 activée, sinon avec un tas de code en ligne pour rint sans roundsd. lrint n'est pas en ligne.
  • x86 gcc avec -ffast-math: en tous sens sur cvtsd2si (optimal) , pas besoin de SSE4.1.

  • AArch64 gcc6.3 sans -ffast-math: (int/long)rint inséré dans 2 instructions. lrint n'est pas en ligne

  • AArch64 gcc6.3 avec -ffast-math: (int/long)rint est compilé en appel à lrint. lrint n'est pas en ligne. Ceci peut être une optimisation manquée à moins que les deux instructions que nous obtenons sans -ffast-math soient très lentes.
4
Peter Cordes

Faites attention à floor(x+0.5). Voici ce qui peut arriver pour les nombres impairs dans l'intervalle [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

C'est 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 d'éviter le plancher (x + 0.5) est donnée ici .

4
aka.nice

Il n’est pas nécessaire de mettre en œuvre quoi que ce soit, je ne suis donc pas sûr de savoir pourquoi tant de réponses impliquent des définitions, des fonctions ou des méthodes.

En C99

Nous avons les en-têtes suivants 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 oublié la bibliothèque de mathématiques. Une commande similaire à celle-ci fonctionne sur tous les compilateurs C que j'ai (plusieurs).

gcc -lm -std=c99 ...

En C++ 11

Les surcharges suivantes et supplémentaires dans #include <cmath> 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 équivalents dans l'espace de noms std aussi.

Si vous ne pouvez pas compiler cela, vous utilisez peut-être une compilation C au lieu de C++. La commande de base suivante ne génère ni erreur ni avertissement 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 l'on divise deux nombres ordinaux, où T est court, int, long ou un autre ordinal, l'expression arrondie est la suivante.

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

Précision

Il ne fait aucun doute que des inexactitudes étranges apparaissent dans les opérations en virgule flottante, mais cela ne se produit que lorsque les chiffres apparaissent et a peu à voir avec l'arrondissement.

La source n'est pas simplement le nombre de chiffres significatifs dans la mantisse de la représentation IEEE d'un nombre à virgule flottante, elle est liée à notre façon de penser décimale en tant qu'être humain.

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 parfaitement représentées 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 qu'il convient de prendre 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 indiquant ces problèmes de conversion décimale-binaire, elle exprime de manière visuelle une précision qui n'existe pas dans la réalité numérique et qui doit être modifiée.

2
FauChristian

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

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 compilé proprement, il faut "math.h" et "limits". La fonction fonctionne selon un schéma d'arrondi suivant:

  • tour de 5.0 est 5.0
  • tour de 3.8 est 4.0
  • tour de 2.3 est 2.0
  • tour de 1,5 est 2,0
  • tour de 0,501 est 1,0
  • tour de 0.5 est 1.0
  • tour de 0.499 est 0.0
  • tour de 0.01 est 0.0
  • tour de 0.0 est 0.0
  • tour de -0,01 est -0,0
  • tour de -0,499 est -0,0
  • tour de -0.5 est -0.0
  • tour de -0.501 est -1.0
  • tour de -1.5 est -1.0
  • tour de -2.3 est -2.0
  • tour de -3,8 est -4,0
  • tour de -5.0 est -5.0
2
konik

D'après la réponse de Kalaxy, voici une solution basée sur un modèle qui arrondit tout nombre à virgule flottante au type entier le plus proche, en fonction de l'arrondissement 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 approximativement de 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);
    }
1
quant

Si vous devez pouvoir compiler du code dans des environnements prenant en charge la norme C++ 11, mais également 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 -DCPP11 ou /DCPP11 au compilateur conforme à 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 .

Ceci se rapproche de std :: round () dans les environnements non compatibles avec C++ 11, y compris la préservation du bit de signe pour -0.0. Toutefois, cela peut entraîner de légères pertes de performances et des problèmes d’arrondissement de certaines valeurs à virgule flottante "problème" connues, telles que 0,49999999999999994 ou d’autres valeurs similaires, risquent de poser problème.

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

1
Justin Time

Comme indiqué dans les commentaires et autres réponses, la bibliothèque standard ISO C++ n'ajoutait pas round() jusqu'à ISO C++ 11, lorsque cette fonction a été extraite 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 223 pour float lorsque mappé vers IEEE-754 (2008) binary32, et 252 pour double lorsqu'il est mappé sur 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) == 0, et pour les opérandes positifs dans ( ub , + ∞] round(x) == x. Comme la fonction est symétrique par rapport au En abscisse, les arguments négatifs x peuvent être traités selon round(-x) == -round(x).

Cela conduit au code compact ci-dessous. Il compile 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 environ une douzaine d'instructions. En fonction de l'architecture du processeur et de la chaîne d'outils, cette approche à virgule flottante peut être plus rapide ou plus lente que l'implémentation à base d'entiers de newlib référencée dans un réponse différente .

J'ai testé my_roundf() de manière exhaustive par rapport à l'implémentation newlib roundf() à l'aide du compilateur Intel version 13, avec /fp:strict et /fp:fast. J'ai également vérifié que la version de newlib correspond à la roundf() dans la bibliothèque mathimf du compilateur Intel. Un test exhaustif n'est pas possible pour round() en double précision, 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;
}
1
njuffa

Le meilleur moyen d’arrondir une valeur flottante par "n" décimales, est le suivant avec in O(1) time: -

Nous devons arrondir la valeur par 3 positions, à savoir n = 3.So,

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

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

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

UPD: pour retourner la double valeur

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

Sortie:

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
0
Aleksey F.