The Python ceil() function rounds a number up to the nearest whole number. It lives in the math module, so you call it as math.ceil(x) after import math: math.ceil(2.1) gives 3, and math.ceil(-2.8) gives -2, because up means towards positive infinity. It always returns an int, never a float. This guide covers the ceiling function with positive and negative numbers, the integer-division version that does not lose precision, rounding money up, NumPy and pandas columns, and the errors you will hit.
Every example was run with Python 3.12.5 (NumPy 2.5.3 and pandas 3.0.6) in the Windows Command Prompt, and the output shown is the real output. Reference: math.ceil() in the Python documentation.
The Python ceil() function
ceil() is not a built-in: it has to be imported from math first. After that it takes one number and gives you the smallest whole number that is not less than it:
import math
print(math.ceil(2.1)) # up to the next whole number
print(math.ceil(2.9))
print(math.ceil(3.0)) # already whole, nothing changes
print(math.ceil(-2.8)) # "up" means towards zero here
result = math.ceil(4.2)
print(result, type(result).__name__) # always an int, never a float
Output:
3
3
3
-2
5 int

math.ceil() returns an int, so it is ready to use as a count or an index.| Call | Result | Why |
|---|---|---|
math.ceil(2.1) | 3 | the next whole number up |
math.ceil(2.9) | 3 | same, however close it already is |
math.ceil(3.0) | 3 | already whole, unchanged |
math.ceil(-2.8) | -2 | up means towards positive infinity |
math.ceil(7) | 7 | an int passes straight through |
Python ceil() syntax and what it accepts
The signature is math.ceil(x), where x is any real number. That includes Decimal and Fraction values, and any object of your own that defines a __ceil__ method, which is how the function stays useful outside plain floats:
import math
from decimal import Decimal
from fractions import Fraction
print(math.ceil(7)) # an int comes back unchanged
print(math.ceil(Decimal("2.0001"))) # Decimal works
print(math.ceil(Fraction(7, 3))) # so does Fraction: 7/3 is 2.33...
print(math.ceil(True)) # bool is an int in Python
class Delivery:
"""Any object can support ceil() by defining __ceil__."""
def __init__(self, hours: float) -> None:
self.hours = hours
def __ceil__(self) -> int:
return math.ceil(self.hours)
print(math.ceil(Delivery(4.2)))
Output:
7
3
3
1
5
In Python 2, math.ceil() returned a float. In Python 3 it returns an int, so you no longer need int(math.ceil(x)): that extra call does nothing.
ceil() with negative numbers, and how it differs from floor(), round() and int()
Negative numbers are where the four functions part company. ceil() always moves towards positive infinity, floor() always moves towards negative infinity, int() and math.trunc() chop the decimals off towards zero, and round() goes to the nearest even number on a tie:
import math
values = [2.4, 2.5, 2.6, -2.4, -2.5, -2.6]
print(f"{'value':>6} {'ceil':>6} {'floor':>6} {'int':>6} {'round':>6} {'trunc':>6}")
for value in values:
print(f"{value:>6} {math.ceil(value):>6} {math.floor(value):>6} {int(value):>6} "
f"{round(value):>6} {math.trunc(value):>6}")
Output:
value ceil floor int round trunc
2.4 3 2 2 2 2
2.5 3 2 2 2 2
2.6 3 2 2 3 2
-2.4 -2 -3 -2 -2 -2
-2.5 -2 -3 -2 -2 -2
-2.6 -2 -3 -2 -3 -2

