Apakah ada plafon yang setara dengan // operator di Python?

127

Saya mengetahui tentang //operator dengan Python yang dalam Python 3 melakukan pembagian dengan lantai.

Apakah ada operator yang membagi dengan ceil? (Saya tahu tentang /operator yang dalam Python 3 melakukan pembagian floating point.)

Cradam
sumber
1
Penting: Apakah Anda menginginkan hasil int atau float?
smci
10
Anda harus mengubah jawaban yang diterima ke dlitz's. math.ceil adalah untuk float, ia tidak bekerja dengan long int presisi sewenang-wenang Python.
endolith
2
@milllimoose Pertanyaannya valid, karena 1) "pembagian langit" juga didasarkan pada "pembagian dengan modulus", 2) matematika tidak benar-benar menjelaskan apa yang umum dan apa yang tidak, 3) Anda memerlukan operasi ini untuk "bin berkelanjutan masalah pengepakan ", yaitu berapa kotak ukuran $ k $ yang dibutuhkan untuk mengemas $ n $ item.
Tomasz Gandor

Jawaban:

55

Tidak ada operator yang membagi dengan langit-langit. Anda perlu import mathdan menggunakanmath.ceil

Charles Salvia
sumber
jadi foobar = math.ceil (foo / bar)? Hmm, saya bisa hidup dengan itu, tidak tahu di mana saya ingin menggunakannya, hanya penasaran, terima kasih
Cradam
37
–1 jangan gunakan , ini akan mulai gagal untuk integer yang sangat besar. Gunakan pustaka aritmatika multi-presisi atau tetap dalam domain integer dengan pendekatan ini .
wim
5
pasti tetap dalam domain integer. itu hampir dijamin akan lebih berkinerja dan tidak membuat pusing kepala.
Samy Bencherif
1
@David 天宇 Wong gmpy2 (disebutkan dalam jawaban lain di sini) bagus.
wim
1
Perhatikan bahwa math.ceil dibatasi hingga 53 bit presisi. Jika Anda bekerja dengan bilangan bulat besar, Anda mungkin tidak mendapatkan hasil yang tepat.
techkuz
291

Anda bisa melakukan pembagian lantai terbalik:

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

Ini berfungsi karena operator divisi Python melakukan pembagian lantai (tidak seperti di C, di mana pembagian integer memotong bagian pecahan).

Ini juga berfungsi dengan bilangan bulat besar Python, karena tidak ada konversi floating-point (lossy).

Berikut peragaannya:

>>> 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
sumber
2
@apadana Saya setuju ini sangat cerdas, tetapi tidak terlalu mudah dibaca dan sulit dipertahankan! Saya telah memutuskan untuk mengimpor ceil dari matematika sehingga ketika salah satu kolega saya membaca baris kode saya, dia akan mengerti apa fungsinya!
SlimCheney
2
@apadana saya tidak setuju. Pertanyaannya menanyakan apakah ada "operator untuk ini" dalam "Python. Berdasarkan tanggapannya, jawabannya adalah "tidak". Saya mendukung jawaban dlitz untuk kegunaannya.
Ana Nimbus
12
@SlimCheney Masukkan metode ini ke dalam fungsi yang didokumentasikan dan Anda siap melakukannya. Performa + keterbacaan dalam satu gerakan menyapu.
Samy Bencherif
2
@SamyBencherif: Tidak hanya kinerja + keterbacaan, tetapi juga ketepatan untuk input yang besar; floating point memiliki batasan representasi, sedangkan Python inttidak (yah, tidak ada yang berarti; pada Python 64 bit Anda terbatas pada 30 * (2**63 - 1)angka bit), dan bahkan untuk sementara mengonversi floatdapat kehilangan informasi. Bandingkan math.ceil((1 << 128) / 10)dengan -(-(1 << 128) // 10).
ShadowRanger
1
Ini seharusnya hanya dimasukkan dalam perpustakaan standar
endolith
26

Anda bisa melakukannya (x + (d-1)) // dsaat membagi xdengan d, yaitu (x + 4) // 5.

menyodok
sumber
2
Ini adalah metode klasik yang saya gunakan selamanya. Tidak bekerja untuk pembagi negatif.
Markus Tebusan
Ini menghasilkan hasil yang sama seperti math.ceil().
Abhijeet
3
@Abhijeet Ya, itulah pertanyaan yang diajukan. Kecuali itu bekerja lebih baik untuk bilangan bulat besar di atas sys.float_info.max, dan itu tidak memerlukan impor.
Artyer
22

Solusi 1: Ubah lantai menjadi langit-langit dengan negasi

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

Mengingatkan pada trik levitasi Penn & Teller , ini "membalikkan dunia (dengan negasi), menggunakan pembagian lantai biasa (di mana langit-langit dan lantai telah ditukar), dan kemudian membalikkan dunia ke kanan (dengan negasi lagi) "

Solusi 2: Biarkan divmod () melakukan pekerjaannya

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

Fungsi divmod () diberikan (a // b, a % b)untuk integer (ini mungkin kurang dapat diandalkan dengan float karena kesalahan round-off). Langkah dengan bool(r)menambahkan satu ke hasil bagi setiap kali ada sisa bukan nol.

Solusi 3: Sesuaikan pembilang sebelum pembagian

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

Terjemahkan pembilangnya ke atas sehingga pembagian lantai membulat ke bawah ke langit-langit yang diinginkan. Catatan, ini hanya berfungsi untuk bilangan bulat.

Solusi 4: Ubah menjadi float untuk menggunakan math.ceil ()

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

Kode math.ceil () mudah dimengerti, tetapi diubah dari int menjadi floats dan back. Ini tidak terlalu cepat dan mungkin memiliki masalah pembulatan. Juga, ini bergantung pada semantik Python 3 di mana "pembagian benar" menghasilkan pelampung dan di mana fungsi ceil () mengembalikan bilangan bulat.

Raymond Hettinger
sumber
2
Dalam tes cepat, # 1 adalah yang tercepat di sini, bahkan dibandingkan dengan -(-a // b)o_O
endolith
Mengkonfirmasi di sini yang -(a // -b)lebih cepat dari -(-a // b), setidaknya ketika contoh mainan waktu denganpython -m timeit ...
Jasha
19

Anda juga selalu dapat melakukannya secara inline

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

Dalam python3, ini hanya malu urutan besarnya lebih cepat daripada memaksa divisi float dan memanggil ceil (), asalkan Anda peduli dengan kecepatan. Yang seharusnya tidak Anda lakukan, kecuali Anda telah membuktikan melalui penggunaan yang Anda perlukan.

>>> 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
sumber
hanya menjalankan tes itu sendiri Saya mendapatkan sekitar 12,5 detik, ehrm, mengapa saya tidak peduli dengan kecepatan ketika perbedaan kecepatannya sangat besar?
Cradam
3
@Cradam Perhatikan bahwa dia menggunakan melakukan 100 juta panggilan ( number=100000000). Per satu panggilan, perbedaannya tidak terlalu signifikan.
Rushy Panchal
4
Karena kejelasan kode mengalahkan segalanya. Kejelasan mungkin objektif dalam kasus ini. Tetapi Anda harus selalu membuatnya dapat dibaca / dipelihara terlebih dahulu. Kapan, dan hanya jika, Anda telah menemukan titik pemeriksaan kinerja, Anda bisa melanggar aturan. Mesin modern sangat cepat, dan sering kali semua hal lain yang dilakukan program Anda membuat perbedaan semacam ini hilang dalam kebisingan.
Travis Griggs
6
@TravisGriggs menggunakan matematika integer dan bukan matematika floating point bukan hanya untuk kecepatan. Untuk bilangan bulat yang cukup besar, matematika mengambang memberikan jawaban yang salah
endolit
1
Jika foo = -8dan bar = -4, misalnya, jawabannya harus 2, bukan 3, sama seperti -8 // -4. Pembagian lantai Python didefinisikan sebagai "bahwa pembagian matematika dengan fungsi 'lantai' diterapkan pada hasil" dan pembagian langit-langit adalah hal yang sama tetapi ceil()bukan dengan floor().
endolith
8

Perhatikan bahwa math.ceil dibatasi hingga 53 bit presisi. Jika Anda bekerja dengan bilangan bulat besar, Anda mungkin tidak mendapatkan hasil yang tepat.

The gmpy2 libary menyediakan c_divfungsi yang menggunakan langit-langit pembulatan.

Penafian: Saya mempertahankan gmpy2.

casevh
sumber
3
Paket ini akan berguna jika saya melakukan sesuatu yang sangat berorientasi pada matematika atau sains, saya lebih suka jawaban yang menggunakan perpustakaan inti. Saya memberikan suara positif karena ini adalah jawaban yang berguna
Cradam
Wah, bisa konfirmasi. python2 -c 'from math import ceil;assert ceil(11520000000000000102.9)==11520000000000000000'(dan juga menggantikannya python3) KEDUATrue
JamesTheAwesomeDude
-6

Solusi sederhana: a // b + 1

AL Verminburger
sumber
2
Ini salah untuk apa pun yang terbagi rata. a = 4, b = 2, dll.
endolit