web-dev-qa-db-fra.com

C++: arrondi au multiple de nombre le plus proche

OK - je suis presque gêné de poster ceci ici (et je le supprimerai si quelqu'un vote pour fermer) car cela semble être une question fondamentale.

Est-ce la bonne façon d'arrondir un multiple d'un nombre en C++? 

Je sais qu'il existe d'autres questions liées à cela, mais je suis particulièrement intéressé de savoir quelle est la meilleure façon de procéder en C++:

int roundUp(int numToRound, int multiple)
{
 if(multiple == 0)
 {
  return numToRound;
 }

 int roundDown = ( (int) (numToRound) / multiple) * multiple;
 int roundUp = roundDown + multiple; 
 int roundCalc = roundUp;
 return (roundCalc);
}

Mise à jour: Désolé, je n'ai probablement pas précisé l'intention. Voici quelques exemples:

roundUp(7, 100)
//return 100

roundUp(117, 100)
//return 200

roundUp(477, 100)
//return 500

roundUp(1077, 100)
//return 1100

roundUp(52, 20)
//return 60

roundUp(74, 30)
//return 90

EDIT: Merci pour toutes les réponses. Voici ce que je suis allé pour:

int roundUp(int numToRound, int multiple)  
{  
 if(multiple == 0)  
 {  
  return numToRound;  
 }  

 int remainder = numToRound % multiple; 
 if (remainder == 0)
  {
    return numToRound; 
  }

 return numToRound + multiple - remainder; 
}  
142

Cela fonctionne pour les nombres positifs, pas sûr de négatif. Il utilise uniquement des mathématiques entières.

int roundUp(int numToRound, int multiple)
{
    if (multiple == 0)
        return numToRound;

    int remainder = numToRound % multiple;
    if (remainder == 0)
        return numToRound;

    return numToRound + multiple - remainder;
}

Edit: Voici une version qui fonctionne avec des nombres négatifs, si par "up" vous entendez un résultat toujours> = l'entrée.

int roundUp(int numToRound, int multiple)
{
    if (multiple == 0)
        return numToRound;

    int remainder = abs(numToRound) % multiple;
    if (remainder == 0)
        return numToRound;

    if (numToRound < 0)
        return -(abs(numToRound) - remainder);
    else
        return numToRound + multiple - remainder;
}
140
Mark Ransom

Sans conditions:

int roundUp(int numToRound, int multiple) 
{
    assert(multiple);
    return ((numToRound + multiple - 1) / multiple) * multiple;
}

Cela fonctionne comme arrondir à zéro pour les nombres négatifs

EDIT: Version qui fonctionne aussi pour les nombres négatifs

int roundUp(int numToRound, int multiple) 
{
    assert(multiple);
    int isPositive = (int)(numToRound >= 0);
    return ((numToRound + isPositive * (multiple - 1)) / multiple) * multiple;
}

Tests


Si multiple est une puissance de 2

int roundUp(int numToRound, int multiple) 
{
    assert(multiple && ((multiple & (multiple - 1)) == 0));
    return (numToRound + multiple - 1) & -multiple;
}

Tests

92
KindDragon

Cela fonctionne lorsque le facteur sera toujours positif:

int round_up(int num, int factor)
{
    return num + factor - 1 - (num - 1) % factor;
}

Edit: Ceci retourne round_up(0,100)=100. Veuillez voir le commentaire de Paul ci-dessous pour une solution qui retourne round_up(0,100)=0.

33
xlq

C’est une généralisation du problème "comment savoir combien d’octets n bits prendront? (A: (n bits + 7)/8).

int RoundUp(int n, int roundTo)
{
    // fails on negative?  What does that mean?
    if (roundTo == 0) return 0;
    return ((n + roundTo - 1) / roundTo) * roundTo; // edit - fixed error
}
22
plinth
int roundUp(int numToRound, int multiple)
{
 if(multiple == 0)
 {
  return 0;
 }
 return ((numToRound - 1) / multiple + 1) * multiple;  
}

Et pas besoin de jouer avec les conditions

14
doron
float roundUp(float number, float fixedBase) {
    if (fixedBase != 0 && number != 0) {
        float sign = number > 0 ? 1 : -1;
        number *= sign;
        number /= fixedBase;
        int fixedPoint = (int) ceil(number);
        number = fixedPoint * fixedBase;
        number *= sign;
    }
    return number;
}

Cela fonctionne pour n’importe quel numéro ou base de flottant (par exemple, vous pouvez arrondir -4 à 6,75 près). Essentiellement, il convertit en point fixe, y arrondit, puis reconvertit. Il gère les négatifs en arrondissant AWAY à partir de 0. Il traite également un arrondi négatif à valeur en tournant essentiellement la fonction en roundDown.

Une version spécifique à int ressemble à ceci:

int roundUp(int number, int fixedBase) {
    if (fixedBase != 0 && number != 0) {
        int sign = number > 0 ? 1 : -1;
        int baseSign = fixedBase > 0 ? 1 : 0;
        number *= sign;
        int fixedPoint = (number + baseSign * (fixedBase - 1)) / fixedBase;
        number = fixedPoint * fixedBase;
        number *= sign;
    }
    return number;
}

Ce qui est plus ou moins la réponse du socle, avec le support d’entrée négatif ajouté.

9
Dolphin

Pour ceux qui recherchent une réponse courte et douce. C'est ce que j'ai utilisé. Pas de comptabilité pour les négatifs.

n - (n % r)

Cela retournera le facteur précédent.

(n + r) - (n % r)

Reviendra le prochain. J'espère que ça aide quelqu'un. :)

