The weird quirk with rounding in Python (and that is good)
Tags: python, datascience, beginners, tutorial
Published: 2024-03-20 19:43:01

When working with rounding in Python, you may have come across an unexpected quirk. Unlike most people who tend to round numbers ending in .5 up, Python operates differently. While it rounds 0.5 to 0, it rounds 1.5 to 2, following the international standard ([IEEE 754](https://en.wikipedia.org/wiki/IEEE_754)). This may be seen as a feature or a bug, depending on how you view it.

![The Python logo surrounded by different examples of rounding](https://cdn-images-1.medium.com/max/2000/0*BFmQo2jQ6haUTnD4)

## It is a bug, for me!

If you encounter a bug when working with numbers ending in .5 I recommend utilizing the build library [decimal](https://docs.python.org/3/library/decimal.html). Within this library, there is a function that rounds all numbers ending with .5 up by using the ROUND_HALF_UP mode. This mode can be found in the [documentation](https://docs.python.org/3/library/decimal.html#rounding-modes), along with other options for rounding. By incorporating this function into our code, we can ensure that any numbers ending in .5 are rounded correctly and avoid potential bugs.

I do NOT recommend using this unless it specifically causes a bug!
```python
    from decimal import Decimal as D, ROUND_HALF_UP
    
    
    def round_nat(d):
        """
        Rounds all halves up
        """
        return D(d).quantize(D('1'), ROUND_HALF_UP)
    
    
    for x in range(1,100):
        d = x/10
        print(f"{d} rounds to {round_nat(d)}")
```
## It is a feature!

Rounding behavior in Python conforms to [IEEE 754 rounding to nearest, ties to even](https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even). This is also known as **bankers rounding** or **Dutch rounding**. Guido van Rossum is not a banker but he is Dutch. 
This ensures a fully random number is just as likely to round up as down. For example, let’s consider a range of numbers from 0 to 10 in increments of 0.1. If we calculate the average of these numbers, it comes out to 4.95. However, if we use the round_nat function (created in the previous chapter), we get an average of 5, due to the presence of 10 numbers ending in .5, which are all rounded up. This results in a slight bias towards a larger number. On the other hand, using native Python rounding eliminates this bias.
```python
    all_num = [i/10 for i in range(0, 100)]
    print("average of all numbers:", sum(all_num)/len(all_num))
    all_rounded_num = [round(i) for i in all_num]
    print("average of all rounded numbers:", sum(all_rounded_num)/len(all_rounded_num))
    all_nat_rounded_num = [round_nat(i) for i in all_num]
    print("average of all natural rounded numbers:", sum(all_nat_rounded_num)/len(all_nat_rounded_num))
```
```
    average of all numbers: 4.95
    average of all rounded numbers: 4.95
    average of all natural rounded numbers: 5
```
Another way to understand it is by looking at the numbers between 0 and 1 with 1 decimal. We have 9 numbers in between, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9. To round them we need to remove 0.1 from 0.1, and add 0.1 to 0.9. Meaning their biases balance out.
The same for 0.2 and 0.8, 0.3 and 0.7, and 0.4 and 0.6; All numbers except 0.5. We do not have a counterbalance for 0.5 between 0 and 1.

To balance rounding 0.5 we balance it with 1.5. We remove 0.5 from 0.5 and add 0.5 to 1.5. Meaning that rounding is just as likely to increase as decrease a random number.

## Conclusion

Rounding in Python may seem unusual at first glance, but it’s actually quite useful. Understanding how it works is essential as it can cause a program to behave differently than expected. While it may not have a significant impact in most cases, it’s important to be aware of this behavior to avoid any potential issues.