web-dev-qa-db-fra.com

Arrondir à la division entière

J'étais curieux de savoir comment arrondir un nombre au plus proche. dixième nombre entier. Par exemple, si j'avais:

int a = 59 / 4;

qui serait 14,75 calculé en virgule flottante; Comment puis-je stocker le numéro 15 dans "a"?

60
Dave
int a = 59.0f / 4.0f + 0.5f;

Cela ne fonctionne que lors de l'assignation à un int car il rejette quoi que ce soit après le '.

Edit: Cette solution ne fonctionnera que dans les cas les plus simples. Une solution plus robuste serait:

unsigned int round_closest(unsigned int dividend, unsigned int divisor)
{
    return (dividend + (divisor / 2)) / divisor;
}
38
0xC0DEFACE

L'idiome standard pour arrondir les nombres entiers est le suivant:

int a = (59 + (4 - 1)) / 4;

Vous ajoutez le diviseur moins un au dividende.

112
Jonathan Leffler

Un code qui fonctionne pour tout signe en dividende et diviseur:

int divRoundClosest(const int n, const int d)
{
  return ((n < 0) ^ (d < 0)) ? ((n - d/2)/d) : ((n + d/2)/d);
}

Si vous préférez une macro:

#define DIV_ROUND_CLOSEST(n, d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))

La macro du noyau Linux DIV_ROUND_CLOSEST ne fonctionne pas pour les diviseurs négatifs!

41
ericbn

Vous devriez plutôt utiliser quelque chose comme ceci:

int a = (59 - 1)/ 4 + 1;

Je suppose que vous essayez vraiment de faire quelque chose de plus général:

int divide(x, y)
{
   int a = (x -1)/y +1;

   return a;
}

x + (y-1) est susceptible de déborder, donnant un résultat incorrect; alors que, x - 1 ne sera submergé que si x = min_int ...

23
WayneJ

(Edité) Arrondir les entiers avec virgule flottante est la solution la plus simple à ce problème; Cependant, en fonction du problème posé, il est peut-être possible. Par exemple, dans les systèmes intégrés, la solution en virgule flottante peut s'avérer trop coûteuse.

Faire cela en utilisant des mathématiques entières s'avère être un peu difficile et un peu peu intuitif. La première solution publiée marchait bien pour le problème pour lequel je l'avais utilisé, mais après avoir caractérisé les résultats sur la plage d'entiers, elle s'est révélée très mauvaise en général. En parcourant plusieurs livres sur le peu de twiddling et les mathématiques intégrées, on obtient peu de résultats. Quelques notes. Premièrement, je n’ai testé que les entiers positifs, mon travail n’impliquant ni numérateurs ni dénominateurs négatifs. Deuxièmement, un test exhaustif des entiers 32 bits est un calcul prohibitif, alors j’ai commencé avec des entiers 8 bits, puis j’ai assuré que j’obtenais des résultats similaires avec des entiers 16 bits.

J'ai commencé avec les 2 solutions que j'avais précédemment proposées:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

#define DIVIDE_WITH_ROUND(N, D) (N == 0) ? 0:(N - D/2)/D + 1;

Je pensais que la première version déborderait avec de grands nombres et la seconde insuffisante avec de petits nombres. Je n'ai pas pris 2 choses en considération. 1.) Le deuxième problème est en réalité récursif, car pour obtenir la bonne réponse, vous devez arrondir correctement D/2. 2.) Dans le premier cas, vous débordez souvent puis descendez, les deux s'annulant l'un l'autre. Voici un graphique d'erreur des deux algorithmes (incorrects):Divide with Round1 8 bit x=numerator y=denominator

Ce graphique montre que le premier algorithme n'est incorrect que pour les petits dénominateurs (0 <d <10). De façon inattendue, il gère réellement les grands numérateurs par rapport à la version 2.

Voici un graphique du 2ème algorithme: 8 bit signed numbers 2nd algorithm.

Comme prévu, il échoue pour les petits numérateurs, mais également pour davantage de grands numérateurs que la 1ère version.

Clairement, c’est le meilleur point de départ pour une version correcte:

#define DIVIDE_WITH_ROUND(N, D) (((N) == 0) ? 0:(((N * 10)/D) + 5)/10)

Si vos dénominateurs sont> 10, cela fonctionnera correctement.

Un cas spécial est nécessaire pour D == 1, il suffit de retourner N. Un cas spécial est nécessaire pour D == 2, = N/2 + (N & 1) // Arrondissez si impair.

D> = 3 a aussi des problèmes une fois que N devient assez grand. Il s'avère que les grands dénominateurs ne rencontrent des problèmes qu'avec les plus grands numérateurs. Pour le numéro signé 8 bits, les points problématiques sont

if (D == 3) && (N > 75))
else if ((D == 4) && (N > 100))
else if ((D == 5) && (N > 125))
else if ((D == 6) && (N > 150))
else if ((D == 7) && (N > 175))
else if ((D == 8) && (N > 200))
else if ((D == 9) && (N > 225))
else if ((D == 10) && (N > 250))