8
aaron-bond

Il s’agit de l’approche c ++ moderne utilisant une fonction de modèle qui fonctionne pour float, double, long, int et short (mais pas pour longtemps, et long double en raison des valeurs doubles utilisées).

#include <cmath>
#include <iostream>

template<typename T>
T roundMultiple( T value, T multiple )
{
    if (multiple == 0) return value;
    return static_cast<T>(std::round(static_cast<double>(value)/static_cast<double>(multiple))*static_cast<double>(multiple));
}

int main()
{
    std::cout << roundMultiple(39298.0, 100.0) << std::endl;
    std::cout << roundMultiple(20930.0f, 1000.0f) << std::endl;
    std::cout << roundMultiple(287399, 10) << std::endl;
}

Mais vous pouvez facilement ajouter un support pour long long et long double avec une spécialisation de modèle, comme indiqué ci-dessous:

template<>
long double roundMultiple<long double>( long double value, long double multiple)
{
    if (multiple == 0.0l) return value;
    return std::round(value/multiple)*multiple;
}

template<>
long long roundMultiple<long long>( long long value, long long multiple)
{
    if (multiple == 0.0l) return value;
    return static_cast<long long>(std::round(static_cast<long double>(value)/static_cast<long double>(multiple))*static_cast<long double>(multiple));
}

Pour créer des fonctions à arrondir, utilisez std::ceil et pour toujours arrondir, utilisez std::floor. Mon exemple ci-dessus est arrondi avec std::round.

Créez la fonction de modèle "arrondir" ou mieux connue sous le nom de "plafond arrondi" comme indiqué ci-dessous:

template<typename T>
T roundCeilMultiple( T value, T multiple )
{
    if (multiple == 0) return value;
    return static_cast<T>(std::ceil(static_cast<double>(value)/static_cast<double>(multiple))*static_cast<double>(multiple));
}

Créez la fonction de modèle "arrondir vers le bas" ou mieux connue sous le nom de "plancher arrondi" comme indiqué ci-dessous:

