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 like round(2.5) returning 2 instead of 3
  • Floating-point representation issues mean round(2.675, 2) returns 2.67 instead of 2.68—use the decimal module for financial calculations
  • Negative ndigits values let you round to tens, hundreds, or thousands, making round() 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 to None.

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.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.