Plafond rapide d'une division entière en C / C ++

262

Étant donné les valeurs entières xet y, C et C ++ renvoient tous les deux comme quotient q = x/yle plancher de l'équivalent en virgule flottante. Je suis intéressé par une méthode de retour du plafond à la place. Par exemple, ceil(10/5)=2et ceil(11/5)=3.

L'approche évidente implique quelque chose comme:

q = x / y;
if (q * y < x) ++q;

Cela nécessite une comparaison et une multiplication supplémentaires; et d'autres méthodes que j'ai vues (utilisées en fait) impliquent le casting en tant que floatou double. Existe-t-il une méthode plus directe qui évite la multiplication supplémentaire (ou une deuxième division) et la branche, et qui évite également la conversion en nombre à virgule flottante?

et et
la source
70
l'instruction divide renvoie souvent à la fois le quotient et le reste, donc il n'y a pas besoin de multiplier, c'est juste q = x/y + (x % y != 0);assez
phuclv
2
@ LưuVĩnhPhúc ce commentaire devrait être la réponse acceptée, imo.
Andreas Grapentin
1
@ LưuVĩnhPhúc Sérieusement, vous devez ajouter cela comme réponse. Je viens de l'utiliser pour ma réponse lors d'un test de codilité. Cela a fonctionné comme un charme, même si je ne sais pas comment fonctionne la partie mod de la réponse, mais elle a fait le travail.
Zachary Kraus
2
@AndreasGrapentin la réponse ci-dessous de Miguel Figueiredo a été soumise près d'un an avant que Lưu Vĩnh Phúc ne laisse le commentaire ci-dessus. Bien que je comprenne à quel point la solution de Miguel est attrayante et élégante, je ne suis pas enclin à changer la réponse acceptée à cette date tardive. Les deux approches restent valables. Si vous en êtes assez convaincu, je vous suggère de montrer votre soutien en votant la réponse de Miguel ci-dessous.
andand
1
Étrange, je n'ai vu aucune mesure saine ou analyse des solutions proposées. Vous parlez de vitesse à proximité de l'os, mais il n'y a aucune discussion sur les architectures, les pipelines, les instructions de branchement et les cycles d'horloge.
Rado

Réponses:

394

Pour les nombres positifs

unsigned int x, y, q;

Pour arrondir ...

q = (x + y - 1) / y;

ou (en évitant le débordement en x + y)

q = 1 + ((x - 1) / y); // if x != 0
Sparky
la source
6
@bitc: Pour les nombres négatifs, je pense que C99 spécifie arrondi à zéro, tout x/ycomme le plafond de la division. C90 n'a pas précisé comment arrondir, et je ne pense pas que la norme C ++ actuelle le fasse non plus.
David Thornley
6
Voir le post d'Eric Lippert: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar
3
Remarque: cela pourrait déborder. q = ((long long) x + y - 1) / y ne le sera pas. Mon code est cependant plus lent, donc si vous savez que vos numéros ne déborderont pas, vous devriez utiliser la version de Sparky.
Jørgen Fogh
1
@bitc: Je pense que le point de David était que vous n'utiliseriez pas le calcul ci-dessus si le résultat est négatif - vous utiliseriez simplementq = x / y;
caf
12
Le second a un problème où x est 0. ceil (0 / y) = 0 mais il renvoie 1.
Omry Yadan
78

Pour les nombres positifs:

    q = x/y + (x % y != 0);
Miguel Figueiredo
la source
5
l'instruction de division de l'architecture la plus courante inclut également le reste dans son résultat, donc cela n'a vraiment besoin que d'une division et serait très rapide
phuclv
58

La réponse de Sparky est un moyen standard de résoudre ce problème, mais comme je l'ai également écrit dans mon commentaire, vous courez le risque de débordements. Cela peut être résolu en utilisant un type plus large, mais que se passe-t-il si vous souhaitez diviser long longs?

La réponse de Nathan Ernst fournit une solution, mais elle implique un appel de fonction, une déclaration de variable et un conditionnel, ce qui le rend pas plus court que le code OP et probablement encore plus lent, car il est plus difficile à optimiser.

Ma solution est la suivante:

q = (x % y) ? x / y + 1 : x / y;

Il sera légèrement plus rapide que le code OP, car le modulo et la division sont effectués en utilisant la même instruction sur le processeur, car le compilateur peut voir qu'ils sont équivalents. Au moins gcc 4.4.1 effectue cette optimisation avec l'indicateur -O2 sur x86.