template<typename T>
T roundFloorMultiple( T value, T multiple )
{
    if (multiple == 0) return value;
    return static_cast<T>(std::floor(static_cast<double>(value)/static_cast<double>(multiple))*static_cast<double>(multiple));
}
7
Flovdis

Tout d’abord, votre condition d’erreur (multiple == 0) devrait probablement avoir une valeur de retour. Quoi? Je ne sais pas. Peut-être que vous voulez lancer une exception, à vous de choisir. Mais ne rien retourner est dangereux.

Deuxièmement, vous devriez vérifier que numToRound n'est pas déjà un multiple. Sinon, lorsque vous ajoutez multiple à roundDown, vous obtiendrez la mauvaise réponse.

Troisièmement, vos moulages sont incorrects. Vous transformez numToRound en un entier, mais c'est déjà un entier. Vous devez utiliser cast pour doubler avant la division, puis pour int après la multiplication.

Enfin, que voulez-vous pour les nombres négatifs? Arrondir "en haut" peut signifier arrondir à zéro (arrondir dans la même direction que les nombres positifs) ou s'éloigner de zéro (nombre négatif "plus grand"). Ou peut-être que vous ne vous en souciez pas.

Voici une version avec les trois premiers correctifs, mais je ne traite pas le problème négatif:

int roundUp(int numToRound, int multiple)
{
 if(multiple == 0)
 {
  return 0;
 }
 else if(numToRound % multiple == 0)
 {
  return numToRound
 }

 int roundDown = (int) (( (double) numToRound / multiple ) * multiple);
 int roundUp = roundDown + multiple; 
 int roundCalc = roundUp;
 return (roundCalc);
}
5
Mike Caron

Arrondissez au pouvoir de deux:

Juste au cas où quelqu'un aurait besoin d'une solution pour des nombres positifs arrondis au multiple le plus proche d'une puissance de deux (parce que c'est comme ça que j'ai fini ici):

// number: the number to be rounded (ex: 5, 123, 98345, etc.)
// pow2:   the power to be rounded to (ex: to round to 16, use '4')
int roundPow2 (int number, int pow2) {
    pow2--;                     // because (2 exp x) == (1 << (x -1))
    pow2 = 0x01 << pow2;

    pow2--;                     // because for any
                                //
                                // (x = 2 exp x)
                                //
                                // subtracting one will
                                // yield a field of ones
                                // which we can use in a
                                // bitwise OR

    number--;                   // yield a similar field for
                                // bitwise OR
    number = number | pow2;
    number++;                   // restore value by adding one back

    return number;
}

Le numéro saisi restera le même s'il s'agit déjà d'un multiple.

Voici la sortie x86_64 que GCC génère avec -O2 ou -Os (9Sep2013 Build - godbolt GCC en ligne):

roundPow2(int, int):
    lea ecx, [rsi-1]
    mov eax, 1
    sub edi, 1
    sal eax, cl
    sub eax, 1
    or  eax, edi
    add eax, 1
    ret

Chaque ligne de code C correspond parfaitement à sa ligne dans l’Assemblée: http://goo.gl/DZigfX

Chacune de ces instructions est extrêmement rapide, la fonction est donc extrêmement rapide. Étant donné que le code est si petit et rapide, il peut être utile de inline la fonction lors de son utilisation.


Crédit:

4
haneefmubarak

J'utilise:

template <class _Ty>
inline _Ty n_Align_Up(_Ty n_x, _Ty n_alignment)
{
    assert(n_alignment > 0);
    //n_x += (n_x >= 0)? n_alignment - 1 : 1 - n_alignment; // causes to round away from zero (greatest absolute value)
    n_x += (n_x >= 0)? n_alignment - 1 : -1; // causes to round up (towards positive infinity)
    //n_x += (_Ty(-(n_x >= 0)) & n_alignment) - 1; // the same as above, avoids branch and integer multiplication
    //n_x += n_alignment - 1; // only works for positive numbers (fastest)
    return n_x - n_x % n_alignment; // rounds negative towards zero
}

