While defensive coding requires enforcing types, it is important to make conscious design decisions on how conversions are rounded.
The example01.py
code demonstrates how int()
behaves differently to round()
.
""" Code Example """
print(int(0.5)) # prints 0
print(int(1.5)) # prints 1
print(int(1.45)) # prints 1
print(int(1.51)) # prints 1
print(int(-1.5)) # prints -1
print(round(0.5)) # prints 0
print(round(1.5)) # prints 2
print(round(1.45)) # prints 1
print(round(1.51)) # prints 2
print(round(-1.5)) # prints -2
print(type(round(0.5))) # prints <class 'int'>
The build in round()
does not allow to specify the type of rounding in use [python round( ) 2024]. In Python 3 the round()
function uses “bankers’ rounding” (rounds to the nearest even number in case of ties). This is different to Python 2 which always rounds away from zero. Rounding provided by the decimal
module allows a choice between 8 rounding modes [python decimal 2024]. Rounding in mathematics and science is not discussed here as it requires a deeper knowledge of computer floating-point arithmetic’s.
In noncompliant01.py
there is no conscious choice of rounding mode.
""" Non-compliant Code Example """
print(int(0.5)) # prints 0
print(int(1.5)) # prints 1
print(round(0.5)) # prints 0
print(round(1.5)) # prints 2
Using the Decimal
class from the decimal
module allows more control over rounding by choosing one of the 8
rounding modes [python decimal 2024].
""" Compliant Code Example """
from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN
print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 1
print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 2
print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 0
print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 1
The .quantize(Decimal("1")
, determines the precision to be integer
and rounding=ROUND_HALF_UP
determines the type of rounding applied. Specifying numbers as strings avoids issues such as floating-point representations in binary.
That Decimal
can have unexpected results when operated without Decimal.quantize()
on floating point numbers is demonstrated in example02.py
.
# SPDX-FileCopyrightText: OpenSSF project contributors
# SPDX-License-Identifier: MIT
""" Code Example """
from decimal import ROUND_HALF_UP, Decimal
print(Decimal("0.10")) # prints 0.10
print(Decimal(0.10)) # prints 0.1000000000000000055511151231257827021181583404541015625
print(Decimal("0.10").quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10
print(Decimal(0.10).quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10
Initializing Decimal
with an actual float
, such as 0.10
, and without rounding creates an unprecise number 0.1000000000000000055511151231257827021181583404541015625
in Python 3.9.2
.
Tool | Version | Checker | Description |
---|---|---|---|
Bandit | 1.7.4 on Python 3.10.4 | Not Available | |
Flake8 | 8-4.0.1 on Python 3.10.4 | Not Available |
python round( ) 2024 | python round( ), available from: https://docs.python.org/3/library/functions.html#round, [Last accessed June 2024] |
python decimal 2024 | Python decimal module, available from: https://docs.python.org/3/library/decimal.html#rounding-modes |