(retour D/N pour ceux-ci)

Donc, en général, la pointe où un numérateur particulier devient mauvais se situe quelque part
N > (MAX_INT - 5) * D/10

Ce n'est pas exact mais proche. Lorsque vous travaillez avec des nombres de 16 bits ou plus, l'erreur <1% si vous ne faites qu'une division en C (troncature) pour ces cas.

Pour les nombres signés 16 bits, les tests seraient

if ((D == 3) && (N >= 9829))
else if ((D == 4) && (N >= 13106))
else if ((D == 5) && (N >= 16382))
else if ((D == 6) && (N >= 19658))
else if ((D == 7) && (N >= 22935))
else if ((D == 8) && (N >= 26211))
else if ((D == 9) && (N >= 29487))
else if ((D == 10) && (N >= 32763))

Bien sûr, pour les entiers non signés, MAX_INT serait remplacé par MAX_UINT. Je suis sûr qu'il existe une formule exacte pour déterminer le plus grand N qui fonctionnera pour un D et un nombre de bits particuliers, mais je n'ai plus le temps de travailler sur ce problème ...

(Il semble que ce graphique me manque pour le moment, je vais l'éditer et l'ajouter plus tard.) Il s'agit d'un graphique de la version 8 bits avec les cas particuliers notés ci-dessus:! [8 bits signés avec des cas particuliers pour 0 < N <= 10

Notez que pour 8 bits, l’erreur correspond à 10% ou moins pour toutes les erreurs du graphique, 16 bits = <0,1%.

10
WayneJ

Comme vous l'avez écrit, vous effectuez une arithmétique entière, qui tronque automatiquement tout résultat décimal. Pour effectuer une arithmétique en virgule flottante, modifiez les constantes en valeurs à virgule flottante:

int a = round(59.0 / 4);

Ou lancez-les dans une variable float ou un autre type à virgule flottante:

int a = round((float)59 / 4);

Dans les deux cas, vous devez arrondir le résultat final avec la fonction round() dans l'en-tête math.h. Veillez donc à utiliser #include <math.h> et à utiliser un compilateur compatible C99.

7
Chris Lutz
int a, b;
int c = a / b;
if(a % b) { c++; }

Vérifier s'il y a un reste vous permet d'arrondir manuellement le quotient de la division entière.

4
user3707766

Depuis le noyau Linux (GPLv2):

/*
 * Divide positive or negative dividend by positive divisor and round
 * to closest integer. Result is undefined for negative divisors and
 * for negative dividends if the divisor variable type is unsigned.
 */
#define DIV_ROUND_CLOSEST(x, divisor)(          \
{                           \
    typeof(x) __x = x;              \
    typeof(divisor) __d = divisor;          \
    (((typeof(x))-1) > 0 ||             \
     ((typeof(divisor))-1) > 0 || (__x) > 0) ?  \
        (((__x) + ((__d) / 2)) / (__d)) :   \
        (((__x) - ((__d) / 2)) / (__d));    \
}                           \
)
4
PauliusZ
#define CEIL(a, b) (((a) / (b)) + (((a) % (b)) > 0 ? 1 : 0))

Un autre MACROS utile (DOIT AVOIR):

#define MIN(a, b)  (((a) < (b)) ? (a) : (b))
#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
#define ABS(a)     (((a) < 0) ? -(a) : (a))
3
Magnetron

Voici ma solution. Je l’aime bien parce que je le trouve plus lisible et qu’il n’a pas de branche (ni if ​​ni ternaires).

int32_t divide(int32_t a, int32_t b) {
  int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
  int32_t sign = resultIsNegative*-2+1;
  return (a + (b / 2 * sign)) / b;
}

Programme de test complet illustrant le comportement souhaité:

#include <stdint.h>
#include <assert.h>

int32_t divide(int32_t a, int32_t b) {
  int32_t resultIsNegative = ((a ^ b) & 0x80000000) >> 31;
  int32_t sign = resultIsNegative*-2+1;
  return (a + (b / 2 * sign)) / b;
}

int main() {
  assert(divide(0, 3) == 0);

  assert(divide(1, 3) == 0);
  assert(divide(5, 3) == 2);

  assert(divide(-1, 3) == 0);
  assert(divide(-5, 3) == -2);

  assert(divide(1, -3) == 0);
  assert(divide(5, -3) == -2);

  assert(divide(-1, -3) == 0);
  assert(divide(-5, -3) == 2);
}
2

Emprunter de @ericbn je préfère définir comme

#define DIV_ROUND_INT(n,d) ((((n) < 0) ^ ((d) < 0)) ? (((n) - (d)/2)/(d)) : (((n) + (d)/2)/(d)))
or if you work only with unsigned ints
#define DIV_ROUND_UINT(n,d) ((((n) + (d)/2)/(d)))
2
zevero
int divide(x,y){
 int quotient = x/y;
 int remainder = x%y;
 if(remainder==0)
  return quotient;
 int tempY = divide(y,2);
 if(remainder>=tempY)
  quotient++;
 return quotient;
}

