Python Float Rounding: How to Round Correctly
python float rounding: Understand how Python rounds floats, why round() behaves unexpectedly, and when to use Decimal or formatting for precise control.
When you write round(2.675, 2) in Python, you get 2.67, not 2.68. That result surprises many developers, but it follows directly from how floating-point numbers are stored in binary. The decimal value 2.675 cannot be represented exactly as a binary fraction, so the underlying float is slightly less than 2.675. The round() function then rounds that imprecise value, producing 2.67. This is the first thing to understand about python float rounding: the input to round() is already an approximation, so the output reflects that approximation, not the decimal you typed.
How the Built-in round() Actually Works
The built-in round() function follows IEEE 754 round-half-to-even, often called banker's rounding. For a number exactly halfway between two candidates, it rounds to the nearest even digit. For example, round(2.5) returns 2, and round(3.5) returns 4. This behavior is intentional and reduces cumulative bias in statistical calculations, but it can be unexpected in financial or display contexts where you expect round-half-up.
print(round(2.5)) # 2 print(round(3.5)) # 4 print(round(2.675, 2)) # 2.67 (due to binary representation)
The second argument to round() is the number of decimal digits. When it is negative, rounding applies to tens, hundreds, and so on: round(1234, -2) gives 1200. The function returns an integer when called with no ndigits, but a float when ndigits is provided, even if the result could be an integer.
Formatting Floats for Display: A Different Rounding Path
String formatting with f-strings or the format() method performs rounding for display purposes, but it does not change the underlying value. The format spec :.2f rounds to two decimal places using the same binary representation, so it can also produce surprising results. However, formatting always returns a string, which is often exactly what you need for output.
value = 2.675 print(f"{value:.2f}") # 2.67 print(format(value, ".2f")) # 2.67
Formatting uses round-half-even as well, but because it operates on the binary float, the result is consistent with round(). For display, this is usually acceptable, but if you need to round a value before storing it or using it in further calculations, formatting alone is insufficient.
Using decimal.Decimal for Exact Decimal Arithmetic
When you need predictable decimal rounding, the decimal module provides exact representation of decimal numbers and full control over rounding modes. You create a Decimal from a string or integer, never from a float, because converting a float to Decimal preserves the binary approximation. The quantize() method rounds to a specified exponent and accepts a rounding mode.
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_EVEN value = Decimal("2.675") rounded = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) print(rounded) # 2.68
ROUND_HALF_UP rounds ties away from zero, which matches the intuitive rounding taught in most contexts. Other modes include ROUND_CEILING, ROUND_FLOOR, ROUND_DOWN, and ROUND_UP. The Decimal context also sets precision and rounding globally, but quantize() with an explicit mode is the clearest way to control a single operation.
Comparing Rounding Approaches
The following table summarizes the main options for python float rounding, their output types, and when each is appropriate.
| Method | Output type | Rounding rule | Best for |
|---|---|---|---|
round(x, n) | float/int | Round half to even | General numeric work, statistics |
f-string / format() | string | Round half to even | Display and reporting |
Decimal.quantize() | Decimal | Configurable | Financial calculations, exact decimals |
math.floor() / ceil() | int | Toward/away from zero | Integer rounding in specific directions |
math.floor() and math.ceil() are not rounding in the decimal sense, but they are often used when you need to force a value down or up to the nearest integer. They do not accept a number of digits, so they are limited to integer results.
Precision and Performance Tradeoffs
Using Decimal is slower than using native floats because decimal arithmetic is implemented in software, not hardware. For a single rounding operation the difference is negligible, but in a tight loop processing millions of values, Decimal can become a bottleneck. If you only need to round for display, formatting is the fastest approach because it avoids creating a Decimal object. If you need exact decimal behavior in financial calculations, the performance cost of Decimal is usually acceptable compared to the risk of incorrect rounding.
Another performance consideration is the cost of creating a Decimal from a string. If you are parsing user input, you already have a string, so conversion is natural. But if you are working with floats from a sensor or computation, converting to Decimal requires a string representation, which adds overhead. In such cases, decide whether the rounding error is acceptable for your domain or whether you need the exactness.
Common Pitfalls in Float Rounding
One frequent mistake is assuming round() always rounds half up. The banker's rounding behavior is correct for many statistical applications, but it can cause off-by-one errors in financial reports. Another pitfall is chaining rounding operations: rounding an intermediate result and then rounding again can introduce more error than rounding once at the end. For example, round(round(2.675, 2), 1) gives 2.7, while round(2.675, 1) gives 2.7 as well, but with other numbers the results can diverge.
Accumulated floating-point error is another issue. Adding many small floats can produce a sum that is slightly off, and rounding that sum may not give the expected result. The math.fsum() function provides an accurate sum, but it still returns a float. If you need exact decimal sums, use Decimal throughout the calculation.
Choosing the Right Rounding Strategy
Selecting the correct approach depends on the context. For display purposes, use f-string formatting because it is concise and does not alter the original value. For general numeric rounding where a small bias is acceptable, round() is fine. For financial calculations, legal reporting, or any situation where the exact decimal value matters, use Decimal with an explicit rounding mode. If you are rounding to an integer and need to control the direction, use math.floor() or math.ceil().
The key is to understand that python float rounding is not a single operation with one universal behavior. The binary representation of floats makes exact decimal rounding impossible without a decimal type. By choosing the right tool for each situation, you avoid the most common rounding errors and keep your code predictable and maintainable.