Esiste un equivalente del soffitto dell'operatore // in Python?


127

Ho scoperto l' //operatore in Python che in Python 3 fa la divisione con il pavimento.

Esiste invece un operatore che divide con ceil? (Conosco l' /operatore che in Python 3 esegue la divisione in virgola mobile.)


1
Importante: vuoi un risultato int o float?
smci

10
Dovresti cambiare la risposta accettata in dlitz's. math.ceil è per float, non funziona con gli int lunghi di precisione arbitraria di Python.
endolith

2
@milllimoose La domanda è valida, perché 1) "divisione ceil" è anche basata su "divisione con modulo", 2) la matematica non dice veramente cosa è comune e cosa non lo è, 3) hai bisogno di questa operazione per "contenitore continuo problema di imballaggio ", ovvero quante scatole di dimensione $ k $ sono necessarie per imballare $ n $ articoli.
Tomasz Gandor

Risposte:


55

Non c'è operatore che divide con ceil. È necessario import mathe utilizzaremath.ceil


quindi foobar = math.ceil (foo / bar)? Hmm, posso conviverci, non so dove volessi usarlo, ero solo curioso, grazie
Cradam

37
–1 non utilizzare , inizierà a non funzionare per interi molto grandi. Utilizzare una libreria aritmetica a precisione multipla o rimanere nel dominio dei numeri interi con questo approccio.
wim

5
rimani sicuramente nel dominio intero. è quasi garantito che sia più performante e meno mal di testa.
Samy Bencherif

1
@David 天宇 Wong gmpy2 (menzionato in un'altra risposta qui) è buono.
wim

1
Si noti che math.ceil è limitato a 53 bit di precisione. Se stai lavorando con numeri interi di grandi dimensioni, potresti non ottenere risultati esatti.
techkuz

291

Puoi semplicemente eseguire la divisione del pavimento capovolta:

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

Questo funziona perché l'operatore di divisione di Python esegue la divisione dei piani (a differenza di C, dove la divisione intera tronca la parte frazionaria).

Funziona anche con i grandi interi di Python, perché non c'è conversione in virgola mobile (con perdita).

Ecco una dimostrazione:

>>> 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]

2
@apadana Sono d'accordo che è molto intelligente, ma non molto leggibile e difficile da mantenere! Ho deciso di importare ceil dalla matematica in modo che quando uno dei miei colleghi leggerà la mia riga di codice capirà cosa fa!
SlimCheney

2
@apadana Non sono d'accordo. La domanda chiedeva se "esiste" un operatore per questo "in" Python. Sulla base delle risposte, la risposta sembra essere "no". Tuttavia, sto votando positivamente la risposta di dlitz per la sua utilità.
Ana Nimbus

12
@SlimCheney Metti questo metodo in una funzione documentata e sei a posto. Prestazioni e leggibilità in un unico movimento.
Samy Bencherif