2.5 and -2.5 rows show banker’s rounding in round().So ceil() and int() agree on negative values and differ on positive ones, which is exactly the opposite of what most people assume.
Real work: pages, billing blocks and boxes
Almost every practical use of the ceiling function is “how many whole units do I need to cover this”. Dividing gives a fraction, and you cannot ship 11.4 boxes:
import math
items = 47
per_page = 10
print("pages needed:", math.ceil(items / per_page))
minutes = 23
print("billed 15-minute blocks:", math.ceil(minutes / 15))
# round a value up to the next multiple of 5
for length in (11, 15, 16):
print(length, "->", math.ceil(length / 5) * 5)
boxes = math.ceil(137 / 12) # 137 mugs, 12 to a box
print("boxes to ship:", boxes, "| empty slots:", boxes * 12 - 137)
Output:
pages needed: 5
billed 15-minute blocks: 2
11 -> 15
15 -> 15
16 -> 20
boxes to ship: 12 | empty slots: 7
The same pattern rounds a value up to the next multiple: divide by the step, take the ceiling, multiply back.
Ceiling division without math.ceil()
There is an integer-only version of the same idea: -(-a // b). It uses the double slash floor-division operator twice, so the answer never passes through a float. For everyday numbers both give the same result. For big integers, only one of them is right:
import math
items, per_page = 47, 10
print(math.ceil(items / per_page), -(-items // per_page)) # same answer, two ways
# with big integers, the float division inside math.ceil loses precision
big = 10 ** 17
print("math.ceil(big / 3) =", math.ceil(big / 3))
print("-(-big // 3) =", -(-big // 3))
print("equal?", math.ceil(big / 3) == -(-big // 3))
print("exact remainder:", big % 3)
Output:
5 5
math.ceil(big / 3) = 33333333333333332
-(-big // 3) = 33333333333333334
equal? False
exact remainder: 1

math.ceil(big / 3) is off by two: the float lost the precision before ceil() ever ran.A Python int has no size limit, but a float carries only about 15 to 17 significant digits. big / 3 is rounded to a float before math.ceil() sees it, so the result is wrong while looking perfectly reasonable. Any time both operands are integers, prefer -(-a // b).
Floats round up when you do not expect it
The other float surprise is smaller but far more common. 0.1 + 0.2 is not exactly 0.3, so multiplying by ten lands just above 3 and ceil() dutifully returns 4. The same thing happens with money and two decimal places:
import math
x = 0.1 + 0.2
print(x) # not exactly 0.3
print(math.ceil(x * 10)) # 4, although 0.3 * 10 should be 3
print(1.1 * 3, math.ceil(1.1 * 3))
# fix it by rounding away the noise first, or by not using floats at all
print(math.ceil(round(x * 10, 9)))
from decimal import Decimal, ROUND_CEILING
price = Decimal("12.341")
print(price.quantize(Decimal("0.01"), rounding=ROUND_CEILING)) # always up to the next cent
Output:
0.30000000000000004
4
3.3000000000000003 4
3
12.35

Decimal where the cents have to be exact.For prices, Decimal with ROUND_CEILING is the honest tool: it rounds up to the next cent without a float ever being involved.
ceil() for NumPy arrays and pandas columns
math.ceil() takes a single number, so passing an array raises a TypeError. NumPy has its own np.ceil(), which works element by element and keeps the float dtype, so add .astype(int) when you want whole numbers. In pandas the same function applies to a whole column, which is how float columns become integer counts:
import math
import numpy as np
import pandas as pd
hours = np.array([1.2, 3.5, -0.4])
print(np.ceil(hours), np.ceil(hours).dtype) # NumPy keeps floats
print(np.ceil(hours).astype(int)) # convert if you want ints
try:
math.ceil(hours) # math.ceil takes one number
except TypeError as error:
print("TypeError:", error)
timesheet = pd.DataFrame({"consultant": ["Emma", "Michael", "Olivia"],
"hours": [1.2, 3.5, 0.1]})
timesheet["billed"] = np.ceil(timesheet["hours"]).astype(int)
print(timesheet.to_string(index=False))
Output:
[ 2. 4. -0.] float64
[2 4 0]
TypeError: only 0-dimensional arrays can be converted to Python scalars
consultant hours billed
Emma 1.2 2
Michael 3.5 4
Olivia 0.1 1

np.ceil() on a column turns 1.2 and 3.5 hours into 2 and 4 billed hours.Round up to a number of decimal places
ceil() only rounds to whole numbers, so to round up to two decimals you scale the value, take the ceiling, and scale back. That works for display values; for anything you will bill someone for, do the same job with Decimal so no float ever touches the cents:
import math
from decimal import Decimal, ROUND_CEILING
def ceil_to(value: float, digits: int = 0) -> float:
"""Round up to a fixed number of decimal places."""
factor = 10 ** digits
return math.ceil(value * factor) / factor
print(ceil_to(12.341, 2), ceil_to(12.3401, 2), ceil_to(0.0001, 2))
print(ceil_to(-12.341, 2))
print(ceil_to(7.2)) # no digits: a whole number, as a float
def ceil_money(value: str, digits: int = 2) -> Decimal:
"""The same job for prices, without floats."""
step = Decimal(1).scaleb(-digits) # 0.01 for 2 digits
return Decimal(value).quantize(step, rounding=ROUND_CEILING)
print(ceil_money("12.341"), ceil_money("12.340"), ceil_money("-12.341"))
Output:
12.35 12.35 0.01
-12.34
8.0
12.35 12.34 -12.34
Notice that the Decimal version of -12.341 rounds up to -12.34, towards zero, because ceiling always means towards positive infinity.
Errors from math.ceil()
Three inputs fail, each with its own exception, and the messages are worth recognising:
import math
for value in (float("inf"), float("nan"), "3.2", None):
try:
math.ceil(value)
except Exception as error:
print(f"{value!r:>6} -> {type(error).__name__}: {error}"