213 votes

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

J'ai découvert le // en Python qui, en Python 3, fait la division avec le plancher.

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

437voto

dlitz Points 842

Non, mais vous pouvez utiliser la division du plancher à l'envers:¹

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

Cela fonctionne parce que L'opérateur de division de Python effectue la division de plancher. (contrairement au C, où la division entière tronque la partie fractionnaire).

Voici une démonstration :

>>> from __future__ import division     # for Python 2.x compatibility
>>> import math
>>> def ceildiv(a, b):
...     return -(a // -b)
...
>>> b = 3
>>> for a in range(-7, 8):
...     q1 = math.ceil(a / b)   # a/b is float division
...     q2 = ceildiv(a, b)
...     print("%2d/%d %2d %2d" % (a, b, q1, q2))
...
-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

Pourquoi ceci au lieu de math.ceil ?

math.ceil(a / b) peut tranquillement produire des résultats incorrects, car il introduit une erreur de virgule flottante. Par exemple :

>>> from __future__ import division     # Python 2.x compat
>>> import math
>>> def ceildiv(a, b):
...     return -(a // -b)
...
>>> x = 2**64
>>> y = 2**48
>>> ceildiv(x, y)
65536
>>> ceildiv(x + 1, y)
65537                       # Correct
>>> math.ceil(x / y)
65536
>>> math.ceil((x + 1) / y)
65536                       # Incorrect!

En général, il est considéré comme une bonne pratique d'éviter complètement l'arithmétique à virgule flottante, sauf si vous en avez spécifiquement besoin. L'arithmétique à virgule flottante présente plusieurs cas limites délicats, ce qui tend à introduire des bogues si vous ne faites pas attention. Elles peuvent également s'avérer coûteuses en termes de calcul sur les appareils de petite taille ou de faible puissance qui ne disposent pas d'une FPU matérielle.


¹Dans une version précédente de cette réponse, ceildiv était implémenté en tant que return -(-a // b) mais il a été changé en return -(a // -b) après que des commentateurs aient signalé que ce dernier est légèrement plus performant dans les benchmarks. C'est logique, car le dividende ( a ) est généralement plus grande que le diviseur ( b ). Étant donné que Python utilise une arithmétique de précision arbitraire pour effectuer ces calculs, calculer la négation unaire -a impliquerait presque toujours un travail égal ou supérieur à celui du calcul. -b .

72voto

Charles Salvia Points 28661

Il n'existe pas d'opérateur qui divise avec ceil. Vous devez import math et utiliser math.ceil

67voto

Raymond Hettinger Points 231

Solution 1 : Convertissez le plancher en plafond avec la négation.

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

Rappelant le Tour de lévitation de Penn & Teller il s'agit de "mettre le monde à l'envers (avec négation), d'utiliser une simple division du sol (où le plafond et le sol ont été échangés), puis de mettre le monde à l'endroit (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)

El divmod() La fonction donne (a // b, a % b) pour les entiers (cela peut être moins fiable avec les flottants en raison des erreurs 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 de sorte que la division du plancher s'arrondisse au plafond prévu. Remarque : cela ne fonctionne que pour les nombres entiers.

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

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

El math.ceil() Le code est facile à comprendre, mais il convertit les ints en flottants et inversement. Ce n'est pas très rapide et cela peut poser des problèmes d'arrondi. De plus, il s'appuie sur la sémantique de Python 3, où la "vraie division" produit un flottant et où la balise ceil() renvoie un nombre entier.

33voto

poke Points 64398

Vous pourriez faire (x + (d-1)) // d en divisant x por d par exemple (x + 4) // 5 .

24voto

Travis Griggs Points 2851

Vous pouvez toujours le faire en ligne.

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

En python3, cette méthode est un peu moins d'un ordre de grandeur plus rapide que de forcer la division des flottants et d'appeler ceil(), pour autant que la vitesse vous importe. Ce qui ne devrait pas être le cas, sauf si vous avez prouvé par l'usage 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X