Comment arrondir le résultat d'une division entière?


335

Je pense notamment à la façon d'afficher les contrôles de pagination, lors de l'utilisation d'un langage tel que C # ou Java.

Si j'ai x éléments que je veux afficher en morceaux de y par page, combien de pages seront nécessaires?


1
Suis-je en train de manquer quelque chose? y / x + 1 fonctionne très bien (à condition de savoir que l'opérateur / arrondit toujours vers le bas).
rikkit

51
@rikkit - si y et x sont égaux, y / x + 1 est un trop haut.
Ian Nelson

1
Pour quiconque vient de le trouver, cette réponse à une question dupe évite une conversion inutile en double et évite les problèmes de débordement en plus de fournir une explication claire.
ZX9

2
@IanNelson plus généralement si xest divisible par y, y/x + 1serait un trop élevé.
Ohad Schneider

1
@ ZX9 Non, cela n'évite pas les problèmes de débordement. C'est exactement la même solution que Ian Nelson a posté ici.
user247702

Réponses:


478

Trouvé une solution élégante:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Source: Conversion de nombres, Roland Backhouse, 2001


12
-1 à cause du bug de débordement signalé par Brandon DuRette
finnw

30
M. Obvious dit: N'oubliez pas de vous assurer que recordsPerPage n'est pas nul
Adam Gent

7
Bon travail, je ne peux pas croire que C # n'a pas de plafond entier.
gosukiwi

2
Ouais, me voici à la mi-2017, tombant sur cette excellente réponse après avoir essayé plusieurs approches beaucoup plus complexes.
Mifo

1
Pour les langages avec un opérateur de division euclidienne approprié comme Python, une approche encore plus simple serait pageCount = -((-records) // recordsPerPage).
supercat

194

La conversion en virgule flottante et en arrière semble être une énorme perte de temps au niveau du processeur.

La solution d'Ian Nelson:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Peut être simplifié pour:

int pageCount = (records - 1) / recordsPerPage + 1;

AFAICS, cela n'a pas le bogue de débordement que Brandon DuRette a souligné, et parce qu'il ne l'utilise qu'une seule fois, vous n'avez pas besoin de stocker le recordsPerPage spécialement s'il provient d'une fonction coûteuse pour récupérer la valeur d'un fichier de configuration ou quelque chose.

C'est à dire que cela pourrait être inefficace, si config.fetch_value a utilisé une recherche de base de données ou quelque chose:

int pageCount = (records + config.fetch_value('records per page') - 1) / config.fetch_value('records per page');

Cela crée une variable dont vous n'avez pas vraiment besoin, qui a probablement des implications de mémoire (mineures) et est tout simplement trop de frappe:

int recordsPerPage = config.fetch_value('records per page')
int pageCount = (records + recordsPerPage - 1) / recordsPerPage;

Il s'agit d'une seule ligne et ne récupère les données qu'une seule fois:

int pageCount = (records - 1) / config.fetch_value('records per page') + 1;

5
+1, le problème de zéro enregistrement renvoyant toujours 1 pageCount est en fait pratique, car je voudrais toujours 1 page, montrant la placeholder / fausse ligne de "aucun enregistrement ne correspond à vos critères", permet d'éviter tout problème de "0 page count" dans quoi que ce soit contrôle de la pagination que vous utilisez.
Timothy Walters

27
N'oubliez pas que les deux solutions ne renvoient pas le même nombre de pages pour zéro enregistrement. Cette version simplifiée renverra 1 pageCount pour zéro enregistrement, tandis que la version Roland Backhouse renvoie 0 pageCount. Très bien si c'est ce que vous désirez, mais les deux équations ne sont pas équivalentes lorsqu'elles sont effectuées par une division entière de style C # / Java.
Ian Nelson

10
petite modification pour plus de clarté pour les personnes qui le scannent et les bodmas manquants lors du passage à la simulation de la solution Nelson (comme je l'ai fait la première fois!), la simplification avec des crochets est ... int pageCount = ((records - 1) / recordsPerPage) + 1;
Dave Heywood

1
Vous devez ajouter des parenthèses à la version simplifiée afin qu'elle ne repose pas sur un ordre d'opérations spécifique. c'est-à-dire, ((records - 1) / recordsPerPage) + 1.
Martin

1
@Ian, cette réponse ne revient pas toujours 1. Il peut retourner 0 si votre recordsPerPage est « 1 » et il y a 0 enregistrements: -1 / 1 + 1 = 0. Bien que ce ne soit pas un phénomène très courant, il est important de garder à l'esprit si vous autorisez les utilisateurs à ajuster la taille de la page. Donc, ne permettez pas aux utilisateurs d'avoir une taille de page de 1, vérifiez la taille de la page ou les deux (probablement préférable pour éviter un comportement inattendu).
Michael

81

Pour C #, la solution consiste à convertir les valeurs en double (comme Math.Ceiling prend un double):

int nPages = (int)Math.Ceiling((double)nItems / (double)nItemsPerPage);

En java, vous devriez faire de même avec Math.ceil ().


4
pourquoi cette réponse est-elle si loin quand l'op demande explicitement C #!
felickz

2
Vous devez également convertir la sortie en intcar Math.Ceilingrenvoie un doubleou decimal, selon les types d'entrée.
DanM7

15
car il est extrêmement inefficace
Zar Shardan

6
Il peut être inefficace mais il est extrêmement facile à comprendre. Étant donné que le calcul du nombre de pages est généralement effectué une fois par demande, toute perte de performances ne serait pas mesurable.
Jared Kells

1
C'est à peine plus lisible que ce "(dividende + (diviseur - 1)) / diviseur;" également son lent et nécessite la bibliothèque de mathématiques.
roule le

68

Cela devrait vous donner ce que vous voulez. Vous voudrez certainement x éléments divisés par y éléments par page, le problème est lorsque des nombres inégaux apparaissent, donc s'il y a une page partielle, nous voulons également ajouter une page.

int x = number_of_items;
int y = items_per_page;

// with out library
int pages = x/y + (x % y > 0 ? 1 : 0)

// with library
int pages = (int)Math.Ceiling((double)x / (double)y);

5
x / y + !! (x% y) évite la branche pour les langages de type C. Les chances sont bonnes, cependant, votre compilateur le fait quand même.
Rhys Ulerich

2
+1 pour ne pas déborder comme les réponses ci-dessus ... bien que la conversion des entiers en doubles juste pour Math.ceiling et puis en arrière soit une mauvaise idée dans le code sensible aux performances.
Cogwheel

3
@RhysUlerich qui ne fonctionne pas en c # (ne peut pas convertir directement un int en bool). La solution de rjmunro est le seul moyen d'éviter les branchements je pense.
smead

18

La solution mathématique d'entier fournie par Ian est intéressante, mais souffre d'un bogue de dépassement d'entier. En supposant que les variables sont toutes int, la solution pourrait être réécrite pour utiliser les longmathématiques et éviter le bogue:

int pageCount = (-1L + records + recordsPerPage) / recordsPerPage;

Si recordsc'est un long, le bogue persiste. La solution de module n'a pas le bogue.


4
Je ne pense pas que vous allez réellement toucher ce bug dans le scénario présenté. 2 ^ 31 enregistrements, c'est beaucoup à parcourir.
rjmunro


@finnw: AFAICS, il n'y a pas d'exemple concret sur cette page, juste un rapport de quelqu'un d'autre trouvant le bogue dans un scénario théorique.
rjmunro

5
Oui, j'étais pédant en soulignant le bug. De nombreux bugs peuvent exister à perpétuité sans jamais causer de problème. Un bogue de la même forme existait dans la mise en œuvre par JDK de binarySearch depuis environ neuf ans, avant que quelqu'un ne le signale ( googleresearch.blogspot.com/2006/06/… ). Je suppose que la question est, quelle que soit la probabilité que vous rencontriez ce bug, pourquoi ne pas le corriger à l'avance?
Brandon DuRette

4
En outre, il convient de noter que ce n'est pas seulement le nombre d'éléments paginés qui importe, c'est aussi la taille de la page. Donc, si vous créez une bibliothèque et que quelqu'un choisit de ne pas paginer en passant 2 ^ 31-1 (Integer.MAX_VALUE) comme taille de page, le bogue est déclenché.
Brandon DuRette

7

Une variante de la réponse de Nick Berardi qui évite une branche:

int q = records / recordsPerPage, r = records % recordsPerPage;
int pageCount = q - (-r >> (Integer.SIZE - 1));

Remarque: se (-r >> (Integer.SIZE - 1))compose du bit de signe de r, répété 32 fois (grâce à l'extension de signe de l' >>opérateur.) Il est évalué à 0 si rest nul ou négatif, -1 si rest positif. La soustraire qa donc pour effet d'ajouter 1 si records % recordsPerPage > 0.


4

Pour les enregistrements == 0, la solution de rjmunro donne 1. La solution correcte est 0. Cela dit, si vous savez que records> 0 (et je suis sûr que nous avons tous supposé recordsPerPage> 0), alors la solution rjmunro donne des résultats corrects et n'a aucun des problèmes de débordement.

int pageCount = 0;
if (records > 0)
{
    pageCount = (((records - 1) / recordsPerPage) + 1);
}
// no else required

Toutes les solutions mathématiques entières vont être plus efficaces que toutes les solutions à virgule flottante.


Il est peu probable que cette méthode soit un goulot d'étranglement des performances. Et si c'est le cas, vous devriez également considérer le coût de la succursale.
finnw

4

Besoin d'une méthode d'extension:

    public static int DivideUp(this int dividend, int divisor)
    {
        return (dividend + (divisor - 1)) / divisor;
    }

Aucun contrôle ici (débordement, DivideByZero etc.), n'hésitez pas à ajouter si vous le souhaitez. Soit dit en passant, pour ceux qui s'inquiètent de la surcharge de l'appel de méthode, des fonctions simples comme celle-ci peuvent de toute façon être intégrées par le compilateur, donc je ne pense pas que ce soit le cas. À votre santé.

PS, vous trouverez peut-être utile de le savoir également (il obtient le reste):

    int remainder; 
    int result = Math.DivRem(dividend, divisor, out remainder);

1
Ceci est une erreur. Par exemple: DivideUp(4, -2)renvoie 0 (devrait être -2). Elle n'est correcte que pour les entiers non négatifs, ce qui n'est pas clair dans la réponse ou dans l'interface de la fonction.
Thash

6
Thash, pourquoi ne faites-vous pas quelque chose d'utile comme ajouter la petite vérification supplémentaire puis si le nombre est négatif, au lieu de voter ma réponse vers le bas, et de faire incorrectement la déclaration générale: "C'est incorrect", alors qu'en fait c'est juste un bord Cas. J'ai déjà précisé que vous devriez d'abord faire d'autres vérifications: "Pas de vérifications ici (débordement, DivideByZero, etc.), n'hésitez pas à ajouter si vous le souhaitez ."
Nicholas Petersen du

1
La question mentionnait "Je pense en particulier à la façon d'afficher les contrôles de pagination ", de sorte que les nombres négatifs auraient été hors des limites de toute façon. Encore une fois, faites quelque chose d'utile et suggérez une vérification supplémentaire si vous le souhaitez, c'est un travail d'équipe.
Nicholas Petersen du

Je ne voulais pas être impoli et je suis désolé si vous le prenez ainsi. La question était "Comment arrondir le résultat d'une division entière". L'auteur a mentionné la pagination mais d'autres personnes peuvent avoir des besoins différents. Je pense qu'il serait préférable que votre fonction reflète d'une manière ou d'une autre qu'elle ne fonctionne pas pour les entiers négatifs car elle n'est pas claire à partir de l'interface (par exemple, un nom différent ou des types d'arguments). Pour travailler avec des entiers négatifs, vous pouvez par exemple prendre une valeur absolue du dividende et du diviseur et multiplier le résultat par son signe.
Thash

2

Une autre alternative consiste à utiliser la fonction mod () (ou '%'). S'il y a un reste non nul, incrémentez le résultat entier de la division.


1

Je fais ce qui suit, gère tous les débordements:

var totalPages = totalResults.IsDivisble(recordsperpage) ? totalResults/(recordsperpage) : totalResults/(recordsperpage) + 1;

Et utilisez cette extension pour s'il y a 0 résultat:

public static bool IsDivisble(this int x, int n)
{
           return (x%n) == 0;
}

Aussi, pour le numéro de page actuel (n'a pas été demandé mais pourrait être utile):

var currentPage = (int) Math.Ceiling(recordsperpage/(double) recordsperpage) + 1;

0

Alternative pour supprimer la ramification lors du test de zéro:

int pageCount = (records + recordsPerPage - 1) / recordsPerPage * (records != 0);

Je ne sais pas si cela fonctionnera en C #, devrait le faire en C / C ++.


-1

Une méthode générique, dont vous pouvez parcourir le résultat peut être intéressante:

public static Object[][] chunk(Object[] src, int chunkSize) {

    int overflow = src.length%chunkSize;
    int numChunks = (src.length/chunkSize) + (overflow>0?1:0);
    Object[][] dest = new Object[numChunks][];      
    for (int i=0; i<numChunks; i++) {
        dest[i] = new Object[ (i<numChunks-1 || overflow==0) ? chunkSize : overflow ];
        System.arraycopy(src, i*chunkSize, dest[i], 0, dest[i].length); 
    }
    return dest;
}

La goyave a une méthode similaire ( Lists.partition(List, int)) et, ironiquement, la size()méthode du résultat List(à partir de r09) souffre du bogue de débordement mentionné dans la réponse de Brandon DuRette .
finnw

-2

J'avais un besoin similaire où j'avais besoin de convertir des minutes en heures et minutes. Ce que j'ai utilisé était:

int hrs = 0; int mins = 0;

float tm = totalmins;

if ( tm > 60 ) ( hrs = (int) (tm / 60);

mins = (int) (tm - (hrs * 60));

System.out.println("Total time in Hours & Minutes = " + hrs + ":" + mins);

-2

Les éléments suivants devraient mieux arrondir que les solutions ci-dessus, mais au détriment des performances (en raison du calcul en virgule flottante de 0,5 * rctDenominator):

uint64_t integerDivide( const uint64_t& rctNumerator, const uint64_t& rctDenominator )
{
  // Ensure .5 upwards is rounded up (otherwise integer division just truncates - ie gives no remainder)
  return (rctDenominator == 0) ? 0 : (rctNumerator + (int)(0.5*rctDenominator)) / rctDenominator;
}

-4

Vous voudrez faire une division en virgule flottante, puis utiliser la fonction plafond, pour arrondir la valeur à l'entier suivant.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.