et pour les pouvoirs de deux:

template <class _Ty>
bool b_Is_POT(_Ty n_x)
{
    return !(n_x & (n_x - 1));
}

template <class _Ty>
inline _Ty n_Align_Up_POT(_Ty n_x, _Ty n_pot_alignment)
{
    assert(n_pot_alignment > 0);
    assert(b_Is_POT(n_pot_alignment)); // alignment must be power of two
    -- n_pot_alignment;
    return (n_x + n_pot_alignment) & ~n_pot_alignment; // rounds towards positive infinity (i.e. negative towards zero)
}

Notez que ces deux valeurs arrondissent les valeurs négatives vers zéro (c'est-à-dire arrondies à l'infini positif pour toutes les valeurs), aucune d'elles ne repose sur le dépassement de capacité signé (qui n'est pas défini en C/C++).

Cela donne:

n_Align_Up(10, 100) = 100
n_Align_Up(110, 100) = 200
n_Align_Up(0, 100) = 0
n_Align_Up(-10, 100) = 0
n_Align_Up(-110, 100) = -100
n_Align_Up(-210, 100) = -200
n_Align_Up_POT(10, 128) = 128
n_Align_Up_POT(130, 128) = 256
n_Align_Up_POT(0, 128) = 0
n_Align_Up_POT(-10, 128) = 0
n_Align_Up_POT(-130, 128) = -128
n_Align_Up_POT(-260, 128) = -256
3
the swine

Pour toujours arrondir

int alwaysRoundUp(int n, int multiple)
{
    if (n % multiple != 0) {
        n = ((n + multiple) / multiple) * multiple;

        // Another way
        //n = n - n % multiple + multiple;
    }

    return n;
}

alwaysRoundUp (1, 10) -> 10

alwaysRoundUp (5, 10) -> 10

alwaysRoundUp (10, 10) -> 10


Pour arrondir toujours

int alwaysRoundDown(int n, int multiple)
{
    n = (n / multiple) * multiple;

    return n;
}

alwaysRoundDown (1, 10) -> 0

alwaysRoundDown (5, 10) -> 0

alwaysRoundDown (10, 10) -> 10


Pour arrondir la voie normale

int normalRound(int n, int multiple)
{
    n = ((n + multiple/2)/multiple) * multiple;

    return n;
}

normalRound (1, 10) -> 0

normalRound (5, 10) -> 10

normalRound (10, 10) -> 10

2
onmyway133

peut être cela peut aider:

int RoundUpToNearestMultOfNumber(int val, int num)
{
  assert(0 != num);
  return (floor((val + num) / num) * num);
}
2
Arsen

Il est probablement plus sûr de lancer les flottants et d'utiliser ceil () - à moins que vous ne sachiez que la division int va produire le résultat correct.

2
Martin Beckett
int noOfMultiples = int((numToRound / multiple)+0.5);
return noOfMultiples*multiple

C++ arrondit chaque nombre vers le bas, donc si vous ajoutez 0,5 (si son 1,5, ce sera 2) mais 1,49 sera 1,99 donc 1.

EDIT - Désolé de ne pas voir que vous vouliez arrondir, je suggérerais d’utiliser une méthode ceil () au lieu de +0.5

2
Michal Ciechan

bien pour une chose, puisque je ne comprends pas vraiment ce que vous voulez faire, les lignes 

int roundUp = roundDown + multiple;
int roundCalc = roundUp;
return (roundCalc); 

pourrait certainement être raccourci à 

int roundUp = roundDown + multiple;
return roundUp;
2
Jesse Naugher

Voici ce que je ferais:

#include <cmath>

