New to Rust? Grab our free Rust for Beginners eBook Get it free →
Python rounding: round(), math, NumPy and Decimal compared

Python rounding looks simple until round(2.5) returns 2 instead of 3, or round(2.675, 2) hands back 2.67 when you expected 2.68. Those are not bugs. They come from how Python breaks ties and how floats get stored in binary. This guide covers the round() function with precision control, math.floor() and math.ceil(), NumPy’s array-based rounding and the decimal module for the cases where floats are not precise enough.
The round() function in Python
round() is Python’s built-in function for rounding a number to the nearest whole value or to a set number of decimal places. It takes the number itself plus an optional second argument that controls precision.
Leave that second argument out and round() hands back an int. Pass a second argument and you get a float back, even when the result looks like a whole number.
That return-type switch trips up beginners more than the rounding logic itself, especially coming from languages where input type and output type always match. Python’s numeric types work a bit differently here, and it pays to know which one you are holding before you compare or store the result.
- number: The value to round. Works with both int and float input.
- ndigits: Optional. The number of decimal places to round to. Leave it out and round() rounds to the nearest whole number. Pass a negative value to round to the left of the decimal point instead.
print(round(3.14159)) # 3
print(round(3.14159, 2)) # 3.14
print(type(round(3.14159))) # <class 'int'>
print(type(round(3.14, 1))) # <class 'float'>
Rounding to a set number of decimal places
Pass a positive ndigits to round to that many decimal places, or a negative one to round to the nearest ten, hundred or thousand instead. This second case comes up more than people expect, mostly when you want to bucket numbers for a report rather than keep exact precision.
price = 19.987
print(round(price, 2)) # 19.99
population = 1247
print(round(population, -2)) # 1200
Banker’s rounding: why round(2.5) equals 2
Python’s round() uses round half to even, also called banker’s rounding. When a number sits exactly halfway between two values, it rounds to whichever neighbor is even rather than always rounding up. That is why round(2.5) gives 2 and round(3.5) gives 4.
print(round(0.5)) # 0
print(round(1.5)) # 2
print(round(2.5)) # 2
print(round(3.5)) # 4
The reasoning is not arbitrary. Rounding every tie upward biases a large dataset toward larger totals over thousands of operations. Rounding ties to even cancels that drift out on average, which is why the same rule shows up in NumPy and in the decimal module you will meet further down.
The floating-point precision problem
Here is the one that catches almost everyone at some point.
print(round(2.675, 2)) # 2.67, not 2.68
That is not banker’s rounding at work. The number 2.675 cannot be stored exactly in binary floating point, so Python actually stores something fractionally below 2.675.
round() rounds that stored value correctly. It just is not the decimal value you typed. Checking 2.675 == 2.6749999999999998 would confirm the gap if you tried it in a REPL.
For everyday display rounding this rarely matters. It matters a lot once money or scientific measurements are on the line, which is exactly the gap the decimal module closes later in this guide.
Rounding down and up with math.floor() and math.ceil()
The math module ships with Python and gives you two functions that never round to the nearest value. They only round in one direction.
math.floor() always rounds down toward negative infinity. math.ceil() always rounds up toward positive infinity, no matter how close the number already is to the next integer.
That makes them useful any time you need a guaranteed lower or upper bound rather than the closest value, such as calculating how many full pages a set of records needs or how many buses a group of people needs. The math module covers a lot of other ground worth knowing too, from trigonometry to angle conversion.
import math
print(math.floor(4.7)) # 4
print(math.ceil(4.3)) # 5
print(math.floor(4.0)) # 4
print(math.ceil(4.0)) # 4
floor() and ceil() with negative numbers
Negative numbers are where floor() and ceil() surprise people the most, because “down” always means toward negative infinity, not toward zero.
print(math.floor(-4.3)) # -5
print(math.ceil(-4.3)) # -4
math.floor(-4.3) returns -5, not -4, because -5 is the smaller of the two neighboring integers. If you want a number that always moves toward zero instead, use math.trunc() rather than floor() or ceil(). Pairing floor() or ceil() with an absolute value function is a common pattern when you care about magnitude rather than direction.
Rounding a single float at a time with round() or math.floor() works fine until you have thousands of values sitting in a list or a data file. Looping over each one and calling round() individually gets slow and verbose fast.
That is the gap NumPy fills. It applies the same rounding logic to an entire array in one call instead of one value at a time.
Rounding numbers using NumPy
1. The round() function
The round() changes a number or a group of numbers to the nearest whole number or a certain number of decimal places.
The basic syntax looks like this:
np.round(array, decimals)
Here,
- array: This is the input array or number that you want to round.
- decimals: This allows you to choose how many numbers after the decimal point you want to round to. If you don’t say, it will round to the nearest whole number.
How it rounds:
If the decimal part of a number is less than 0.5, it rounds down. If the decimal part is 0.5 or greater, it rounds up.