par exemple 59/4 Quotient = 14, tempY = 2, reste = 3, reste> = tempY donc quotient = 15;

1
Abhay Lolekar
double a=59.0/4;
int b=59/4;
if(a-b>=0.5){
    b++;
}
printf("%d",b);
  1. soit x la valeur flottante exacte de 59,0/4 (ici, il s'agit de 14,750000)
  2. laisser le plus petit entier inférieur à x être y (ici, il est 14)
  3. si x-y <0.5 alors y est la solution
  4. sinon y + 1 est la solution
1
Siva Dhatra

Si vous divisez des entiers positifs, vous pouvez le décaler, faire la division puis vérifier le bit situé à droite du réel b0. En d’autres termes, 100/8 correspond à 12,5 mais renverrait 12. Si vous effectuez (100 << 1)/8, vous pouvez vérifier b0, puis arrondir après avoir reporté le résultat.

0
bryan

essayez d'utiliser la fonction mathématique ceil qui rend les arrondis plus rapides. Math Ceil !

0
Samuel Santos

Ce qui suit arrondit correctement le quotient au nombre entier le plus proche pour les opérandes positifs et négatifs SANS les branches à virgule flottante ou conditionnelle (voir Résultat de l’assemblage ci-dessous). Suppose que les entiers complémentaires du N-bit 2.

#define ASR(x) ((x) < 0 ? -1 : 0)  // Compiles into a (N-1)-bit arithmetic shift right
#define ROUNDING(x,y) ( (y)/2 - (ASR((x)^(y)) & (y)))

int RoundedQuotient(int x, int y)
   {
   return (x + ROUNDING(x,y)) / y ;
   }

La valeur de ROUNDING aura le même signe que le dividende (x) et la moitié de la magnitudedu diviseur (y). Ajouter ROUNDING au dividende augmente donc sa magnitude avant que la division entière ne tronque le quotient obtenu. Voici la sortie du compilateur gcc avec l'optimisation -O3 pour un processeur Cortex-M4 ARM 32 bits:

RoundedQuotient:                // Input parameters: r0 = x, r1 = y
    eor     r2, r1, r0          // r2 = x^y
    and     r2, r1, r2, asr #31 // r2 = ASR(x^y) & y
    add     r3, r1, r1, lsr #31 // r3 = (y < 0) ? y + 1 : y
    rsb     r3, r2, r3, asr #1  // r3 = y/2 - (ASR(x^y) & y)
    add     r0, r0, r3          // r0 = x + (y/2 - (ASR(x^y) & y)
    sdiv    r0, r0, r1          // r0 = (x + ROUNDING(x,y)) / y
    bx      lr                  // Returns r0 = rounded quotient
0
Dan Lewis

Quelques alternatives pour la division par 4

return x/4 + (x/2 % 2);
return x/4 + (x % 4 >= 2)

Ou en général, division par n'importe quelle puissance de 2

return x/y + x/(y/2) % 2;    // or
return (x >> i) + ((x >> i - 1) & 1);  // with y = 2^i

Cela fonctionne en arrondissant vers le haut si la partie décimale 0.5, c’est-à-dire le premier chiffre base/2. En binaire, cela revient à ajouter le premier bit fractionnaire au résultat.

Cette méthode présente un avantage dans les architectures avec un registre d'indicateurs, car l'indicateur de report contiendra le dernier bit qui a été décalé . Par exemple sur x86, il peut être optimisé en

shr eax, i
adc eax, 0

Il est également facilement étendu pour prendre en charge les entiers signés. Notez que l'expression pour les nombres négatifs est

(x - 1)/y + ((x - 1)/(y/2) & 1)

nous pouvons le faire fonctionner pour les valeurs positives et négatives avec

int t = x + (x >> 31);
return (t >> i) + ((t >> i - 1) & 1);
0
phuclv

Pour certains algorithmes, vous avez besoin d'un biais cohérent lorsque «le plus proche» est un lien.

// round-to-nearest with mid-value bias towards positive infinity
int div_nearest( int n, int d )
   {
   if (d<0) n*=-1, d*=-1;
   return (abs(n)+((d-(n<0?1:0))>>1))/d * ((n<0)?-1:+1);
   }

Cela fonctionne quel que soit le signe du numérateur ou du dénominateur.


Si vous souhaitez faire correspondre les résultats de round(N/(double)D) (division et arrondi en virgule flottante), voici quelques variantes qui produisent toutes les mêmes résultats:

int div_nearest( int n, int d )
   {
   int r=(n<0?-1:+1)*(abs(d)>>1); // eliminates a division
// int r=((n<0)^(d<0)?-1:+1)*(d/2); // basically the same as @ericbn
// int r=(n*d<0?-1:+1)*(d/2); // small variation from @ericbn
   return (n+r)/d;
   }

Remarque: La vitesse relative de (abs(d)>>1) par rapport à (d/2) dépend probablement de la plate-forme.

0
nobar