Existe-t-il un équivalent plafond de l'opérateur // en Python?

128

J'ai découvert l' //opérateur en Python qui, dans Python 3, effectue une division avec le sol.

Y a-t-il un opérateur qui divise avec ceil? (Je connais l' /opérateur qui en Python 3 effectue la division en virgule flottante.)

Cradam
la source
1
Important: voulez-vous un résultat int ou float?
smci
10
Vous devriez changer la réponse acceptée en dlitz. math.ceil est pour les flottants, il ne fonctionne pas avec les entiers longs de précision arbitraire de Python.
endolith
2
@milllimoose La question est valide, car 1) "division ceil" est aussi basée sur "division avec module", 2) les maths ne disent pas vraiment ce qui est commun et ce qui ne l'est pas, 3) vous avez besoin de cette opération pour "bin continu problème d'emballage ", c'est-à-dire combien de boîtes de taille $ k $ sont nécessaires pour emballer $ n $ articles.
Tomasz Gandor

Réponses:

55

Il n'y a pas d'opérateur qui divise avec ceil. Vous devez import mathet utilisermath.ceil

Charles Salvia
la source
donc foobar = math.ceil (foo / bar)? Hmm, je peux vivre avec ça, je ne sais pas où je voulais l'utiliser, c'était juste curieux, merci
Cradam
37
–1 ne pas utiliser , cela commencera à échouer pour les très grands entiers. Utilisez une bibliothèque arithmétique à précision multiple ou restez dans le domaine des nombres entiers avec cette approche.
wim
5
restez définitivement dans le domaine entier. c'est presque garanti d'être plus performant et moins de maux de tête.
Samy Bencherif
1
@David 天宇 Wong gmpy2 (mentionné dans une autre réponse ici) est bon.
wim
1
Notez que math.ceil est limité à 53 bits de précision. Si vous travaillez avec de grands entiers, vous risquez de ne pas obtenir de résultats exacts.
techkuz
292

Vous pouvez simplement faire la division du sol à l'envers:

def ceildiv(a, b):
    return -(-a // b)

Cela fonctionne parce que l'opérateur de division de Python effectue la division par étage (contrairement à C, où la division entière tronque la partie fractionnaire).

Cela fonctionne également avec les grands entiers de Python, car il n'y a pas de conversion en virgule flottante (avec perte).

Voici une démonstration:

>>> from __future__ import division   # a/b is float division
>>> from math import ceil
>>> b = 3
>>> for a in range(-7, 8):
...     print(["%d/%d" % (a, b), int(ceil(a / b)), -(-a // b)])
... 
['-7/3', -2, -2]
['-6/3', -2, -2]
['-5/3', -1, -1]
['-4/3', -1, -1]
['-3/3', -1, -1]
['-2/3', 0, 0]
['-1/3', 0, 0]
['0/3', 0, 0]
['1/3', 1, 1]
['2/3', 1, 1]
['3/3', 1, 1]
['4/3', 2, 2]
['5/3', 2, 2]
['6/3', 2, 2]
['7/3', 3, 3]
dlitz
la source
2
@apadana Je suis d'accord que c'est très intelligent, mais pas très lisible et difficile à maintenir! J'ai décidé d'importer ceil depuis les maths pour que lorsqu'un de mes collègues lit ma ligne de code, il comprenne ce qu'il fait!
SlimCheney
2
@apadana Je ne suis pas d'accord. La question demandait s'il y avait "un" opérateur pour cela "dans" Python. D'après les réponses, la réponse semble être «non». Je vote cependant la réponse de dlitz pour son utilité.
Ana Nimbus
12
@SlimCheney Lancez cette méthode dans une fonction documentée et vous êtes prêt à partir. Performance + lisibilité en un seul mouvement de balayage.
Samy Bencherif
2
@SamyBencherif: non seulement les performances + la lisibilité, mais aussi l'exactitude pour les grandes entrées; la virgule flottante a des limitations de représentation, intcontrairement à Python (enfin, pas de valeurs significatives; sur Python 64 bits, vous êtes limité aux 30 * (2**63 - 1)nombres de bits), et même la conversion temporaire en floatpeut perdre des informations. Comparez math.ceil((1 << 128) / 10)avec -(-(1 << 128) // 10).
ShadowRanger
1
Cela devrait simplement être inclus dans la bibliothèque standard
endolith
26

Vous pouvez le faire (x + (d-1)) // dlors de la division xpar d, c'est-à-dire (x + 4) // 5.

poussée
la source
2
C'est la méthode classique que j'utilise depuis toujours. Cela ne fonctionne pas pour les diviseurs négatifs.
Mark Ransom
Il produit le même résultat que math.ceil().
Abhijeet
3
@Abhijeet Oui, c'est ce que demande la question. Sauf que cela fonctionne mieux pour les grands entiers ci sys.float_info.max- dessus , et qu'il ne nécessite pas d'importation.
Artyer
23

Solution 1: convertir le sol en plafond avec négation

def ceiling_division(n, d):
    return -(n // -d)

Réminiscence de l' astuce de lévitation de Penn & Teller , cela "bouleverse le monde (avec négation), utilise la division du sol simple (où le plafond et le sol ont été échangés), puis retourne le monde à droite (avec négation à nouveau) "

Solution 2: laissez divmod () faire le travail

def ceiling_division(n, d):
    q, r = divmod(n, d)
    return q + bool(r)

La fonction divmod () donne (a // b, a % b)des nombres entiers (cela peut être moins fiable avec des flottants en raison d'une erreur d'arrondi). Le pas avec bool(r)ajoute un au quotient chaque fois qu'il y a un reste non nul.

Solution 3: Ajustez le numérateur avant la division

def ceiling_division(n, d):
    return (n + d - 1) // d

Traduisez le numérateur vers le haut pour que la division de plancher s'arrondisse au plafond prévu. Notez que cela ne fonctionne que pour les entiers.

Solution 4: Convertissez en flottants pour utiliser math.ceil ()

def ceiling_division(n, d):
    return math.ceil(n / d)

Le code math.ceil () est facile à comprendre, mais il se convertit des ints en floats et inversement. Ce n'est pas très rapide et cela peut avoir des problèmes d'arrondi. De plus, il s'appuie sur la sémantique Python 3 où "true division" produit un float et où la fonction ceil () renvoie un entier.

Raymond Hettinger
la source
2
Dans les tests rapides, le n ° 1 est le plus rapide ici, même comparé à -(-a // b)o_O
endolith
Confirmant ici que -(a // -b)c'est plus rapide que -(-a // b), du moins lors du chronométrage des exemples de jouets avecpython -m timeit ...
Jasha
19

Vous pouvez toujours le faire en ligne aussi

((foo - 1) // bar) + 1

En python3, c'est juste un ordre de grandeur plus rapide que de forcer la division float et d'appeler ceil (), à condition que vous vous souciez de la vitesse. Ce que vous ne devriez pas, à moins que vous n'ayez prouvé par l'utilisation que vous en avez besoin.

>>> timeit.timeit("((5 - 1) // 4) + 1", number = 100000000)
1.7249219375662506
>>> timeit.timeit("ceil(5/4)", setup="from math import ceil", number = 100000000)
12.096064013894647
Travis Griggs
la source
je viens de faire ces tests moi-même, je reçois environ 12,5 secondes, ehrm, pourquoi ne me soucierais-je pas de la vitesse alors que la différence de vitesse est si énorme?
Cradam
3
@Cradam Notez qu'il utilise pour faire 100 millions d'appels ( number=100000000). Par appel unique, la différence est assez insignifiante.
Rushy Panchal
4
Parce que la clarté du code l'emporte sur tout. La clarté est probablement objective dans ce cas. Mais vous devez toujours d'abord rendre lisibles / maintenables. Quand, et seulement quand, vous avez découvert un point de contrôle des performances, pouvez-vous enfreindre les règles. Les machines modernes sont si rapides, et si souvent toutes les autres tâches de votre programme rendent ce genre de différence perdu dans le bruit.
Travis Griggs
6
@TravisGriggs utilisant des mathématiques entières au lieu de mathématiques à virgule flottante n'est pas seulement pour la vitesse. Pour des nombres entiers assez grands, les mathématiques flottantes donnent la mauvaise réponse
endolith
1
Si foo = -8et bar = -4, par exemple, la réponse doit être 2, pas 3, tout comme -8 // -4. La division de plancher de Python est définie comme "celle de la division mathématique avec la fonction 'plancher' appliquée au résultat" et la division de plafond est la même chose mais avec ceil()au lieu de floor().
endolith
8

Notez que math.ceil est limité à 53 bits de précision. Si vous travaillez avec de grands entiers, vous risquez de ne pas obtenir de résultats exacts.

La bibliothèque gmpy2 fournit unc_div fonction qui utilise l'arrondi au plafond.

Avis de non-responsabilité: je maintiens gmpy2.

casevh
la source
3
Ce package serait utile si je faisais quelque chose de fortement orienté vers les mathématiques ou la science, je préfère la réponse qui utilise les bibliothèques de base. Je donne un vote positif car c'est une réponse utile
Cradam
Wow, peut confirmer. python2 -c 'from math import ceil;assert ceil(11520000000000000102.9)==11520000000000000000'(ainsi que le remplacement python3) LES DEUX sontTrue
JamesTheAwesomeDude
-6

Solution simple: a // b + 1

AL Verminburger
la source
2
C'est faux pour tout ce qui se divise uniformément. a = 4, b = 2, etc.
endolithe