2
@SamyBencherif: non solo prestazioni + leggibilità, ma anche correttezza per input di grandi dimensioni; la virgola mobile ha limitazioni di rappresentazione, mentre quella di Python intno (beh, non significative; su Python a 64 bit sei limitato a 30 * (2**63 - 1)numeri di bit), e anche la conversione temporanea in floatpuò perdere informazioni. Confronta math.ceil((1 << 128) / 10)con -(-(1 << 128) // 10).
ShadowRanger

1
Questo dovrebbe essere incluso solo nella libreria standard
endolith

26

Potresti fare (x + (d-1)) // dquando dividi xper d, ad es (x + 4) // 5.


2
Questo è il metodo classico che uso da sempre. Tuttavia, non funziona per i divisori negativi.
Mark Ransom

Produce lo stesso risultato di math.ceil().
Abhijeet

3
@Abhijeet Sì, è quello che chiede la domanda. Tranne che funziona meglio per i grandi numeri interi sopra sys.float_info.maxe non richiede un'importazione.
Artyer

22

Soluzione 1: convertire il pavimento in soffitto con la negazione

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

Ricorda il trucco della levitazione Penn & Teller , questo "capovolge il mondo (con negazione), usa una divisione semplice del pavimento (dove il soffitto e il pavimento sono stati scambiati), e poi gira il mondo sul lato destro (di nuovo con la negazione) "

Soluzione 2: lascia che divmod () faccia il lavoro

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

La funzione divmod () fornisce gli (a // b, a % b)interi (questo potrebbe essere meno affidabile con i float a causa di un errore di arrotondamento). Il passaggio con bool(r)aggiunge uno al quoziente ogni volta che è presente un resto diverso da zero.

Soluzione 3: regolare il numeratore prima della divisione

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

Traslare il numeratore verso l'alto in modo che la divisione del pavimento si arrotondi per difetto al soffitto previsto. Nota, questo funziona solo per i numeri interi.

Soluzione 4: converti in float per utilizzare math.ceil ()

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

Il codice math.ceil () è facile da capire, ma converte da int a float e viceversa . Non è molto veloce e potrebbe avere problemi di arrotondamento. Inoltre, si basa sulla semantica di Python 3 dove "true division" produce un float e dove la funzione ceil () restituisce un numero intero.


2
Nei test rapidi, il numero 1 è il più veloce qui, anche rispetto a -(-a // b)o_O
endolith il

Confermando qui che -(a // -b)è più veloce di -(-a // b), almeno quando si temporizzano esempi di giocattoli conpython -m timeit ...
Jasha

19

Puoi sempre farlo anche in linea

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

In python3, questo è appena un ordine di grandezza più veloce che forzare la divisione in virgola mobile e chiamare ceil (), a patto che ti interessi della velocità. Cosa che non dovresti, a meno che tu non abbia dimostrato attraverso l'uso che è necessario.

>>> timeit.timeit("((5 - 1) // 4) + 1", number = 100000000)
1.7249219375662506
>>> timeit.timeit("ceil(5/4)", setup="from math import ceil", number = 100000000)
12.096064013894647

ho appena eseguito quei test da solo ottengo circa 12,5 secondi, ehrm, perché non dovrebbe interessarmi alla velocità quando è una differenza di velocità così grande?
Cradam

3
@Cradam Nota che sta usando facendo 100 milioni di chiamate ( number=100000000). Per singola chiamata, la differenza è piuttosto insignificante.
Rushy Panchal

4
Perché la chiarezza del codice ha la meglio su tutto. La chiarezza è oggettiva in questo caso probabilmente. Ma dovresti sempre renderlo leggibile / gestibile prima. Quando, e solo quando, hai scoperto un punto di controllo delle prestazioni, puoi infrangere le regole. Le macchine moderne sono così veloci, e così spesso tutte le altre cose che il tuo programma sta facendo rendono questo tipo di differenza perso nel rumore.
Travis Griggs

6
@TravisGriggs che utilizza la matematica intera invece della matematica in virgola mobile non è solo per la velocità. Per numeri interi abbastanza grandi, la matematica a virgola mobile fornisce la risposta sbagliata
endolith

1
Se foo = -8e bar = -4, ad esempio, la risposta dovrebbe essere 2, non 3, proprio come -8 // -4. La divisione del pavimento in Python è definita come "quella della divisione matematica con la funzione 'pavimento' applicata al risultato" e la divisione del soffitto è la stessa cosa ma con ceil()invece di floor().
endolith

8

Si noti che math.ceil è limitato a 53 bit di precisione. Se stai lavorando con numeri interi di grandi dimensioni, potresti non ottenere risultati esatti.

La libreria gmpy2 fornisce una c_divfunzione che utilizza l'arrotondamento del soffitto.

Disclaimer: mantengo gmpy2.


3
Questo pacchetto sarebbe utile se stessi facendo qualcosa di fortemente orientato alla matematica o alla scienza, ma preferisco la risposta che utilizza le librerie di base. Sto dando un
voto positivo,

Wow, posso confermare. python2 -c 'from math import ceil;assert ceil(11520000000000000102.9)==11520000000000000000'(oltre a sostituire python3) ENTRAMBI sonoTrue
JamesTheAwesomeDude

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.