Skip to content

Commit cbd6962

Browse files
authored
2 parents 6f28326 + 6d6dda7 commit cbd6962

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ dev-pytest = [
8484
"pytest-mock == 3.14.0",
8585
"pytest-asyncio == 0.23.7",
8686
"async-solipsism == 0.6",
87+
"hypothesis == 6.103.2",
8788
]
8889
dev = [
8990
"frequenz-core[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",

src/frequenz/core/math.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Internal math tools."""
5+
6+
import math
7+
8+
9+
def is_close_to_zero(value: float, abs_tol: float = 1e-9) -> bool:
10+
"""Check if a floating point value is close to zero.
11+
12+
A value of 1e-9 is a commonly used absolute tolerance to balance precision
13+
and robustness for floating-point numbers comparisons close to zero. Note
14+
that this is also the default value for the relative tolerance.
15+
For more technical details, see https://peps.python.org/pep-0485/#behavior-near-zero
16+
17+
Args:
18+
value: the floating point value to compare to.
19+
abs_tol: the minimum absolute tolerance. Defaults to 1e-9.
20+
21+
Returns:
22+
whether the floating point value is close to zero.
23+
"""
24+
zero: float = 0.0
25+
return math.isclose(a=value, b=zero, abs_tol=abs_tol)

tests/test_math.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Tests for the math module."""
5+
6+
from hypothesis import given
7+
from hypothesis import strategies as st
8+
9+
from frequenz.core.math import is_close_to_zero
10+
11+
# We first do some regular test cases to avoid mistakes using hypothesis and having
12+
# basic cases not working.
13+
14+
15+
def test_is_close_to_zero_default_tolerance() -> None:
16+
"""Test the is_close_to_zero function with the default tolerance."""
17+
assert is_close_to_zero(0.0)
18+
assert is_close_to_zero(1e-10)
19+
assert not is_close_to_zero(1e-8)
20+
21+
22+
def test_is_close_to_zero_custom_tolerance() -> None:
23+
"""Test the is_close_to_zero function with a custom tolerance."""
24+
assert is_close_to_zero(0.0, abs_tol=1e-8)
25+
assert is_close_to_zero(1e-8, abs_tol=1e-8)
26+
assert not is_close_to_zero(1e-7, abs_tol=1e-8)
27+
28+
29+
def test_is_close_to_zero_negative_values() -> None:
30+
"""Test the is_close_to_zero function with negative values."""
31+
assert is_close_to_zero(-1e-10)
32+
assert not is_close_to_zero(-1e-8)
33+
34+
35+
@given(st.floats(allow_nan=False, allow_infinity=False))
36+
def test_is_close_to_zero_default_tolerance_hypothesis(value: float) -> None:
37+
"""Test the is_close_to_zero function with the default tolerance for many values."""
38+
if -1e-9 <= value <= 1e-9:
39+
assert is_close_to_zero(value)
40+
else:
41+
assert not is_close_to_zero(value)
42+
43+
44+
@given(
45+
st.floats(allow_nan=False, allow_infinity=False),
46+
st.floats(allow_nan=False, allow_infinity=False, min_value=0.0, max_value=2.0),
47+
)
48+
def test_is_close_to_zero_custom_tolerance_hypothesis(
49+
value: float, abs_tol: float
50+
) -> None:
51+
"""Test the is_close_to_zero function with a custom tolerance with many values/tolerance."""
52+
if -abs_tol <= value <= abs_tol:
53+
assert is_close_to_zero(value, abs_tol=abs_tol)
54+
else:
55+
assert not is_close_to_zero(value, abs_tol=abs_tol)

0 commit comments

Comments
 (0)