Let’s use this in the code:
import numpy as np
a_input = input("Enter the decimal numbers with separating spaces: ")
arr = np.array([float(x) for x in a_input.split()])
d_places = int(input("Enter the number of decimal places to round to: "))
rounded_arr = np.round(arr, d_places)
print("Original array:", arr)
print("Rounded array:", rounded_arr)
Working:
- It asks the user to enter decimal numbers separated by spaces.
- It takes the input, splits it by spaces, converts each part to a floating-point number, and creates an array from those numbers.
- Then, it asks the user how many decimal places to round to.
- It rounds each number in the array to the specified number of decimal places.
- Finally, it prints both the original array and the rounded array.
Output:

2. The floor() function
The floor() is another function in the NumPy library which helps to round numbers down to the nearest whole number or integer.
The syntax form is like this:
np.floor(array)
Here,
- array is the input data.
How it works:
It always drops the decimal part and rounds to the lower whole number. In the case of positive numbers, it cuts off the decimal part and makes it the smaller whole number. For negative numbers, it moves towards zero and becomes the closest smaller whole number.

Let’s relate with code:
import numpy as np
a_input = input("Enter the decimal numbers with separating spaces: ")
arr = np.array([float(x) for x in a_input.split()])
rounded_arr = np.floor(arr)
print("Original array:", arr)
print("Rounded array:", rounded_arr)
This code does these:
- Asks for decimal numbers separated by spaces.
- Converts them into floating-point numbers and makes an array.
- Round down each number to the nearest whole number.
- Prints the original array and the rounded array.
Output:

3. The ceil() function
The ceil() function takes a number or an array of numbers as input and rounds them up to the nearest integer that is greater than or equal to the original number.
Its syntax is as follows:
np.ceil(array)
Here,
- array is the input data.
How it functions:
It always goes to the next higher whole number, ignoring the decimal part. For positive numbers, it rounds up. For negative numbers, it goes to the next integer farther from zero.

Let’s implement with a code:
import numpy as np
a_input = input("Enter the decimal numbers with separating spaces: ")
arr = np.array([float(x) for x in a_input.split()])
rounded_arr = np.ceil(arr)
print("Original array:", arr)
print("Rounded array:", rounded_arr)
The working of this code is similar to the above two, except it rounds up each number to the nearest whole number.
Output:

The ceil() and floor() functions can be quite confusing, right? Don’t worry, as we have a trick to help you get it right!

4. The trunc() function
The trunc() function takes a number or a group of numbers and chops off the decimal part, leaving only the integer part.
The basic syntax is as follows with an array as the input data:
np.trunc(array)
How it performs:
It just cuts off the decimal part of the number without rounding. Always goes toward zero. For positives, it keeps the integer part. For negatives, it keeps the integer part and the negative sign.

Let’s use this in the code:
import numpy as np
a_input = input("Enter the decimal numbers with separating spaces: ")
arr = np.array([float(x) for x in a_input.split()])
rounded_arr = np.trunc(arr)
print("Original array:", arr)
print("Rounded array:", rounded_arr)
This code takes decimal numbers as input, turns them into an array, and then removes the decimal part to keep only the integer part using trunc().
Output:

5. The around() function
The around() rounds the input to a specified number of decimals. It’s like round() in Python but gives more control over how precise the rounding is.
The syntax for this is:
np.around(array, decimals)
Here,
- array is the input data,
- decimals are the specified limit to which it needs to be rounded.
How it works:
It rounds the input number to the nearest value with the specified number of decimals. If the decimal part is exactly halfway between two rounded values, around() rounds to the nearest even value for a more balanced approach to rounding.

Let’s implement this in the code:
import numpy as np
a_input = input("Enter the decimal numbers with separating spaces: ")
arr = np.array([float(x) for x in a_input.split()])
rounded_arr = np.around(arr, 0)
print("Original array:", arr)
print("Rounded array:", rounded_arr)
This code takes decimal numbers as input, makes them into an array, and rounds each number to the nearest whole number using around().
Output:

