Coverage for tests/unit/misc/test_numerical_conversions.py: 100%
43 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-04 03:33 -0600
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-04 03:33 -0600
1from __future__ import annotations
3import random
4from math import isclose, isinf, isnan
6import pytest
8from muutils.misc import shorten_numerical_to_str, str_to_numeric
11@pytest.mark.parametrize(
12 "quantity, expected",
13 [
14 ("5", 5),
15 ("-5", -5),
16 ("0.1", 0.1),
17 ("-0.1", -0.1),
18 ("1/5", 0.2),
19 ("-1/5", -0.2),
20 ("1K", 1000.0),
21 ("-1K", -1000.0),
22 ("1.5M", 1500000.0),
23 ("-1.5M", -1500000.0),
24 ("2.5B", 2.5 * 1e9),
25 ("-2.5B", -2.5 * 1e9),
26 ("1/2M", 0.5 * 1e6),
27 ("-1/2M", -0.5 * 1e6),
28 ("100q", 100 * 1e15),
29 ("-100q", -100 * 1e15),
30 ("0.001Q", 0.001 * 1e18),
31 ("-0.001Q", -0.001 * 1e18),
32 ("1.23", 1.23),
33 ("-1.23", -1.23),
34 ("4.5678e2", 456.78),
35 ("-4.5678e2", -456.78),
36 ],
37)
38def test_str_to_numeric(quantity, expected):
39 assert str_to_numeric(quantity) == expected
42@pytest.mark.parametrize(
43 "x, s",
44 [
45 ("inf", 1),
46 ("INF", 1),
47 ("infinity", 1),
48 ("INFINITY", 1),
49 ("-inf", -1),
50 ("-INF", -1),
51 ("-infinity", -1),
52 ("-INFINITY", -1),
53 ],
54)
55def test_str_to_numeric_inf(x: str, s: int):
56 x_num: float = str_to_numeric(x)
57 assert isinf(x_num)
58 if s == 1:
59 assert x_num > 0
60 else:
61 assert x_num < 0
64def test_str_to_numeric_nan():
65 assert isnan(str_to_numeric("nan"))
66 assert isnan(str_to_numeric("NAN"))
69@pytest.mark.parametrize(
70 "x",
71 [
72 "1/0",
73 "-1/0",
74 "5/0",
75 "-5/0",
76 ],
77)
78def test_div_by_zero(x: str):
79 with pytest.raises(ZeroDivisionError):
80 str_to_numeric(x)
83@pytest.mark.parametrize(
84 "quantity",
85 [
86 "invalid",
87 "5.5.5",
88 "1/2/3",
89 "1QQ",
90 "1K5",
91 "1.2.3M",
92 "1e1e1",
93 "None",
94 "null",
95 "nil",
96 "nanan",
97 "infinitesimal",
98 "infinity and beyond",
99 "True or False",
100 "a lot!",
101 "12*&**@&#!dkjadhkj",
102 ],
103)
104def test_str_to_numeric_invalid(quantity):
105 with pytest.raises(ValueError):
106 str_to_numeric(quantity)
109@pytest.mark.parametrize(
110 "quantity, mapping, expected",
111 [
112 ("5k", {"k": 1e3}, 5000),
113 ("-5k", {"k": 1e3}, -5000),
114 ("2.5x", {"x": 1e5}, 2.5 * 1e5),
115 ("-2.5x", {"x": 1e5}, -2.5 * 1e5),
116 ("1/4y", {"y": 1e2}, 0.25 * 1e2),
117 ("-1/4y", {"y": 1e2}, -0.25 * 1e2),
118 ("1.2LOL", {"LOL": 1e6}, 1.2 * 1e6),
119 ("-1.2LOL", {"LOL": 1e6}, -1.2 * 1e6),
120 ],
121)
122def test_str_to_numeric_custom_mapping(quantity, mapping, expected):
123 assert str_to_numeric(quantity, mapping) == expected
126@pytest.mark.parametrize(
127 "quantity, small_as_decimal, precision, expected",
128 [
129 (1234, True, 1, "1.2K"),
130 (1234, False, 1, "1K"),
131 (1234, True, 2, "1.23K"),
132 (1234567, True, 1, "1.2M"),
133 (1234567890, True, 1, "1.2B"),
134 (1234567890123, True, 1, "1.2t"),
135 (1234567890123456, True, 1, "1.2q"),
136 (1234567890123456789, True, 1, "1.2Q"),
137 ],
138)
139def test_shorten_numerical_to_str(quantity, small_as_decimal, precision, expected):
140 assert shorten_numerical_to_str(quantity, small_as_decimal, precision) == expected
143@pytest.mark.parametrize(
144 "exponent_range, n_tests",
145 [
146 ((1, 18), 1000),
147 ((-2, 2), 1000),
148 ],
149)
150def test_round_trip_fuzzing(exponent_range, n_tests):
151 for _ in range(n_tests):
152 exponent: int = random.randint(*exponent_range)
153 mantissa: float = random.uniform(1, 10)
154 num_sign: int = random.choice([-1, 1])
155 num: float = num_sign * mantissa * (10**exponent)
157 shortened: str = shorten_numerical_to_str(num)
158 print(f"num: {num}, shortened: {shortened}")
159 restored: float = str_to_numeric(shortened)
161 assert isclose(
162 num, restored, rel_tol=1e-1
163 ), f"Failed for num: {num}, shortened: {shortened}, restored: {restored}"