En théorie, le compilateur pourrait incorporer l'appel de fonction dans le code de Nathan Ernst et émettre la même chose, mais gcc ne l'a pas fait quand je l'ai testé. Cela peut être dû au fait qu'il lierait le code compilé à une seule version de la bibliothèque standard.

En conclusion, rien de tout cela n'a d'importance sur une machine moderne, sauf si vous êtes dans une boucle extrêmement serrée et que toutes vos données sont dans des registres ou dans le cache L1. Sinon, toutes ces solutions seront également rapides, à l'exception peut-être de Nathan Ernst, qui pourrait être considérablement plus lent si la fonction doit être récupérée de la mémoire principale.

Jørgen Fogh
la source
3
Il y avait un moyen plus facile de corriger le débordement, réduisez simplement y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt
-1: c'est un moyen inefficace, car il se négocie à bon marché * pour un% coûteux; pire que l'approche OP.
Yves Daoust
2
Non. Comme je l'ai expliqué dans la réponse, l'opérateur% est libre lorsque vous effectuez déjà la division.
Jørgen Fogh
1
Alors, q = x / y + (x % y > 0);c'est plus facile que l' ? :expression?
Han
Cela dépend de ce que vous entendez par «plus facile». Cela peut être plus rapide ou non, selon la façon dont le compilateur le traduit. Ma conjecture serait plus lente mais je devrais la mesurer pour être sûr.
Jørgen Fogh
18

Vous pouvez utiliser la divfonction dans cstdlib pour obtenir le quotient et le reste en un seul appel, puis gérer le plafond séparément, comme ci-dessous

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}
Nathan Ernst
la source
12
Comme un cas intéressant du double coup, vous pourriez aussi return res.quot + !!res.rem;:)
Sam Harwell
Est-ce que ldiv ne fait pas toujours la promotion des arguments en de longues périodes? Et cela ne coûte rien, en amont ou en aval?
einpoklum
12

Que dis-tu de ça? (nécessite y non négatif, ne l'utilisez donc pas dans les rares cas où y est une variable sans garantie de non-négativité)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

J'ai réduit y/yà un, éliminant le terme x + y - 1et avec lui toute possibilité de débordement.

J'évite de x - 1m'enrouler quand xest un type non signé et contient zéro.

Pour signé x, négatif et zéro se combinent toujours en un seul cas.

Probablement pas un énorme avantage sur un CPU polyvalent moderne, mais ce serait beaucoup plus rapide dans un système embarqué que n'importe quelle autre bonne réponse.

Ben Voigt
la source
Votre autre retourne toujours 0, pas besoin de calculer quoi que ce soit.
Ruud Althuizen
@Ruud: pas vrai. Considérons x = -45 et y = 4
Ben Voigt
7

Il existe une solution à la fois positive et négative, xmais uniquement pour le positif yavec une seule division et sans branches:

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

Notez que si xest positif, la division est vers zéro et nous devons ajouter 1 si le rappel n'est pas nul.

Si xest négatif alors la division est vers zéro, c'est ce dont nous avons besoin, et nous n'ajouterons rien parce que ce x % yn'est pas positif

RiaD
la source
intéressant, car il y a des cas courants où y est constant
Wolf
1
le mod nécessite une division, donc ce n'est pas seulement une division ici, mais peut-être que complier peut optimiser deux divisions similaires en une.
M.kazem Akhgary
4

Cela fonctionne pour les nombres positifs ou négatifs:

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

S'il y a un reste, vérifie si xet ysont du même signe et ajoute en 1conséquence.

Mark Conway
la source
3

J'aurais plutôt commenté mais je n'ai pas de représentant suffisamment élevé.

Pour autant que je sache, pour les arguments positifs et un diviseur qui est une puissance de 2, c'est le moyen le plus rapide (testé dans CUDA):

//example y=8
q = (x >> 3) + !!(x & 7);

Pour les arguments positifs génériques uniquement, j'ai tendance à le faire comme suit:

q = x/y + !!(x % y);
Anroca
la source
Il serait intéressant de voir comment se q = x/y + !!(x % y);comparent q = x/y + (x % y == 0);et les q = (x + y - 1) / y;solutions en termes de performances dans le CUDA contemporain.
Greg Kramida
-2

Compiler avec O3, le compilateur effectue bien l'optimisation.

q = x / y;
if (x % y)  ++q;
dhb
la source