# How to round numbers in Python

Working with numbers is a significant part of programming, especially when it comes to scientific applications or handling monetary values.

Here, the need to round numbers in Python becomes essential. It allows us to adjust a number to a nearby value, usually to decrease its complexity or to fit it into a certain precision. Let’s dive into how we can perform this in Python.

## Using Built-in round() Function

Python’s built-in `round()` function is the easiest way to round numbers. It always rounds a number to the nearest even number when exactly halfway between two integers.
Let’s see how this function works with integers, floats, and complex numbers.

```# Integer
num = 5
print(round(num))

# Float
num = 3.14159
print(round(num))

# Complex number
num = 3.5 + 4.5j
try:
print(round(num))
except TypeError as e:
print(e)
```

Output:

```5
3
type complex doesn't define __round__ method
```

The first example returns 5 as it’s already an integer. In the second example, the floating-point number 3.14159 is rounded down to the nearest whole integer, which is 3. The last example results in a TypeError because the `round()` function doesn’t support complex numbers.

## Rounding Negative Numbers to Nearest Integer

The `round()` function can also work with negative numbers, rounding them to the nearest integer just as it does with positive numbers.
Here’s an example:

```num = -3.6
print(round(num))
```

Output:

```-4
```

As you can see, the negative number -3.6 is rounded to -4, each moving towards the nearest whole integer on the number line.

## Rounding to a Specific Decimal Place

The `round()` function can also take an additional argument to indicate the number of places to which you want the number rounded.

The second argument specifies how many decimal places you want.
Here’s how it works:

```num = 3.14159

# Round to 1 decimal place
print(round(num, 1))

# Round to 2 decimal places
print(round(num, 2))

# Round to 3 decimal places
print(round(num, 3))
```

Output:

```3.1
3.14
3.142
```

In the first case, the number is rounded to the nearest number with 1 decimal place, which is 3.1. In the second case, it’s rounded to the nearest number with 2 decimal places, which is 3.14.

And in the last case, it’s rounded to the nearest number with 3 decimal places, which is 3.142.

## Rounding with math.floor()

The `math.floor()` function in Python rounds down a number to the nearest integer, moving to the left on the number line. This means it always rounds a number down to the lesser of the two numbers around it.
Let’s illustrate this:

```import math

# Positive number
num = 3.6
print(math.floor(num))

# Negative number
num = -3.6
print(math.floor(num))
```

Output:

```3
-4
```

Here, 3.6 is rounded down to 3, the nearest integer less than it. However, for -3.6, the function rounds to -4, as -4 is to the left of -3.6 on the number line.

## Rounding with math.ceil()

The `math.ceil()` function in Python is the counterpart of `math.floor()`. It rounds up a number to the nearest integer, shifting the number to the right on the number line.

This means it always rounds a number up to the larger of the two numbers around it.
Here’s an example:

```import math

# Positive number
num = 3.6
print(math.ceil(num))

# Negative number
num = -3.6
print(math.ceil(num))
```

Output:

```4
-3
```

In the above example, 3.6 is rounded up to 4, the nearest integer larger than it. However, for -3.6, the function rounds to -3, as -3 is to the right of -3.6 on the number line.

## Creating a custom round function

In Python, you have the ability to dictate the rounding behavior of a class by defining a special method named `__round__`.

This method will be invoked when the built-in `round()` function is used on instances of your class.

For illustration, let’s create a class, `CustomRoundNumber`, that will round a number towards zero, a method that is also known as truncation. This is a distinct approach from the standard Python’s rounding behavior:

```class CustomRoundNumber:
def __init__(self, value):
self.value = value

def __round__(self, n=None):
# if no precision is given, round to the nearest integer
if n is None:
return int(self.value)

# if precision is given, shift the decimal point to the right by the desired number of places
multiplier = 10 ** n
value = self.value * multiplier

# Round towards zero
result = int(value)
return result / multiplier
num = CustomRoundNumber(1.6789)
print(round(num, 2))  # 1.67
print(round(num))  # 1
```

Output:

```1.67
1```

In the provided code, we’ve defined a class `CustomRoundNumber` with a `__round__` method. This method is tasked with implementing the round towards zero strategy.

Hence, when `round()` is called on an instance of this class, the `__round__` method is invoked to round towards zero.

## floating point representation and limitations

While rounding can be very useful, it’s important to understand that floating-point numbers in Python (and most programming languages) have limitations due to how they’re represented in binary.

This is due to the fact that floating-point numbers can’t be precisely represented as binary fractions. In base 2, 1/10 is an infinitely repeating fraction.

So, in base 2, 1/10 becomes 0.0001100110011001100110011001100110011001100110011… and so on. Hence, the precision gets lost and can’t be regained.

This isn’t a bug, but rather, it’s a result of a fundamental property of computers, and it’s an issue that everyone who uses floating-point numbers needs to be aware of.

However, we’ll handle this issue using the Decimal type later in this tutorial.
Let’s see an example:

```num = 0.1 + 0.1 + 0.1
print(num == 0.3)

# Correctly rounded version
num_rounded = round(num, 2)
print(num_rounded == 0.3)
```

Output:

```False
True
```

Here, adding three instances of 0.1 gives a number that isn’t exactly 0.3 due to the imprecise binary representation of 0.1 Actually, it’s 0.30000000000000004.

However, when we round the result to two decimal places, the comparison with 0.3 returns True.

## Using decimal module

The `quantize()` method from the `decimal` module rounds a number to a fixed amount of decimal places according to a given rounding strategy:

```from decimal import *
num = Decimal('0.555')

# Define the decimal places
decimal_places = Decimal('0.00')

# Round towards infinity
print(num.quantize(decimal_places, rounding=ROUND_CEILING))

# Round towards zero
print(num.quantize(decimal_places, rounding=ROUND_DOWN))

# Round towards negative infinity
print(num.quantize(decimal_places, rounding=ROUND_FLOOR))

# Round towards nearest, ties go towards zero
print(num.quantize(decimal_places, rounding=ROUND_HALF_DOWN))

# Round towards nearest, ties go to nearest even number
print(num.quantize(decimal_places, rounding=ROUND_HALF_EVEN))

# Round towards nearest, ties go away from zero
print(num.quantize(decimal_places, rounding=ROUND_HALF_UP))

# Round away from zero
print(num.quantize(decimal_places, rounding=ROUND_UP))
```

Output:

```0.56
0.55
0.55
0.55
0.56
0.56
0.56
```

In this example, the `quantize()` method rounds the decimal number `num` to two decimal places using the specified rounding modes. As you can see, different rounding strategies produce different results.

## Python Decimal type for handling precision

The `Decimal` type provided by the `decimal` module represents decimal floating-point numbers with a user-definable precision.

This can help solve precision issues that arise with the standard floating-point representation.
`Decimal` can precisely represent numbers like 0.1, which cannot be exactly represented as a binary floating-point number.

Furthermore, `Decimal` allows you to control the precision and rounding of the number to match your needs.
Here’s an example of how Decimal can maintain precision:

```from decimal import Decimal

# Using float
num_float = 0.1 + 0.1 + 0.1
print(num_float)

# Using Decimal
num_decimal = Decimal('0.1') + Decimal('0.1') + Decimal('0.1')
print(num_decimal)
```

Output:

```0.30000000000000004
0.3
```

As shown above, using `Decimal` can prevent the precision errors that can occur with floating-point numbers.

## Python 2 and Python 3 round() Functions

In Python 2, the `round()` function follows the traditional round half away from zero strategy – for instance, `round(0.5)` and `round(-0.5)` both result in `1.0` and `-1.0` respectively.
On the other hand, Python 3 uses the round half to even strategy (also known as Banker’s rounding) by default, where half-way values are rounded to the nearest even number.

So, in Python 3, `round(0.5)` results in `0` and `round(1.5)` in `2`.
This difference can lead to unexpected results when moving code from Python 2 to Python 3.

## When to use each rounding method

Choosing the right rounding method can be critical in certain applications. Here’s a general guideline:

1. Use built-in `round()`: For general use cases, especially when you need to round to the nearest even number (Banker’s rounding).
2. Use `math.floor()` and `math.ceil()`: When you need to always round down (towards negative infinity) or up (towards positive infinity), regardless of the fractional part.
3. Use custom rounding functions: When you need specific behavior, like always rounding towards or away from zero, or any custom rounding.
4. Use the `decimal` module: When precision is crucial, like in financial or scientific applications. This module also allows you to define the precision and use different rounding strategies through the `quantize()` method.

It’s important to understand your needs and choose the rounding method that best suits your use case.

## Float Bug Fixed with Decimal

I was assigned to investigate a bug in the financial module of the telecom company I worked in. Customers were reporting minor discrepancies in their account balances, which when summed across millions of users, became a significant concern.

I focused on our use of Python’s float type for financial calculations. Floating point arithmetic, while efficient, can introduce minor rounding errors due to its binary fraction representation.

These tiny rounding errors, across millions of transactions, were causing the discrepancies.

I tested this hypothesis with a script that performed the same operations using float and Decimal types. The script confirmed my suspicion: the float arithmetic produced slight differences compared to decimal, causing our discrepancy.

I proceeded to replace the float type with Python’s Decimal type from the decimal module in our financial calculations. It provided accurate decimal floating point arithmetic, perfectly suited for financial transactions.

## F.A.Q

Q: How can I round the number to the nearest number with the specified number of decimal places?

A: The round function in Python can be used to round a number to a specified number of digits. In addition to the number, you should pass a second argument representing the number of decimal places you want. Example: round(1.6666, 2) will return 1.67.

Q: How can we round every number to the nearest whole number in a large dataset?

A: If you are dealing with several numbers in a large dataset, such as a list, you can use list comprehension along with Python’s built-in round function to round every number to the nearest whole number. An example is as follows: [round(num) for num in list]

Q: How can a number be rounded up to the ceiling of the number in Python?

A: The math module in Python provides the math.ceil() function to always round up a number. By using this function, you can round up the given number to the ceiling, meaning it will always round the number up.

Q: What is the strategy to round a number to the nearest number with the specified precision?

A: Python’s built-in round function adheres to a strategy known as “round ties to the nearest even number”, which can be used to round a number with the specified precision. If a number is exactly halfway between two others, it will be rounded toward the nearest even number.

Q: What is the round toward negative infinity bias in number rounding in Python?

A: The math.floor() function in Python implements round toward negative infinity bias in number rounding. This means it will always round down to the nearest whole number, irrespective of the decimal part.

Q: How can we round up and round down a number to the nearest whole number in Python?

A: To round up, you can use math.ceil(), and to round down, you can use math.floor(). Both are methods in Python’s math module. For example, math.ceil(1.2) would return 2, and math.floor(1.2) would return 1.

Q: How can we always round a tie away from zero in Python?

A: The standard round() function in Python rounds towards the nearest even choice, but if you want to always round away from zero (i.e., 1.5 rounds to 2, -1.5 rounds to -2), you can use the decimal module’s ROUND_HALF_UP method.