The decimal module for precise rounding
Floats are fast, but they are not exact. round() inherits that gap, as you saw earlier with 2.675.
For financial calculations, tax math or anything where a fraction of a cent compounding across thousands of rows actually matters, Python’s built-in decimal module stores numbers exactly instead of approximating them in binary.
Creating and rounding Decimal values
Build a Decimal from a string, not a float, or you carry the same floating-point error straight into it.
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("2.675")
print(price.quantize(Decimal("0.01"))) # 2.68
print(price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)) # 2.68
quantize() rounds a Decimal to match the pattern of a second Decimal you pass in. Decimal(“0.01”) means “keep two decimal places.”
Because Decimal(“2.675”) stores the exact value 2.675 rather than a binary approximation, quantize() rounds it the way you would expect on paper.
Setting precision and rounding mode
The decimal module also lets you change how ties get broken globally, through getcontext(). ROUND_HALF_UP always rounds a tie away from zero, ROUND_HALF_EVEN matches round()’s banker’s rounding and ROUND_DOWN truncates toward zero. Pick whichever one your business rule actually requires instead of assuming Python’s default fits every case.
import decimal
decimal.getcontext().prec = 6
decimal.getcontext().rounding = decimal.ROUND_HALF_UP
round() vs math vs NumPy vs Decimal: which one to use
Each tool solves a different rounding problem, and picking the wrong one is usually where rounding bugs sneak into a codebase.
| Tool | Best for | Tie-breaking rule | Typical input |
|---|---|---|---|
| round() | Quick rounding of a single number for display | Half to even | int or float |
| math.floor() / math.ceil() | Guaranteed lower or upper bound | Not applicable, always one direction | int or float |
| NumPy round() / floor() / ceil() | Rounding thousands of values at once | Half to even | array |
| decimal.Decimal | Money, tax and anything needing exact precision | Configurable, defaults to half to even | string or Decimal |
For everyday display formatting, round() is enough.
Once you are working across a full column of data rather than one value, reach for NumPy’s vectorized functions instead of looping. The same logic applies whether you are grouping and aggregating pandas data or cleaning numbers straight after reading an Excel file into pandas.
Reach for decimal.Decimal the moment a rounding error would show up on an invoice or a balance sheet. That is the one situation where “close enough” genuinely is not.
Key takeaways
- round() rounds to the nearest whole number or decimal place, using round half to even for ties.
- Leaving out round()’s second argument returns an int. Passing one returns a float.
- round(2.675, 2) returns 2.67 because of floating-point storage, not a rounding bug.
- math.floor() and math.ceil() always round in one direction, unlike round().
- floor() and ceil() round toward negative and positive infinity, not toward zero.
- NumPy’s round(), floor(), ceil(), trunc() and around() apply the same logic across arrays.
- Use decimal.Decimal, built from strings, for money and other exact-precision work.
Frequently asked questions
Why does round(2.5) return 2 instead of 3 in Python?
Python’s round() uses round half to even, also called banker’s rounding. Ties round to whichever neighboring integer is even, so round(2.5) becomes 2 instead of 3.
Why does round(2.675, 2) return 2.67 and not 2.68?
The number 2.675 cannot be stored exactly in binary floating point, so Python stores a value fractionally below it. round() correctly rounds that stored value.
What is the difference between round() and math.floor() in Python?
round() rounds to the nearest value, up or down. math.floor() always rounds down toward negative infinity, no matter how close the input is to the next integer.
How do I round a number to two decimal places in Python?
Pass 2 as the second argument to round(), for example round(19.987, 2), which returns 19.99. Use decimal.Decimal with quantize() instead where precision matters.
How do I round numbers accurately for money in Python?
Build a decimal.Decimal from a string rather than a float, then call quantize() with a rounding mode such as ROUND_HALF_UP. This avoids floating-point storage errors.
Can NumPy round an entire array at once?
Yes. np.round(array, decimals) rounds every value in a NumPy array in one call, using the same round half to even rule as Python’s built-in round().
What does math.ceil() return for negative numbers?
math.ceil() always rounds toward positive infinity, so math.ceil(-4.3) returns -4, not -5. “Rounding up” a negative number moves it closer to zero, not further away.
Conclusion
Most rounding bugs in Python come down to picking the wrong tool for the job rather than a flaw in any one of them. round() is fine for display, math.floor() and math.ceil() give you a guaranteed direction, NumPy handles whole arrays at once and Decimal is there the moment exact precision actually matters.




