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