web-dev-qa-db-fra.com

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

Étant donné les valeurs entières x et y, C et C++ renvoient tous deux comme quotient q = x/y le 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)=2 et 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 de transtyper en tant que float ou double. Existe-t-il une méthode plus directe qui évite la multiplication supplémentaire (ou une seconde division) et la branche, ainsi que la conversion en tant que nombre à virgule flottante?

207
andand

Pour arrondir ...

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

ou (éviter le débordement dans x + y)

q = 1 + ((x - 1) / y); // if x != 0
327
Sparky

Pour les nombres positifs:

    q = x/y + (x % y != 0);
59
Miguel Figueiredo

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éborder. Cela peut être résolu en utilisant un type plus large, mais que se passe-t-il si vous voulez 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 une conditionnelle, ce qui le rend plus court que le code des 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 des OP, car le modulo et la division sont effectués à l'aide de 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 intégrer l'appel de fonction dans le code de Nathan Ernst et émettre la même chose, mais gcc ne l'a pas fait lorsque je l'ai testé. Cela peut être dû au fait que le code compilé serait lié à une version unique de la bibliothèque standard.

Pour terminer, rien de tout cela n’importe 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 aussi rapides, à l'exception peut-être de celle de Nathan Ernst, qui risque d'être beaucoup plus lente si la fonction doit être extraite de la mémoire principale.

56
Jørgen Fogh

Vous pouvez utiliser la fonction div dans cstdlib pour obtenir le quotient et le reste en un seul appel, puis gérer le plafond séparément, comme dans l'exemple 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;
}
16
Nathan Ernst

Que dis-tu de ça? (nécessite y non négatif, donc ne l'utilisez 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, en éliminant le terme x + y - 1 et avec lui toute chance de débordement.

J'évite que x - 1 tourne autour lorsque x est un type non signé et contient zéro.

Pour x signé, négatif et zéro se combinent quand même dans un seul cas.

Ce n’est probablement pas un avantage énorme pour un processeur moderne polyvalent, mais dans un système embarqué, cela serait beaucoup plus rapide que n’importe laquelle des réponses correctes.

12
Ben Voigt

Il existe une solution pour x positif et négatif, mais uniquement pour y positif avec une division et sans branche:

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

Notez que si x est positif, alors la division va vers zéro et nous devrions ajouter 1 si le rappel n’est pas nul.

Si x est négatif, alors la division est vers zéro, c'est ce dont nous avons besoin et nous n'ajouterons rien car x % y n'est pas positif.

5
RiaD

forme générique simplifiée, 

int div_up(int n, int d) {
    return n / d + (((n < 0) ^ (d > 0)) && (n % d));
} //i.e. +1 iff (not exact int && positive result)

Pour une réponse plus générique, Fonctions C++ pour la division d’entiers avec une stratégie d’arrondi bien définie

2
Atif Hussain

J'aurais plutôt commenté mais je n'ai pas assez de représentants.

Autant que je sache, c'est le moyen le plus rapide pour les + et 2 personnes (testé dans CUDA)

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

sinon (aussi + ve seulement) j'aurais tendance à le faire comme suit:

q = x/y + !!(x % y);
0
Anroca