1#include "math/tests/test_internal.h"
2
3TEST_GROUP_DECLARE(fixed, .intensity_desc = {
4 .curve = SCALE_PIECEWISE_LOG,
5 .unit = "iters",
6 });
7
8/* Q32.32, [integer] [fraction], everything here is signed, so we try to test
9 * those cases more because _mul/_div work on absolute values and reapply
10 * sign at end, and _ceil/_floor round by masking */
11
12#define FX_QUARTER (FX_ONE / 4)
13#define FX_EPS ((fx32_32_t) 1)
14
15static bool fx_near(fx32_32_t a, fx32_32_t b, fx32_32_t tol) {
16 fx32_32_t d = a - b;
17 return (d < 0 ? -d : d) <= tol;
18}
19
20TEST_DECLARE_UNIT(fixed, mul_identities) {
21 static const fx32_32_t vals[] = {
22 0, FX_ONE, -FX_ONE, FX_HALF, -FX_HALF,
23 FX(3.25), FX(-3.25), FX(1000), FX(-1000), FX(0.001),
24 };
25
26 for (size_t i = 0; i < TEST_ARRAY_LEN(vals); i++) {
27 fx32_32_t x = vals[i];
28
29 TEST_ASSERT_EQ(fx_mul(x, 0), 0);
30 TEST_ASSERT_EQ(fx_mul(0, x), 0);
31 TEST_ASSERT_EQ(fx_mul(x, FX_ONE), x);
32 TEST_ASSERT_EQ(fx_mul(FX_ONE, x), x);
33 TEST_ASSERT_EQ(fx_mul(x, -FX_ONE), -x);
34
35 for (size_t j = 0; j < TEST_ARRAY_LEN(vals); j++)
36 TEST_ASSERT_EQ(fx_mul(x, vals[j]), fx_mul(vals[j], x));
37 }
38
39 return TEST_SUCCESS;
40}
41
42TEST_DECLARE_UNIT(fixed, mul_standard) {
43 TEST_ASSERT_EQ(fx_mul(FX_HALF, FX_HALF), FX_QUARTER);
44 TEST_ASSERT_EQ(fx_mul(FX(2.0), FX(3.0)), FX(6.0));
45 TEST_ASSERT_EQ(fx_mul(FX(0.25), FX(4.0)), FX_ONE);
46
47 /* 32x32 partial products are recombined via carry out of middle word */
48 TEST_ASSERT_EQ(fx_mul(FX(1.5), FX(1.5)), FX(2.25));
49 TEST_ASSERT_EQ(fx_mul(FX(12345.5), FX(2.0)), FX(24691.0));
50
51 return TEST_SUCCESS;
52}
53
54TEST_DECLARE_UNIT(fixed, mul_signs) {
55 TEST_ASSERT_EQ_S(fx_mul(FX(-2.0), FX(3.0)), FX(-6.0));
56 TEST_ASSERT_EQ_S(fx_mul(FX(2.0), FX(-3.0)), FX(-6.0));
57 TEST_ASSERT_EQ(fx_mul(FX(-2.0), FX(-3.0)), FX(6.0));
58 TEST_ASSERT_EQ(fx_mul(FX(-0.5), FX(-0.5)), FX_QUARTER);
59
60 return TEST_SUCCESS;
61}
62
63TEST_DECLARE_UNIT(fixed, div_identities) {
64 static const fx32_32_t vals[] = {
65 FX_ONE, -FX_ONE, FX_HALF, -FX_HALF, FX(3.25), FX(-3.25), FX(1000),
66 };
67
68 for (size_t i = 0; i < TEST_ARRAY_LEN(vals); i++) {
69 fx32_32_t x = vals[i];
70
71 TEST_ASSERT_EQ(fx_div(x, x), FX_ONE);
72 TEST_ASSERT_EQ(fx_div(x, FX_ONE), x);
73 TEST_ASSERT_EQ(fx_div(0, x), 0);
74 TEST_ASSERT_EQ_S(fx_div(x, -x), -FX_ONE);
75 }
76
77 return TEST_SUCCESS;
78}
79
80TEST_DECLARE_UNIT(fixed, div_standard, TEST_INTENSITY(16, 64, 1024)) {
81 size_t iters = ctx->intensity_val ? ctx->intensity_val : 64;
82 TEST_ASSERT_EQ(fx_div(FX_ONE, FX(2.0)), FX_HALF);
83 TEST_ASSERT_EQ(fx_div(FX(6.0), FX(3.0)), FX(2.0));
84 TEST_ASSERT_EQ(fx_div(FX_ONE, FX(4.0)), FX_QUARTER);
85 TEST_ASSERT_EQ_S(fx_div(FX(-1.0), FX(2.0)), -FX_HALF);
86 TEST_ASSERT_EQ_S(fx_div(FX(1.0), FX(-2.0)), -FX_HALF);
87 TEST_ASSERT_EQ(fx_div(FX(-1.0), FX(-2.0)), FX_HALF);
88
89 /* Round trip, allowing for truncation each of two ops contributes */
90 for (int64_t d = 1; d <= (int64_t) iters; d++) {
91 fx32_32_t q = fx_div(FX_ONE, b: fx_from_int(x: d));
92 TEST_ASSERT(fx_near(fx_mul(q, fx_from_int(d)), FX_ONE, 64));
93 }
94
95 return TEST_SUCCESS;
96}
97
98/* Masking fractional bits rounds toward -inf both +/- */
99TEST_DECLARE_UNIT(fixed, floor_ceil_standard) {
100 TEST_ASSERT_EQ(fx_floor(0), 0);
101 TEST_ASSERT_EQ(fx_ceil(0), 0);
102
103 TEST_ASSERT_EQ(fx_floor(FX_ONE), FX_ONE);
104 TEST_ASSERT_EQ(fx_ceil(FX_ONE), FX_ONE);
105
106 TEST_ASSERT_EQ(fx_floor(FX(1.5)), FX_ONE);
107 TEST_ASSERT_EQ(fx_ceil(FX(1.5)), FX(2.0));
108
109 TEST_ASSERT_EQ(fx_floor(FX_HALF), 0);
110 TEST_ASSERT_EQ(fx_ceil(FX_HALF), FX_ONE);
111
112 return TEST_SUCCESS;
113}
114
115TEST_DECLARE_UNIT(fixed, floor_ceil_negatives) {
116 TEST_ASSERT_EQ_S(fx_floor(-FX_HALF), -FX_ONE);
117 TEST_ASSERT_EQ(fx_ceil(-FX_HALF), 0);
118
119 TEST_ASSERT_EQ_S(fx_floor(FX(-1.5)), FX(-2.0));
120 TEST_ASSERT_EQ_S(fx_ceil(FX(-1.5)), FX(-1.0));
121
122 TEST_ASSERT_EQ_S(fx_floor(FX(-2.0)), FX(-2.0));
123 TEST_ASSERT_EQ_S(fx_ceil(FX(-2.0)), FX(-2.0));
124
125 return TEST_SUCCESS;
126}
127
128TEST_DECLARE_UNIT(fixed, floor_ceil_invariants, TEST_INTENSITY(4, 16, 256)) {
129 int64_t bound = ctx->intensity_val ? (int64_t) (ctx->intensity_val / 2) : 8;
130 if (bound == 0)
131 bound = 1;
132 for (int64_t n = -bound; n <= bound; n++) {
133 for (int64_t f = 0; f < 4; f++) {
134 fx32_32_t x = fx_from_int(x: n) + (fx32_32_t) (f * (FX_ONE / 4));
135
136 fx32_32_t lo = fx_floor(x);
137 fx32_32_t hi = fx_ceil(x);
138
139 TEST_ASSERT(lo <= x && x <= hi);
140 TEST_ASSERT_EQ((lo & (FX_ONE - 1)), 0);
141 TEST_ASSERT_EQ((hi & (FX_ONE - 1)), 0);
142 TEST_ASSERT_EQ(hi - lo, (f == 0 ? 0 : FX_ONE));
143 }
144 }
145
146 return TEST_SUCCESS;
147}
148
149TEST_DECLARE_UNIT(fixed, pow_i32_standard) {
150 TEST_ASSERT_EQ(fx_pow_i32(FX(2.0), 0), FX_ONE);
151 TEST_ASSERT_EQ(fx_pow_i32(FX(2.0), 1), FX(2.0));
152 TEST_ASSERT_EQ(fx_pow_i32(FX(2.0), 2), FX(4.0));
153 TEST_ASSERT_EQ(fx_pow_i32(FX(2.0), 10), FX(1024.0));
154 TEST_ASSERT_EQ(fx_pow_i32(FX(0.5), 2), FX_QUARTER);
155
156 TEST_ASSERT_EQ(fx_pow_i32(0, 0), FX_ONE);
157 TEST_ASSERT_EQ(fx_pow_i32(FX(-3.0), 0), FX_ONE);
158
159 TEST_ASSERT_EQ(fx_pow_i32(FX(-2.0), 2), FX(4.0));
160 TEST_ASSERT_EQ_S(fx_pow_i32(FX(-2.0), 3), FX(-8.0));
161
162 return TEST_SUCCESS;
163}
164
165TEST_DECLARE_UNIT(fixed, pow_i32_negative_exponent) {
166 TEST_ASSERT_EQ(fx_pow_i32(FX(2.0), -1), FX_HALF);
167 TEST_ASSERT_EQ(fx_pow_i32(FX(2.0), -2), FX_QUARTER);
168 TEST_ASSERT(fx_near(fx_pow_i32(FX(4.0), -1), FX(0.25), FX_EPS));
169
170 for (int e = 1; e <= 8; e++) {
171 fx32_32_t pos = fx_pow_i32(FX(2.0), exp: e);
172 fx32_32_t neg = fx_pow_i32(FX(2.0), exp: -e);
173 TEST_ASSERT(fx_near(fx_mul(pos, neg), FX_ONE, 64));
174 }
175
176 return TEST_SUCCESS;
177}
178
179TEST_DECLARE_UNIT(fixed, sqrt_standard) {
180 static const int64_t roots[] = {1, 2, 3, 4, 5, 8, 10, 16};
181
182 for (size_t i = 0; i < TEST_ARRAY_LEN(roots); i++) {
183 int64_t r = roots[i];
184 fx32_32_t got = fx_sqrt(x: fx_from_int(x: r * r));
185 TEST_ASSERT(fx_near(got, fx_from_int(r), FX_ONE / 1024));
186 }
187
188 TEST_ASSERT_EQ(fx_sqrt(FX_QUARTER), FX_HALF);
189 TEST_ASSERT(fx_near(fx_sqrt(FX(2.0)), FX(1.41421356), FX_ONE / 1024));
190
191 return TEST_SUCCESS;
192}
193
194TEST_DECLARE_UNIT(fixed, sqrt_edges) {
195 TEST_ASSERT_EQ(fx_sqrt(0), 0);
196 TEST_ASSERT_EQ(fx_sqrt(-FX_ONE), 0);
197 TEST_ASSERT_EQ(fx_sqrt(FX_ONE), FX_ONE);
198
199 return TEST_SUCCESS;
200}
201
202TEST_DECLARE_UNIT(fixed, conversion_roundtrip,
203 TEST_INTENSITY(256, 1024, 65536)) {
204 int64_t bound = ctx->intensity_val ? (int64_t) ctx->intensity_val : 1024;
205 for (int64_t n = -bound; n <= bound; n++)
206 TEST_ASSERT_EQ_S(fx_to_int(fx_from_int(n)), n);
207
208 /* fx_to_int truncates toward -inf, same as mask */
209 TEST_ASSERT_EQ(fx_to_int(FX_HALF), 0);
210 TEST_ASSERT_EQ(fx_to_int(FX(1.9)), 1);
211 TEST_ASSERT_EQ_S(fx_to_int(-FX_HALF), -1);
212
213 return TEST_SUCCESS;
214}
215
216TEST_DECLARE_UNIT(fixed, clamp_standard) {
217 TEST_ASSERT_EQ(fx_clamp(FX(5.0), 0, FX_ONE), FX_ONE);
218 TEST_ASSERT_EQ(fx_clamp(FX(-5.0), 0, FX_ONE), 0);
219 TEST_ASSERT_EQ(fx_clamp(FX_HALF, 0, FX_ONE), FX_HALF);
220 TEST_ASSERT_EQ(fx_clamp(0, 0, FX_ONE), 0);
221 TEST_ASSERT_EQ(fx_clamp(FX_ONE, 0, FX_ONE), FX_ONE);
222
223 return TEST_SUCCESS;
224}
225