|
| 1 | +# CWE-197: Control rounding when converting to less precise numbers |
| 2 | + |
| 3 | +While defensive coding requires enforcing types, it is important to make conscious design decisions on how conversions are rounded. |
| 4 | + |
| 5 | +The `example01.py` code demonstrates how `int()` behaves differently to `round()`. |
| 6 | + |
| 7 | +[*example01.py:*](example01.py) |
| 8 | + |
| 9 | +```py |
| 10 | +""" Code Example """ |
| 11 | + |
| 12 | +print(int(0.5)) # prints 0 |
| 13 | +print(int(1.5)) # prints 1 |
| 14 | +print(int(1.45)) # prints 1 |
| 15 | +print(int(1.51)) # prints 1 |
| 16 | +print(int(-1.5)) # prints -1 |
| 17 | + |
| 18 | +print(round(0.5)) # prints 0 |
| 19 | +print(round(1.5)) # prints 2 |
| 20 | +print(round(1.45)) # prints 1 |
| 21 | +print(round(1.51)) # prints 2 |
| 22 | +print(round(-1.5)) # prints -2 |
| 23 | + |
| 24 | +print(type(round(0.5))) # prints <class 'int'> |
| 25 | + |
| 26 | +``` |
| 27 | + |
| 28 | +The build in `round()` does not allow to specify the type of rounding in use [[python round( ) 2024]](https://docs.python.org/3/library/functions.html#round). 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]](https://docs.python.org/3/library/decimal.html#rounding-modes). Rounding in mathematics and science is not discussed here as it requires a deeper knowledge of computer floating-point arithmetic's. |
| 29 | + |
| 30 | +## Non-Compliant Code Example (float to int) |
| 31 | + |
| 32 | +In `noncompliant01.py` there is no conscious choice of rounding mode. |
| 33 | + |
| 34 | +[*noncompliant01.py:*](noncompliant01.py) |
| 35 | + |
| 36 | +```py |
| 37 | +""" Non-compliant Code Example """ |
| 38 | + |
| 39 | +print(int(0.5)) # prints 0 |
| 40 | +print(int(1.5)) # prints 1 |
| 41 | +print(round(0.5)) # prints 0 |
| 42 | +print(round(1.5)) # prints 2 |
| 43 | +``` |
| 44 | + |
| 45 | +## Compliant Solution (float to int) |
| 46 | + |
| 47 | +Using the `Decimal` class from the `decimal` module allows more control over rounding by choosing one of the `8` rounding modes [[python decimal 2024]](https://docs.python.org/3/library/decimal.html#rounding-modes). |
| 48 | + |
| 49 | +[*compliant01.py:*](compliant01.py) |
| 50 | + |
| 51 | +```py |
| 52 | +""" Compliant Code Example """ |
| 53 | +from decimal import Decimal, ROUND_HALF_UP, ROUND_HALF_DOWN |
| 54 | + |
| 55 | +print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 1 |
| 56 | +print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_UP)) # prints 2 |
| 57 | +print(Decimal("0.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 0 |
| 58 | +print(Decimal("1.5").quantize(Decimal("1"), rounding=ROUND_HALF_DOWN)) # prints 1 |
| 59 | +``` |
| 60 | + |
| 61 | +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. |
| 62 | + |
| 63 | +That `Decimal` can have unexpected results when operated without `Decimal.quantize()` on floating point numbers is demonstrated in `example02.py`. |
| 64 | + |
| 65 | +[*example02.py:*](example02.py) |
| 66 | + |
| 67 | +```py |
| 68 | +# SPDX-FileCopyrightText: OpenSSF project contributors |
| 69 | +# SPDX-License-Identifier: MIT |
| 70 | +""" Code Example """ |
| 71 | +from decimal import ROUND_HALF_UP, Decimal |
| 72 | + |
| 73 | +print(Decimal("0.10")) # prints 0.10 |
| 74 | +print(Decimal(0.10)) # prints 0.1000000000000000055511151231257827021181583404541015625 |
| 75 | +print(Decimal("0.10").quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10 |
| 76 | +print(Decimal(0.10).quantize(Decimal("0.10"), rounding=ROUND_HALF_UP)) # prints 0.10 |
| 77 | +``` |
| 78 | + |
| 79 | +Initializing `Decimal` with an actual `float`, such as `0.10`, and without rounding creates an unprecise number `0.1000000000000000055511151231257827021181583404541015625` in `Python 3.9.2`. |
| 80 | + |
| 81 | +## Automated Detection |
| 82 | + |
| 83 | +|Tool|Version|Checker|Description| |
| 84 | +|:---|:---|:---|:---| |
| 85 | +|Bandit|1.7.4 on Python 3.10.4|Not Available|| |
| 86 | +|Flake8|8-4.0.1 on Python 3.10.4|Not Available|| |
| 87 | + |
| 88 | +## Related Guidelines |
| 89 | + |
| 90 | +||| |
| 91 | +|:---|:---| |
| 92 | +|[MITRE CWE](http://cwe.mitre.org/)|Pillar [CWE-682, Incorrect Conversion between Numeric Types (mitre.org)](http://cwe.mitre.org/data/definitions/682.html)| |
| 93 | +|[MITRE CWE](http://cwe.mitre.org/)|Class [CWE-197, Numeric Truncation Error](https://cwe.mitre.org/data/definitions/197.html)| |
| 94 | +|[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[INT31-C. Ensure that integer conversions do not result in lost or misinterpreted data](https://wiki.sei.cmu.edu/confluence/display/c/INT31-C.+Ensure+that+integer+conversions+do+not+result+in+lost+or+misinterpreted+data)| |
| 95 | +|[SEI CERT C Coding Standard](https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard)|[FLP34-C. Ensure that floating-point conversions are within range of the new type](https://wiki.sei.cmu.edu/confluence/display/c/FLP34-C.+Ensure+that+floating-point+conversions+are+within+range+of+the+new+type)| |
| 96 | +|[ISO/IEC TR 24772:2019](https://www.iso.org/standard/71091.html)|Programming languages — Guidance to avoiding vulnerabilities in programming languages, available from [https://www.iso.org/standard/71091.html](https://www.iso.org/standard/71091.html)| |
| 97 | +|[SEI CERT Coding Standard for Java](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java)|[NUM12-J. Ensure conversions of numeric types to narrower types do not result in lost or misinterpreted data](https://wiki.sei.cmu.edu/confluence/display/java/NUM12-J.+Ensure+conversions+of+numeric+types+to+narrower+types+do+not+result+in+lost+or+misinterpreted+data)| |
| 98 | + |
| 99 | +## Biblography |
| 100 | + |
| 101 | +||| |
| 102 | +|:---|:---| |
| 103 | +|[python round( ) 2024](https://docs.python.org/3/library/functions.html#round)|python round( ), available from: [https://docs.python.org/3/library/functions.html#round](https://docs.python.org/3/library/functions.html#round), [Last accessed June 2024] | |
| 104 | +|[python decimal 2024](https://docs.python.org/3/library/decimal.html#rounding-modes)|Python decimal module, available from: [https://docs.python.org/3/library/decimal.html#rounding-modes](https://docs.python.org/3/library/decimal.html#rounding-modes)| |
0 commit comments