xref: /linux/lib/math/tests/polynomial_kunit.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 #include <kunit/test.h>
4 #include <linux/polynomial.h>
5 
6 struct polynomial_test_param {
7 	const struct polynomial *poly;
8 	long data;
9 	long expected;
10 	const char *name;
11 };
12 
13 /* f(x) = 5 */
14 static const struct polynomial poly_constant = {
15 	.total_divider = 1,
16 	.terms = {
17 		{0, 5, 1, 1},
18 	}
19 };
20 
21 /* f(x) = 2x^2 + 3x + 5 */
22 static const struct polynomial poly_simple = {
23 	.total_divider = 1,
24 	.terms = {
25 		{2, 2, 1, 1},
26 		{1, 3, 1, 1},
27 		{0, 5, 1, 1},
28 	}
29 };
30 
31 /* f(x) = -5x + 100 */
32 static const struct polynomial poly_negative_coef = {
33 	.total_divider = 1,
34 	.terms = {
35 		{1, -5, 1, 1},
36 		{0, 100, 1, 1},
37 	}
38 };
39 
40 /* f(x) = (150x + 50) / 10 */
41 static const struct polynomial poly_total_divider = {
42 	.total_divider = 10,
43 	.terms = {
44 		{1, 150, 1, 1},
45 		{0,  50, 1, 1},
46 	}
47 };
48 
49 /*
50  * f(x) = x / 2
51  * divider=2 applied once per multiply: mult_frac(coef, data, 2) = coef*data/2
52  */
53 static const struct polynomial poly_step_divider = {
54 	.total_divider = 1,
55 	.terms = {
56 		{1, 1, 2, 1},
57 		{0, 0, 1, 1},
58 	}
59 };
60 
61 /*
62  * f(x) = (100/500) * x^2 = 0.2 * x^2
63  * Encoded as coef=100, divider=10, divider_leftover=5:
64  *   denom = 10^2 * 5 = 500
65  */
66 static const struct polynomial poly_leftover = {
67 	.total_divider = 1,
68 	.terms = {
69 		{2, 100, 10, 5},
70 		{0,   0,  1, 1},
71 	}
72 };
73 
74 /*
75  * f(x) = 2x^3  (single high-degree term, no constant)
76  * Used to exercise the power loop alone.
77  */
78 static const struct polynomial poly_cubic = {
79 	.total_divider = 1,
80 	.terms = {
81 		{3, 2, 1, 1},
82 		{0, 0, 1, 1},
83 	}
84 };
85 
86 /*
87  * f(x) = 4x + 1  with a zero-coefficient quadratic term.
88  * The deg-2 term contributes nothing regardless of input.
89  */
90 static const struct polynomial poly_zero_coef = {
91 	.total_divider = 1,
92 	.terms = {
93 		{2, 0, 1, 1},
94 		{1, 4, 1, 1},
95 		{0, 1, 1, 1},
96 	}
97 };
98 
99 /*
100  * f(x) = 9  with total_divider = 0.
101  * The implementation treats 0 as 1 via `total_divider ?: 1`, so the
102  * result must equal the constant term unchanged.
103  */
104 static const struct polynomial poly_zero_total_divider = {
105 	.total_divider = 0,
106 	.terms = {
107 		{0, 9, 1, 1},
108 	}
109 };
110 
111 
112 static const struct polynomial_test_param test_params[] = {
113 	{
114 		.poly     = &poly_constant,
115 		.data     = 0,
116 		.expected = 5,
117 		.name     = "Constant polynomial at x=0",
118 	},
119 	{
120 		.poly     = &poly_constant,
121 		.data     = 42,
122 		.expected = 5,
123 		.name     = "Constant polynomial is independent of input",
124 	},
125 	{
126 		.poly     = &poly_simple,
127 		.data     = 0,
128 		.expected = 5,	/* zero input collapses all power terms */
129 		.name     = "Zero input yields constant term only",
130 	},
131 	{
132 		.poly     = &poly_simple,
133 		.data     = 10,
134 		.expected = 235,	/* 2*100 + 3*10 + 5 */
135 		.name     = "Simple quadratic at x=10",
136 	},
137 	{
138 		.poly     = &poly_negative_coef,
139 		.data     = 10,
140 		.expected = 50,		/* -5*10 + 100 */
141 		.name     = "Negative coefficient at x=10",
142 	},
143 	{
144 		.poly     = &poly_negative_coef,
145 		.data     = 20,
146 		.expected = 0,		/* -5*20 + 100 = 0 */
147 		.name     = "Negative coefficient result is zero",
148 	},
149 	{
150 		.poly     = &poly_total_divider,
151 		.data     = 3,
152 		.expected = 50,		/* (150*3 + 50) / 10 = 500/10 */
153 		.name     = "total_divider scales the final sum",
154 	},
155 	{
156 		.poly     = &poly_step_divider,
157 		.data     = 100,
158 		.expected = 50,		/* 1*100/2 */
159 		.name     = "Per-step divider halves input",
160 	},
161 	{
162 		.poly     = &poly_leftover,
163 		.data     = 30,
164 		.expected = 180,	/* 100*30^2 / (10^2 * 5) = 90000/500 */
165 		.name     = "divider_leftover with quadratic term",
166 	},
167 	/* Boundary: unit and negative-unit input */
168 	{
169 		/*
170 		 * data=1: each mult_frac(tmp, 1, divider) strips one factor of
171 		 * divider from coef per degree, so coef is left-shifted right
172 		 * until intermediate precision is exhausted.
173 		 * 2*1 + 3*1 + 5 = 10
174 		 */
175 		.poly     = &poly_simple,
176 		.data     = 1,
177 		.expected = 10,
178 		.name     = "Boundary: data=1 (unit input)",
179 	},
180 	{
181 		/*
182 		 * data=-1: even degrees produce positive contributions,
183 		 * odd degrees produce negative ones.
184 		 * 2*(-1)^2 + 3*(-1) + 5 = 2 - 3 + 5 = 4
185 		 */
186 		.poly     = &poly_simple,
187 		.data     = -1,
188 		.expected = 4,
189 		.name     = "Boundary: data=-1 (negative unit input)",
190 	},
191 
192 	/* Boundary: negative non-trivial input */
193 	{
194 		/*
195 		 * 2*(-3)^2 + 3*(-3) + 5 = 18 - 9 + 5 = 14
196 		 * Verifies sign handling for negative data across all degrees.
197 		 */
198 		.poly     = &poly_simple,
199 		.data     = -3,
200 		.expected = 14,
201 		.name     = "Boundary: negative data with quadratic",
202 	},
203 
204 	/* Boundary: total_divider = 0 is treated as 1 */
205 	{
206 		.poly     = &poly_zero_total_divider,
207 		.data     = 42,
208 		.expected = 9,
209 		.name     = "Boundary: total_divider=0 defaults to 1",
210 	},
211 
212 	/* Boundary: zero-coefficient high-degree term */
213 	{
214 		/*
215 		 * The deg-2 term has coef=0, so it contributes 0 regardless
216 		 * of data. Result: 0 + 4*10 + 1 = 41
217 		 */
218 		.poly     = &poly_zero_coef,
219 		.data     = 10,
220 		.expected = 41,
221 		.name     = "Boundary: zero-coefficient term is inert",
222 	},
223 
224 	/* Boundary: single high-degree term, no constant */
225 	{
226 		/* 2 * 5^3 = 250; also verifies the loop terminates on deg-0 */
227 		.poly     = &poly_cubic,
228 		.data     = 5,
229 		.expected = 250,
230 		.name     = "Boundary: single cubic term",
231 	},
232 	{
233 		/* 2 * (-2)^3 = -16; odd power preserves sign of negative data */
234 		.poly     = &poly_cubic,
235 		.data     = -2,
236 		.expected = -16,
237 		.name     = "Boundary: single cubic term, negative data",
238 	},
239 
240 };
241 
242 static void get_desc(const struct polynomial_test_param *param, char *desc)
243 {
244 	strscpy(desc, param->name, KUNIT_PARAM_DESC_SIZE);
245 }
246 
247 KUNIT_ARRAY_PARAM(polynomial, test_params, get_desc);
248 
249 static void polynomial_calc_test(struct kunit *test)
250 {
251 	const struct polynomial_test_param *param = test->param_value;
252 
253 	KUNIT_EXPECT_EQ(test, polynomial_calc(param->poly, param->data),
254 			param->expected);
255 }
256 
257 static struct kunit_case polynomial_test_cases[] = {
258 	KUNIT_CASE_PARAM(polynomial_calc_test, polynomial_gen_params),
259 	{}
260 };
261 
262 static struct kunit_suite polynomial_test_suite = {
263 	.name = "math-polynomial",
264 	.test_cases = polynomial_test_cases,
265 };
266 
267 kunit_test_suites(&polynomial_test_suite);
268 
269 MODULE_DESCRIPTION("math.polynomial_calc KUnit test suite");
270 MODULE_LICENSE("GPL");
271