int roundUp(int numToRound, int multiple)
{
    // if our number is zero, return immediately
   if (numToRound == 0)
        return multiple;

    // if multiplier is zero, return immediately
    if (multiple == 0)
        return numToRound;

    // how many times are number greater than multiple
    float rounds = static_cast<float>(numToRound) / static_cast<float>(multiple);

    // determine, whether if number is multiplier of multiple
    int floorRounds = static_cast<int>(floor(rounds));

    if (rounds - floorRounds > 0)
        // multiple is not multiplier of number -> advance to the next multiplier
        return (floorRounds+1) * multiple;
    else
        // multiple is multiplier of number -> return actual multiplier
        return (floorRounds) * multiple;
}

Le code n'est peut-être pas optimal, mais je préfère un code propre à une performance sèche.

1
Gotcha
int roundUp (int numToRound, int multiple)
{
  return multiple * ((numToRound + multiple - 1) / multiple);
}

bien que:

  • ne fonctionnera pas pour les nombres négatifs
  • ne fonctionnera pas si numRound + plusieurs débordements

suggérerait plutôt d'utiliser des entiers non signés, qui ont un comportement de débordement défini.

Vous aurez une exception est multiple == 0, mais ce n'est pas un problème bien défini dans ce cas.

1
user3392484

J'utilise une combinaison de module pour annuler l'ajout du reste si x est déjà un multiple:

int round_up(int x, int div)
{
    return x + (div - x % div) % div;
}

Nous trouvons l'inverse du reste puis modulons cela avec le diviseur à nouveau pour l'annuler si c'est le diviseur lui-même alors ajoutez x.

round_up(19, 3) = 21
1
Nick Bedford

J'ai trouvé un algorithme qui ressemble un peu à celui posté ci-dessus:

int [(| x | + n-1)/n] * [(nx)/| x |], où x est une valeur entrée par l'utilisateur et n est le multiple utilisé.

Cela fonctionne pour toutes les valeurs x, où x est un entier (positif ou négatif, y compris zéro). Je l'ai écrit spécialement pour un programme C++, mais cela peut être implémenté dans n'importe quel langage.

1
Joshua Wade

Voici ma solution basée sur la suggestion du PO et les exemples donnés par tous les autres. Étant donné que la plupart des utilisateurs cherchaient à gérer les nombres négatifs, cette solution ne sert à rien, sans utilisation de fonctions spéciales, telles que abs, etc.

En évitant le module et en utilisant la division, le nombre négatif est un résultat naturel, bien qu’il soit arrondi. Une fois que la version arrondie est calculée, elle effectue les calculs nécessaires pour arrondir, dans le sens négatif ou positif.

Notez également qu'aucune fonction spéciale n'est utilisée pour calculer quoi que ce soit, il y a donc une petite augmentation de vitesse.

int RoundUp(int n, int multiple)
{
    // prevent divide by 0 by returning n
    if (multiple == 0) return n;

    // calculate the rounded down version
    int roundedDown = n / multiple * multiple;

    // if the rounded version and original are the same, then return the original
    if (roundedDown == n) return n;

    // handle negative number and round up according to the sign
    // NOTE: if n is < 0 then subtract the multiple, otherwise add it
    return (n < 0) ? roundedDown - multiple : roundedDown + multiple;
}
1
weatx

c:

int roundUp(int numToRound, int multiple)
{
  return (multiple ? (((numToRound+multiple-1) / multiple) * multiple) : numToRound);
}

et pour votre ~/.bashrc:

roundup()
{
  echo $(( ${2} ? ((${1}+${2}-1)/${2})*${2} : ${1} ))
}
1
nhed

Pour numToRound négatif:

Cela devrait être très facile à faire, mais l'opérateur modulo% standard ne gère pas les nombres négatifs comme on pourrait s'y attendre. Par exemple -14% 12 = -2 et non 10. La première chose à faire est d'obtenir un opérateur modulo qui ne renvoie jamais de nombres négatifs. Alors roundUp est vraiment simple.

public static int mod(int x, int n) 
{
    return ((x % n) + n) % n;
}

