Blender V5.0
BLI_math_half_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "testing/testing.h"
6
7#include "BLI_math_half.hh"
8#include "BLI_time.h"
9
10#include <cmath>
11
12// #define DO_PERF_TESTS 1
13
14namespace blender::tests {
15
16TEST(math_half, half_to_float_scalar)
17{
19 EXPECT_EQ(blender::math::half_to_float(1), 5.960464478e-08f);
20 EXPECT_EQ(blender::math::half_to_float(32), 1.907348633e-06f);
21 EXPECT_EQ(blender::math::half_to_float(37), 2.205371857e-06f);
22 EXPECT_EQ(blender::math::half_to_float(511), 3.045797348e-05f);
23 EXPECT_EQ(blender::math::half_to_float(999), 5.954504013e-05f);
24 EXPECT_EQ(blender::math::half_to_float(1024), 6.103515625e-05f);
25 EXPECT_EQ(blender::math::half_to_float(1357), 8.088350296e-05f);
26 EXPECT_EQ(blender::math::half_to_float(6789), 0.003183364868f);
27 EXPECT_EQ(blender::math::half_to_float(16383), 1.999023438f);
30 EXPECT_EQ(blender::math::half_to_float(31744), std::numeric_limits<float>::infinity());
31 EXPECT_TRUE(std::isnan(blender::math::half_to_float(31746)));
32 EXPECT_TRUE(std::isnan(blender::math::half_to_float(32767)));
34 EXPECT_EQ(blender::math::half_to_float(32769), -5.960464478e-08f);
35 EXPECT_EQ(blender::math::half_to_float(46765), -0.4172363281f);
36 EXPECT_EQ(blender::math::half_to_float(54501), -78.3125f);
37 EXPECT_EQ(blender::math::half_to_float(64511), -65504.0f);
38 EXPECT_EQ(blender::math::half_to_float(64512), -std::numeric_limits<float>::infinity());
39 EXPECT_TRUE(std::isnan(blender::math::half_to_float(64513)));
40 EXPECT_TRUE(std::isnan(blender::math::half_to_float(65535)));
41}
42
43TEST(math_half, float_to_half_scalar)
44{
45#define HFUN(v) blender::math::float_to_half(v)
46 EXPECT_EQ(HFUN(0.0f), 0);
47 EXPECT_EQ(HFUN(std::numeric_limits<float>::min()), 0);
48 EXPECT_EQ(HFUN(5.960464478e-08f), 1);
49 EXPECT_EQ(HFUN(1.907348633e-06f), 32);
50 EXPECT_EQ(HFUN(2.205371857e-06f), 37);
51 EXPECT_EQ(HFUN(3.045797348e-05f), 511);
52 EXPECT_EQ(HFUN(5.954504013e-05f), 999);
53 EXPECT_EQ(HFUN(6.103515625e-05f), 1024);
54 EXPECT_EQ(HFUN(8.088350296e-05f), 1357);
55 EXPECT_EQ(HFUN(0.003183364868f), 6789);
56 EXPECT_EQ(HFUN(0.1f), 11878);
57 EXPECT_EQ(HFUN(1.0f), 15360);
58 EXPECT_EQ(HFUN(1.999023438f), 16383);
59 EXPECT_EQ(HFUN(1.999523438f), 16384);
60 EXPECT_EQ(HFUN(2.0f), 16384);
61 EXPECT_EQ(HFUN(11.0f), 18816);
62 EXPECT_EQ(HFUN(65504.0f), 31743);
63 EXPECT_EQ(HFUN(65535.0f), 31744); /* FP16 inf */
64 EXPECT_EQ(HFUN(1.0e6f), 31744); /* FP16 inf */
65 EXPECT_EQ(HFUN(std::numeric_limits<float>::infinity()), 31744);
66 EXPECT_EQ(HFUN(std::numeric_limits<float>::max()), 31744);
67 EXPECT_EQ(HFUN(std::numeric_limits<float>::quiet_NaN()), 32256);
68 EXPECT_EQ(HFUN(-0.0f), 32768);
69 EXPECT_EQ(HFUN(-5.960464478e-08f), 32769);
70 EXPECT_EQ(HFUN(-0.4172363281f), 46765);
71 EXPECT_EQ(HFUN(-1.0f), 48128);
72 EXPECT_EQ(HFUN(-78.3125f), 54501);
73 EXPECT_EQ(HFUN(-123.5f), 55224);
74 EXPECT_EQ(HFUN(-65504.0f), 64511);
75 EXPECT_EQ(HFUN(-65536.0f), 64512); /* FP16 -inf */
76 EXPECT_EQ(HFUN(-1.0e6f), 64512); /* FP16 -inf */
77 EXPECT_EQ(HFUN(-std::numeric_limits<float>::infinity()), 64512);
78#undef HFUN
79}
80
81TEST(math_half, float_to_half_make_finite_scalar)
82{
83#define HFUN(v) blender::math::float_to_half_make_finite(v)
84 EXPECT_EQ(HFUN(0.0f), 0);
85 EXPECT_EQ(HFUN(std::numeric_limits<float>::min()), 0);
86 EXPECT_EQ(HFUN(5.960464478e-08f), 1);
87 EXPECT_EQ(HFUN(1.907348633e-06f), 32);
88 EXPECT_EQ(HFUN(2.205371857e-06f), 37);
89 EXPECT_EQ(HFUN(3.045797348e-05f), 511);
90 EXPECT_EQ(HFUN(5.954504013e-05f), 999);
91 EXPECT_EQ(HFUN(6.103515625e-05f), 1024);
92 EXPECT_EQ(HFUN(8.088350296e-05f), 1357);
93 EXPECT_EQ(HFUN(0.003183364868f), 6789);
94 EXPECT_EQ(HFUN(0.1f), 11878);
95 EXPECT_EQ(HFUN(1.0f), 15360);
96 EXPECT_EQ(HFUN(1.999023438f), 16383);
97 EXPECT_EQ(HFUN(1.999523438f), 16384);
98 EXPECT_EQ(HFUN(2.0f), 16384);
99 EXPECT_EQ(HFUN(11.0f), 18816);
100 EXPECT_EQ(HFUN(65504.0f), 31743);
101 /* Too large: result is FP16 65504. */
102 EXPECT_EQ(HFUN(65535.0f), 31743);
103 EXPECT_EQ(HFUN(1.0e6f), 31743);
104 EXPECT_EQ(HFUN(std::numeric_limits<float>::infinity()), 31743);
105 EXPECT_EQ(HFUN(std::numeric_limits<float>::max()), 31743);
106 /* NaN: result is zero. */
107 EXPECT_EQ(HFUN(std::numeric_limits<float>::quiet_NaN()), 0);
108 EXPECT_EQ(HFUN(std::numeric_limits<float>::signaling_NaN()), 0);
109 EXPECT_EQ(HFUN(-0.0f), 32768);
110 EXPECT_EQ(HFUN(-5.960464478e-08f), 32769);
111 EXPECT_EQ(HFUN(-0.4172363281f), 46765);
112 EXPECT_EQ(HFUN(-1.0f), 48128);
113 EXPECT_EQ(HFUN(-78.3125f), 54501);
114 EXPECT_EQ(HFUN(-123.5f), 55224);
115 EXPECT_EQ(HFUN(-65504.0f), 64511);
116 /* Too large negative: result is FP16 -65504. */
117 EXPECT_EQ(HFUN(-65536.0f), 64511);
118 EXPECT_EQ(HFUN(-1.0e6f), 64511);
119 EXPECT_EQ(HFUN(-std::numeric_limits<float>::infinity()), 64511);
120 /* -NaN: result is negative zero. */
121 EXPECT_EQ(HFUN(-std::numeric_limits<float>::quiet_NaN()), 32768);
122 EXPECT_EQ(HFUN(-std::numeric_limits<float>::signaling_NaN()), 32768);
123#undef HFUN
124}
125
127{
128 const uint16_t src[13] = {
129 0, 1, 6789, 16383, 16384, 31743, 31744, 32768, 32769, 46765, 54501, 64511, 64512};
130 /* One extra entry in destination, to check that function leaves it intact. */
131 const float exp[14] = {
132 0.0f,
133 5.960464478e-08f,
134 0.003183364868f,
135 1.999023438f,
136 2.0f,
137 65504.0f,
138 std::numeric_limits<float>::infinity(),
139 -0.0f,
140 -5.960464478e-08f,
141 -0.4172363281f,
142 -78.3125f,
143 -65504.0f,
144 -std::numeric_limits<float>::infinity(),
145 1.2345f,
146 };
147 float dst[14] = {};
148 dst[13] = 1.2345f;
149
151 EXPECT_EQ_ARRAY(exp, dst, 14);
152}
153
155{
156 const float src[13] = {0.0f,
157 5.960464478e-08f,
158 0.003183364868f,
159 1.999023438f,
160 2.0f,
161 65504.0f,
162 std::numeric_limits<float>::infinity(),
163 -0.0f,
164 -5.960464478e-08f,
165 -0.4172363281f,
166 -78.3125f,
167 -65504.0f,
168 -std::numeric_limits<float>::infinity()};
169 /* One extra entry in destination, to check that function leaves it intact. */
170 const uint16_t exp[14] = {
171 0, 1, 6789, 16383, 16384, 31743, 31744, 32768, 32769, 46765, 54501, 64511, 64512, 12345};
172 uint16_t dst[14] = {};
173 dst[13] = 12345;
174
176 EXPECT_EQ_ARRAY(exp, dst, 14);
177}
178
180{
181 const float src[17] = {
182 0.0f,
183 5.960464478e-08f,
184 0.003183364868f,
185 1.999023438f,
186 2.0f,
187 65504.0f,
188 std::numeric_limits<float>::infinity(),
189 -0.0f,
190 -5.960464478e-08f,
191 -0.4172363281f,
192 -78.3125f,
193 -65504.0f,
194 -std::numeric_limits<float>::infinity(),
195 100000.0f,
196 -100000.0f,
197 std::numeric_limits<float>::quiet_NaN(),
198 -std::numeric_limits<float>::quiet_NaN(),
199 };
200 /* One extra entry in destination, to check that function leaves it intact. */
201 const uint16_t exp[18] = {0,
202 1,
203 6789,
204 16383,
205 16384,
206 31743,
207 31743,
208 32768,
209 32769,
210 46765,
211 54501,
212 64511,
213 64511,
214 31743,
215 64511,
216 0,
217 32768,
218 12345};
219 uint16_t dst[18] = {};
220 dst[17] = 12345;
222 EXPECT_EQ_ARRAY(exp, dst, 18);
223}
224
225#ifdef DO_PERF_TESTS
226
227/*
228 * Time to convert 100 million numbers FP16 -> FP32.
229 *
230 * Ryzen 5950X (VS2022):
231 * - `half_to_float`: 164ms
232 * - `half_to_float_array`: 132ms (scalar)
233 * - `half_to_float_array`: 84ms (SSE2 4x wide path)
234 * - `half_to_float_array`: 86ms (w/ AVX2 F16C - however Blender is not compiled for AVX2 yet)
235 *
236 * Mac M1 Max (Clang 15), using NEON VCVT:
237 * - `half_to_float`: 97ms
238 * - `half_to_float_array`: 53ms
239 */
240TEST(math_half_perf, half_to_float_scalar)
241{
242 double t0 = BLI_time_now_seconds();
243 size_t sum = 0;
244 for (int i = 0; i < 100'000'000; i++) {
245 float f = blender::math::half_to_float(uint16_t(i & 0xFFFF));
246 uint32_t fu;
247 memcpy(&fu, &f, sizeof(f));
248 sum += fu;
249 }
250 double t1 = BLI_time_now_seconds();
251 printf("- FP16->FP32 scalar: %.3fs sum %zu\n", t1 - t0, sum);
252}
253
254TEST(math_half_perf, half_to_float_array)
255{
256 const int test_size = 100'000'000;
257 uint16_t *src = new uint16_t[test_size];
258 float *dst = new float[test_size];
259 for (int i = 0; i < test_size; i++) {
260 src[i] = i & 0xFFFF;
261 }
262 double t0 = BLI_time_now_seconds();
263 size_t sum = 0;
264 blender::math::half_to_float_array(src, dst, test_size);
265 for (int i = 0; i < test_size; i++) {
266 uint32_t fu;
267 memcpy(&fu, &dst[i], sizeof(fu));
268 sum += fu;
269 }
270 double t1 = BLI_time_now_seconds();
271 printf("- FP16->FP32 array : %.3fs sum %zu\n", t1 - t0, sum);
272 delete[] src;
273 delete[] dst;
274}
275
276/*
277 * Time to convert 100 million numbers FP32 -> FP16.
278 *
279 * Ryzen 5950X (VS2022):
280 * - `float_to_half`: 242ms
281 * - `float_to_half_array`: 184ms (scalar)
282 * - `float_to_half_array`: 68ms (SSE2 4x wide path)
283 * - `float_to_half_array`: 50ms (w/ AVX2 F16C - however Blender is not compiled for AVX2 yet)
284 *
285 * Mac M1 Max (Clang 15), using NEON VCVT:
286 * - `float_to_half`: 93ms
287 * - `float_to_half_array`: 21ms
288 */
289TEST(math_half_perf, float_to_half_scalar)
290{
291 double t0 = BLI_time_now_seconds();
292 uint32_t sum = 0;
293 for (int i = 0; i < 100'000'000; i++) {
294 float f = ((i & 0xFFFF) - 0x8000) + 0.1f;
296 sum += h;
297 }
298 double t1 = BLI_time_now_seconds();
299 printf("- FP32->FP16 scalar: %.3fs sum %u\n", t1 - t0, sum);
300}
301
302TEST(math_half_perf, float_to_half_array)
303{
304 const int test_size = 100'000'000;
305 float *src = new float[test_size];
306 uint16_t *dst = new uint16_t[test_size];
307 for (int i = 0; i < test_size; i++) {
308 src[i] = ((i & 0xFFFF) - 0x8000) + 0.1f;
309 }
310
311 double t0 = BLI_time_now_seconds();
312 uint32_t sum = 0;
313 blender::math::float_to_half_array(src, dst, test_size);
314 for (int i = 0; i < test_size; i++) {
315 sum += dst[i];
316 }
317 double t1 = BLI_time_now_seconds();
318 printf("- FP32->FP16 array : %.3fs sum %u\n", t1 - t0, sum);
319 delete[] src;
320 delete[] dst;
321}
322
324{
325 const int test_size = 100'000'000;
326 float *src = new float[test_size];
327 uint16_t *dst = new uint16_t[test_size];
328 for (int i = 0; i < test_size; i++) {
329 src[i] = ((i & 0xFFFF) - 0x8000) + 0.1f;
330 }
331 double t0 = BLI_time_now_seconds();
332 uint32_t sum = 0;
334 for (int i = 0; i < test_size; i++) {
335 sum += dst[i];
336 }
337 double t1 = BLI_time_now_seconds();
338 printf("- FP32->FP16 finite array : %.3fs sum %u\n", t1 - t0, sum);
339 delete[] src;
340 delete[] dst;
341}
342
343#endif // #ifdef DO_PERF_TESTS
344
345} // namespace blender::tests
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
#define HFUN(v)
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:113
static T sum(const btAlignedObjectArray< T > &items)
#define printf(...)
void float_to_half_array(const float *src, uint16_t *dst, size_t length)
Definition math_half.cc:238
uint16_t float_to_half(float v)
Definition math_half.cc:27
T exp(const T &x)
void half_to_float_array(const uint16_t *src, float *dst, size_t length)
Definition math_half.cc:368
void float_to_half_make_finite_array(const float *src, uint16_t *dst, size_t length)
Definition math_half.cc:274
float half_to_float(uint16_t v)
Definition math_half.cc:108
TEST(blf_load, load)
Definition BLF_tests.cc:34
i
Definition text_draw.cc:230