Python - round() Function with Examples
The `round()` function is one of Python's built-in functions for handling numeric precision. It rounds a floating-point number to a specified number of decimal places, or to the nearest integer when...
Key Insights
- Python’s
round()function uses banker’s rounding (round half to even), which can produce unexpected results likeround(2.5)returning2instead of3 - Floating-point representation issues mean
round(2.675, 2)returns2.67instead of2.68—use thedecimalmodule for financial calculations - Negative
ndigitsvalues let you round to tens, hundreds, or thousands, makinground()useful for data summarization and reporting
Introduction to round()
The round() function is one of Python’s built-in functions for handling numeric precision. It rounds a floating-point number to a specified number of decimal places, or to the nearest integer when no precision is specified.
While the concept sounds simple, round() has behaviors that catch even experienced developers off guard. Understanding these nuances prevents bugs in financial calculations, data processing, and anywhere precision matters.
# Basic syntax
round(number) # Round to nearest integer
round(number, ndigits) # Round to ndigits decimal places
# Quick examples
print(round(3.7)) # Output: 4
print(round(3.14159, 2)) # Output: 3.14
Syntax and Parameters
The round() function accepts two parameters:
round(number, ndigits=None)
Parameters:
number(required): The numeric value to round. Must be a number (int or float).ndigits(optional): The number of decimal places to round to. Defaults toNone.
Return type behavior:
The return type depends on whether you provide ndigits:
# Without ndigits: returns int
result = round(3.7)
print(result) # Output: 4
print(type(result)) # Output: <class 'int'>
# With ndigits (even 0): returns float
result = round(3.7, 0)
print(result) # Output: 4.0
print(type(result)) # Output: <class 'float'>
# With positive ndigits: returns float
result = round(3.14159, 2)
print(result) # Output: 3.14
print(type(result)) # Output: <class 'float'>
# Rounding an integer with ndigits: returns int
result = round(10, 2)
print(result) # Output: 10
print(type(result)) # Output: <class 'int'>
This distinction matters when you’re serializing data or performing type-sensitive operations.
Basic Usage Examples
Let’s walk through common rounding scenarios:
Rounding to Whole Numbers
# Rounding down (when decimal < 0.5)
print(round(3.2)) # Output: 3
print(round(3.4)) # Output: 3
# Rounding up (when decimal > 0.5)
print(round(3.6)) # Output: 4
print(round(3.9)) # Output: 4
# Negative numbers follow the same rules
print(round(-3.2)) # Output: -3
print(round(-3.7)) # Output: -4
Rounding to Specific Decimal Places
# Round to 2 decimal places (common for currency display)
price = 19.995
print(round(price, 2)) # Output: 20.0
# Round to 3 decimal places
pi = 3.14159265359
print(round(pi, 3)) # Output: 3.142
# Round to 1 decimal place
temperature = 98.67
print(round(temperature, 1)) # Output: 98.7
# More decimal places than the number has
print(round(3.5, 5)) # Output: 3.5 (no padding)
Practical Example: Formatting Prices
def format_price(amount):
"""Round and format a price for display."""
rounded = round(amount, 2)
return f"${rounded:.2f}"
prices = [19.999, 5.5, 100.004, 49.995]
for price in prices:
print(f"{price} -> {format_price(price)}")
# Output:
# 19.999 -> $20.00
# 5.5 -> $5.50
# 100.004 -> $100.00
# 49.995 -> $50.00
Banker’s Rounding Behavior
Here’s where Python’s round() surprises most developers. When a number is exactly halfway between two possible rounded values, Python uses “round half to even” (also called banker’s rounding):
# The surprise: 0.5 rounds to 0, not 1
print(round(0.5)) # Output: 0
print(round(1.5)) # Output: 2
print(round(2.5)) # Output: 2
print(round(3.5)) # Output: 4
print(round(4.5)) # Output: 4
Notice the pattern: halfway values round to the nearest even number.
Why does Python do this?
Traditional rounding (always round 0.5 up) introduces systematic bias. If you’re averaging many rounded numbers, this bias accumulates. Banker’s rounding distributes the rounding equally between up and down, reducing cumulative error.
# Demonstrating bias reduction
import statistics
# Numbers that are exactly halfway
halfway_values = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]
# True average
true_avg = statistics.mean(halfway_values)
print(f"True average: {true_avg}") # Output: 5.0
# Average after banker's rounding
bankers_rounded = [round(x) for x in halfway_values]
print(f"Banker's rounded: {bankers_rounded}") # [0, 2, 2, 4, 4, 6, 6, 8, 8, 10]
bankers_avg = statistics.mean(bankers_rounded)
print(f"Banker's average: {bankers_avg}") # Output: 5.0
# If we always rounded 0.5 up (traditional)
traditional_rounded = [0, 2, 3, 4, 5, 6, 7, 8, 9, 10] # What traditional would give
traditional_avg = statistics.mean(traditional_rounded)
print(f"Traditional average: {traditional_avg}") # Output: 5.4 (biased!)
When you need traditional rounding:
import math
def round_half_up(x, decimals=0):
"""Traditional rounding: 0.5 always rounds up."""
multiplier = 10 ** decimals
return math.floor(x * multiplier + 0.5) / multiplier
print(round_half_up(2.5)) # Output: 3.0
print(round_half_up(3.5)) # Output: 4.0
print(round_half_up(2.25, 1)) # Output: 2.3
Negative ndigits Values
A lesser-known feature: negative ndigits values round to the left of the decimal point.
# Round to nearest 10
print(round(1234, -1)) # Output: 1230
print(round(1235, -1)) # Output: 1240
print(round(1236, -1)) # Output: 1240
# Round to nearest 100
print(round(1234, -2)) # Output: 1200
print(round(1250, -2)) # Output: 1200 # Banker's rounding!
print(round(1251, -2)) # Output: 1300
# Round to nearest 1000
print(round(1234, -3)) # Output: 1000
print(round(1500, -3)) # Output: 2000 # Banker's rounding: 1500 -> 2000 (even)
print(round(2500, -3)) # Output: 2000 # Banker's rounding: 2500 -> 2000 (even)
Practical use case: Data summarization
def summarize_population(population):
"""Round population for readable reporting."""
if population >= 1_000_000:
return f"{round(population, -6) // 1_000_000}M"
elif population >= 1_000:
return f"{round(population, -3) // 1_000}K"
else:
return str(population)
cities = [
("New York", 8_336_817),
("Austin", 978_908),
("Boulder", 105_485),
("Small Town", 847),
]
for city, pop in cities:
print(f"{city}: {summarize_population(pop)}")
# Output:
# New York: 8M
# Austin: 979K
# Boulder: 105K
# Small Town: 847
Common Pitfalls and Edge Cases
Floating-Point Precision Issues
This is the most dangerous pitfall:
# You'd expect 2.68, but...
print(round(2.675, 2)) # Output: 2.67
# Why? 2.675 can't be represented exactly in binary floating-point
print(f"{2.675:.20f}") # Output: 2.67499999999999982236
The number 2.675 is stored as approximately 2.6749999... in binary floating-point, so it rounds down.
None vs 0 for ndigits
# None returns int
result = round(3.7, None)
print(result, type(result)) # Output: 4 <class 'int'>
# 0 returns float
result = round(3.7, 0)
print(result, type(result)) # Output: 4.0 <class 'float'>
Error Handling
def safe_round(value, ndigits=None):
"""Safely round a value, handling edge cases."""
try:
if value is None:
return None
return round(float(value), ndigits)
except (TypeError, ValueError) as e:
raise ValueError(f"Cannot round {value!r}: {e}")
# Usage
print(safe_round("3.14", 1)) # Output: 3.1
print(safe_round(None)) # Output: None
try:
safe_round("not a number")
except ValueError as e:
print(e) # Output: Cannot round 'not a number': could not convert...
Alternatives and When to Use Them
math.floor() and math.ceil()
When you always want to round in one direction:
import math
value = 3.7
print(math.floor(value)) # Output: 3 (always down)
print(math.ceil(value)) # Output: 4 (always up)
print(round(value)) # Output: 4 (to nearest)
# For negative numbers
value = -3.7
print(math.floor(value)) # Output: -4 (toward negative infinity)
print(math.ceil(value)) # Output: -3 (toward positive infinity)
decimal.Decimal for Financial Calculations
For money, use Decimal. Always.
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN
# The problem with float
price = 2.675
print(round(price, 2)) # Output: 2.67 (wrong!)
# The solution with Decimal
price = Decimal("2.675")
print(price.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)) # Output: 2.68
# Different rounding modes
value = Decimal("2.5")
print(value.quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # Output: 3
print(value.quantize(Decimal("1"), rounding=ROUND_HALF_EVEN)) # Output: 2
# Practical financial calculation
def calculate_total(items):
"""Calculate order total with proper decimal handling."""
total = Decimal("0")
for price, quantity in items:
item_total = Decimal(str(price)) * quantity
total += item_total
return total.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
order = [(19.99, 2), (5.50, 3), (2.675, 4)]
print(f"Total: ${calculate_total(order)}") # Output: Total: $67.18
Quick Reference: Which to Use
| Scenario | Use |
|---|---|
| Display formatting | round() |
| Financial calculations | decimal.Decimal |
| Always round down | math.floor() |
| Always round up | math.ceil() |
| Truncate decimals | int() or math.trunc() |
| Scientific data | round() (banker’s rounding reduces bias) |
The round() function handles most rounding needs, but knowing its quirks—especially banker’s rounding and floating-point precision—prevents subtle bugs that are painful to track down.