public static int roundUp(int numToRound, int multiple) 
{
    return numRound + mod(-numToRound, multiple);
}
1
user990343

Arrondis au multiple le plus proche qui correspond à une puissance de 2

unsigned int round(unsigned int value, unsigned int multiple){
    return ((value-1u) & ~(multiple-1u)) + multiple;
}

Cela peut être utile pour l'allocation le long de lignes de cache, où l'incrément d'arrondi souhaité est une puissance de deux, mais la valeur résultante ne doit en être qu'un multiple. Sur gcc, le corps de cette fonction génère 8 instructions d'assemblage sans division ni branche.

round(  0,  16) ->   0
round(  1,  16) ->  16
round( 16,  16) ->  16
round(257, 128) -> 384 (128 * 3)
round(333,   2) -> 334
1
Anne Quinn

Vous obtenez les résultats que vous recherchez pour les entiers positifs:

#include <iostream>
using namespace std;

int roundUp(int numToRound, int multiple);

int main() {
    cout << "answer is: " << roundUp(7, 100) << endl;
    cout << "answer is: " << roundUp(117, 100) << endl;
    cout << "answer is: " << roundUp(477, 100) << endl;
    cout << "answer is: " << roundUp(1077, 100) << endl;
    cout << "answer is: " << roundUp(52,20) << endl;
    cout << "answer is: " << roundUp(74,30) << endl;
    return 0;
}

int roundUp(int numToRound, int multiple) {
    if (multiple == 0) {
        return 0;
    }
    int result = (int) (numToRound / multiple) * multiple;
    if (numToRound % multiple) {
        result += multiple;
    } 
    return result;
}

Et voici les sorties:

answer is: 100
answer is: 200
answer is: 500
answer is: 1100
answer is: 60
answer is: 90
0
Dave

Je pense que cela devrait vous aider. J'ai écrit le programme ci-dessous en C.

# include <stdio.h>
int main()
{
  int i, j;
  printf("\nEnter Two Integers i and j...");
  scanf("%d %d", &i, &j);
  int Round_Off=i+j-i%j;
  printf("The Rounded Off Integer Is...%d\n", Round_Off);
  return 0;
}
0
Neel
/// Rounding up 'n' to the nearest multiple of number 'b'.
/// - Not tested for negative numbers.
/// \see http://stackoverflow.com/questions/3407012/
#define roundUp(n,b) ( (b)==0 ? (n) : ( ((n)+(b)-1) - (((n)-1)%(b)) ) )

/// \c test->roundUp().
void test_roundUp() {   
    // yes_roundUp(n,b) ( (b)==0 ? (n) : ( (n)%(b)==0 ? n : (n)+(b)-(n)%(b) ) )
    // yes_roundUp(n,b) ( (b)==0 ? (n) : ( ((n + b - 1) / b) * b ) )

    // no_roundUp(n,b) ( (n)%(b)==0 ? n : (b)*( (n)/(b) )+(b) )
    // no_roundUp(n,b) ( (n)+(b) - (n)%(b) )

if (true) // couldn't make it work without (?:)
{{  // test::roundUp()
    unsigned m;
                { m = roundUp(17,8); } ++m;
    assertTrue( 24 == roundUp(17,8) );
                { m = roundUp(24,8); }
    assertTrue( 24 == roundUp(24,8) );

    assertTrue( 24 == roundUp(24,4) );
    assertTrue( 24 == roundUp(23,4) );
                { m = roundUp(23,4); }
    assertTrue( 24 == roundUp(21,4) );

    assertTrue( 20 == roundUp(20,4) );
    assertTrue( 20 == roundUp(19,4) );
    assertTrue( 20 == roundUp(18,4) );
    assertTrue( 20 == roundUp(17,4) );

    assertTrue( 17 == roundUp(17,0) );
    assertTrue( 20 == roundUp(20,0) );
}}
}
0
Adolfo