xref: /qemu/tests/tcg/arm/fcvt.c (revision 727385c4)
1 /*
2  * Test Floating Point Conversion
3  */
4 
5 /* we want additional float type definitions */
6 #define __STDC_WANT_IEC_60559_BFP_EXT__
7 #define __STDC_WANT_IEC_60559_TYPES_EXT__
8 
9 #include <stdio.h>
10 #include <inttypes.h>
11 #include <math.h>
12 #include <float.h>
13 #include <fenv.h>
14 
15 #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
16 
17 static char flag_str[256];
18 
19 static char *get_flag_state(int flags)
20 {
21     if (flags) {
22         snprintf(flag_str, sizeof(flag_str), "%s %s %s %s %s",
23                  flags & FE_OVERFLOW ? "OVERFLOW" : "",
24                  flags & FE_UNDERFLOW ? "UNDERFLOW" : "",
25                  flags & FE_DIVBYZERO ? "DIV0" : "",
26                  flags & FE_INEXACT ? "INEXACT" : "",
27                  flags & FE_INVALID ? "INVALID" : "");
28     } else {
29         snprintf(flag_str, sizeof(flag_str), "OK");
30     }
31 
32     return flag_str;
33 }
34 
35 static void print_double_number(int i, double num)
36 {
37     uint64_t double_as_hex = *(uint64_t *) &num;
38     int flags = fetestexcept(FE_ALL_EXCEPT);
39     char *fstr = get_flag_state(flags);
40 
41     printf("%02d DOUBLE: %02.20e / %#020" PRIx64 " (%#x => %s)\n",
42            i, num, double_as_hex, flags, fstr);
43 }
44 
45 static void print_single_number(int i, float num)
46 {
47     uint32_t single_as_hex = *(uint32_t *) &num;
48     int flags = fetestexcept(FE_ALL_EXCEPT);
49     char *fstr = get_flag_state(flags);
50 
51     printf("%02d SINGLE: %02.20e / %#010x  (%#x => %s)\n",
52            i, num, single_as_hex, flags, fstr);
53 }
54 
55 static void print_half_number(int i, uint16_t num)
56 {
57     int flags = fetestexcept(FE_ALL_EXCEPT);
58     char *fstr = get_flag_state(flags);
59 
60     printf("%02d   HALF: %#04x  (%#x => %s)\n",
61            i, num, flags, fstr);
62 }
63 
64 static void print_int64(int i, int64_t num)
65 {
66     uint64_t int64_as_hex = *(uint64_t *) &num;
67     int flags = fetestexcept(FE_ALL_EXCEPT);
68     char *fstr = get_flag_state(flags);
69 
70     printf("%02d   INT64: %20" PRId64 "/%#020" PRIx64 " (%#x => %s)\n",
71            i, num, int64_as_hex, flags, fstr);
72 }
73 
74 #ifndef SNANF
75 /* Signaling NaN macros, if supported.  */
76 # define SNANF (__builtin_nansf (""))
77 # define SNAN (__builtin_nans (""))
78 # define SNANL (__builtin_nansl (""))
79 #endif
80 
81 float single_numbers[] = { -SNANF,
82                            -NAN,
83                            -INFINITY,
84                            -FLT_MAX,
85                            -1.111E+31,
86                            -1.111E+30,
87                            -1.08700982e-12,
88                            -1.78051176e-20,
89                            -FLT_MIN,
90                            0.0,
91                            FLT_MIN,
92                            2.98023224e-08,
93                            5.96046E-8, /* min positive FP16 subnormal */
94                            6.09756E-5, /* max subnormal FP16 */
95                            6.10352E-5, /* min positive normal FP16 */
96                            1.0,
97                            1.0009765625, /* smallest float after 1.0 FP16 */
98                            2.0,
99                            M_E, M_PI,
100                            65503.0,
101                            65504.0, /* max FP16 */
102                            65505.0,
103                            131007.0,
104                            131008.0, /* max AFP */
105                            131009.0,
106                            1.111E+30,
107                            FLT_MAX,
108                            INFINITY,
109                            NAN,
110                            SNANF };
111 
112 static void convert_single_to_half(void)
113 {
114     int i;
115 
116     printf("Converting single-precision to half-precision\n");
117 
118     for (i = 0; i < ARRAY_SIZE(single_numbers); ++i) {
119         float input = single_numbers[i];
120 
121         feclearexcept(FE_ALL_EXCEPT);
122 
123         print_single_number(i, input);
124 #if defined(__arm__)
125         uint32_t output;
126         asm("vcvtb.f16.f32 %0, %1" : "=t" (output) : "x" (input));
127 #else
128         uint16_t output;
129         asm("fcvt %h0, %s1" : "=w" (output) : "x" (input));
130 #endif
131         print_half_number(i, output);
132     }
133 }
134 
135 static void convert_single_to_double(void)
136 {
137     int i;
138 
139     printf("Converting single-precision to double-precision\n");
140 
141     for (i = 0; i < ARRAY_SIZE(single_numbers); ++i) {
142         float input = single_numbers[i];
143         /* uint64_t output; */
144         double output;
145 
146         feclearexcept(FE_ALL_EXCEPT);
147 
148         print_single_number(i, input);
149 #if defined(__arm__)
150         asm("vcvt.f64.f32 %P0, %1" : "=w" (output) : "t" (input));
151 #else
152         asm("fcvt %d0, %s1" : "=w" (output) : "x" (input));
153 #endif
154         print_double_number(i, output);
155     }
156 }
157 
158 static void convert_single_to_integer(void)
159 {
160     int i;
161 
162     printf("Converting single-precision to integer\n");
163 
164     for (i = 0; i < ARRAY_SIZE(single_numbers); ++i) {
165         float input = single_numbers[i];
166         int64_t output;
167 
168         feclearexcept(FE_ALL_EXCEPT);
169 
170         print_single_number(i, input);
171 #if defined(__arm__)
172         /* asm("vcvt.s32.f32 %s0, %s1" : "=t" (output) : "t" (input)); */
173         output = input;
174 #else
175         asm("fcvtzs %0, %s1" : "=r" (output) : "w" (input));
176 #endif
177         print_int64(i, output);
178     }
179 }
180 
181 /* This allows us to initialise some doubles as pure hex */
182 typedef union {
183     double d;
184     uint64_t h;
185 } test_doubles;
186 
187 test_doubles double_numbers[] = {
188     {SNAN},
189     {-NAN},
190     {-INFINITY},
191     {-DBL_MAX},
192     {-FLT_MAX-1.0},
193     {-FLT_MAX},
194     {-1.111E+31},
195     {-1.111E+30}, /* half prec */
196     {-2.0}, {-1.0},
197     {-DBL_MIN},
198     {-FLT_MIN},
199     {0.0},
200     {FLT_MIN},
201     {2.98023224e-08},
202     {5.96046E-8}, /* min positive FP16 subnormal */
203     {6.09756E-5}, /* max subnormal FP16 */
204     {6.10352E-5}, /* min positive normal FP16 */
205     {1.0},
206     {1.0009765625}, /* smallest float after 1.0 FP16 */
207     {DBL_MIN},
208     {1.3789972848607228e-308},
209     {1.4914738736681624e-308},
210     {1.0}, {2.0},
211     {M_E}, {M_PI},
212     {65503.0},
213     {65504.0}, /* max FP16 */
214     {65505.0},
215     {131007.0},
216     {131008.0}, /* max AFP */
217     {131009.0},
218     {.h = 0x41dfffffffc00000 }, /* to int = 0x7fffffff */
219     {FLT_MAX},
220     {FLT_MAX + 1.0},
221     {DBL_MAX},
222     {INFINITY},
223     {NAN},
224     {.h = 0x7ff0000000000001}, /* SNAN */
225     {SNAN},
226 };
227 
228 static void convert_double_to_half(void)
229 {
230     int i;
231 
232     printf("Converting double-precision to half-precision\n");
233 
234     for (i = 0; i < ARRAY_SIZE(double_numbers); ++i) {
235         double input = double_numbers[i].d;
236         uint16_t output;
237 
238         feclearexcept(FE_ALL_EXCEPT);
239 
240         print_double_number(i, input);
241 
242         /* as we don't have _Float16 support */
243 #if defined(__arm__)
244         /* asm("vcvtb.f16.f64 %0, %P1" : "=t" (output) : "x" (input)); */
245         output = input;
246 #else
247         asm("fcvt %h0, %d1" : "=w" (output) : "x" (input));
248 #endif
249         print_half_number(i, output);
250     }
251 }
252 
253 static void convert_double_to_single(void)
254 {
255     int i;
256 
257     printf("Converting double-precision to single-precision\n");
258 
259     for (i = 0; i < ARRAY_SIZE(double_numbers); ++i) {
260         double input = double_numbers[i].d;
261         uint32_t output;
262 
263         feclearexcept(FE_ALL_EXCEPT);
264 
265         print_double_number(i, input);
266 
267 #if defined(__arm__)
268         asm("vcvt.f32.f64 %0, %P1" : "=w" (output) : "x" (input));
269 #else
270         asm("fcvt %s0, %d1" : "=w" (output) : "x" (input));
271 #endif
272 
273         print_single_number(i, output);
274     }
275 }
276 
277 static void convert_double_to_integer(void)
278 {
279     int i;
280 
281     printf("Converting double-precision to integer\n");
282 
283     for (i = 0; i < ARRAY_SIZE(double_numbers); ++i) {
284         double input = double_numbers[i].d;
285         int64_t output;
286 
287         feclearexcept(FE_ALL_EXCEPT);
288 
289         print_double_number(i, input);
290 #if defined(__arm__)
291         /* asm("vcvt.s32.f32 %s0, %s1" : "=t" (output) : "t" (input)); */
292         output = input;
293 #else
294         asm("fcvtzs %0, %d1" : "=r" (output) : "w" (input));
295 #endif
296         print_int64(i, output);
297     }
298 }
299 
300 /* no handy defines for these numbers */
301 uint16_t half_numbers[] = {
302     0xffff, /* -NaN / AHP -Max */
303     0xfcff, /* -NaN / AHP */
304     0xfc01, /* -NaN / AHP */
305     0xfc00, /* -Inf */
306     0xfbff, /* -Max */
307     0xc000, /* -2 */
308     0xbc00, /* -1 */
309     0x8001, /* -MIN subnormal */
310     0x8000, /* -0 */
311     0x0000, /* +0 */
312     0x0001, /* MIN subnormal */
313     0x3c00, /* 1 */
314     0x7bff, /* Max */
315     0x7c00, /* Inf */
316     0x7c01, /* NaN / AHP */
317     0x7cff, /* NaN / AHP */
318     0x7fff, /* NaN / AHP +Max*/
319 };
320 
321 static void convert_half_to_double(void)
322 {
323     int i;
324 
325     printf("Converting half-precision to double-precision\n");
326 
327     for (i = 0; i < ARRAY_SIZE(half_numbers); ++i) {
328         uint16_t input = half_numbers[i];
329         double output;
330 
331         feclearexcept(FE_ALL_EXCEPT);
332 
333         print_half_number(i, input);
334 #if defined(__arm__)
335         /* asm("vcvtb.f64.f16 %P0, %1" : "=w" (output) : "t" (input)); */
336         output = input;
337 #else
338         asm("fcvt %d0, %h1" : "=w" (output) : "x" (input));
339 #endif
340         print_double_number(i, output);
341     }
342 }
343 
344 static void convert_half_to_single(void)
345 {
346     int i;
347 
348     printf("Converting half-precision to single-precision\n");
349 
350     for (i = 0; i < ARRAY_SIZE(half_numbers); ++i) {
351         uint16_t input = half_numbers[i];
352         float output;
353 
354         feclearexcept(FE_ALL_EXCEPT);
355 
356         print_half_number(i, input);
357 #if defined(__arm__)
358         asm("vcvtb.f32.f16 %0, %1" : "=w" (output) : "x" ((uint32_t)input));
359 #else
360         asm("fcvt %s0, %h1" : "=w" (output) : "x" (input));
361 #endif
362         print_single_number(i, output);
363     }
364 }
365 
366 static void convert_half_to_integer(void)
367 {
368     int i;
369 
370     printf("Converting half-precision to integer\n");
371 
372     for (i = 0; i < ARRAY_SIZE(half_numbers); ++i) {
373         uint16_t input = half_numbers[i];
374         int64_t output;
375 
376         feclearexcept(FE_ALL_EXCEPT);
377 
378         print_half_number(i, input);
379 #if defined(__arm__)
380         /* asm("vcvt.s32.f16 %0, %1" : "=t" (output) : "t" (input)); v8.2*/
381         output = input;
382 #else
383         asm("fcvt %s0, %h1" : "=w" (output) : "x" (input));
384 #endif
385         print_int64(i, output);
386     }
387 }
388 
389 typedef struct {
390     int flag;
391     char *desc;
392 } float_mapping;
393 
394 float_mapping round_flags[] = {
395     { FE_TONEAREST, "to nearest" },
396     { FE_UPWARD, "upwards" },
397     { FE_DOWNWARD, "downwards" },
398     { FE_TOWARDZERO, "to zero" }
399 };
400 
401 int main(int argc, char *argv[argc])
402 {
403     int i;
404 
405     printf("#### Enabling IEEE Half Precision\n");
406 
407     for (i = 0; i < ARRAY_SIZE(round_flags); ++i) {
408         fesetround(round_flags[i].flag);
409         printf("### Rounding %s\n", round_flags[i].desc);
410         convert_single_to_half();
411         convert_single_to_double();
412         convert_double_to_half();
413         convert_double_to_single();
414         convert_half_to_single();
415         convert_half_to_double();
416     }
417 
418     /* convert to integer */
419     convert_single_to_integer();
420     convert_double_to_integer();
421     convert_half_to_integer();
422 
423     /* And now with ARM alternative FP16 */
424 #if defined(__arm__)
425     /* See glibc sysdeps/arm/fpu_control.h */
426     asm("mrc p10, 7, r1, cr1, cr0, 0\n\t"
427         "orr r1, r1, %[flags]\n\t"
428         "mcr p10, 7, r1, cr1, cr0, 0\n\t"
429         : /* no output */ : [flags] "n" (1 << 26) : "r1" );
430 #else
431     asm("mrs x1, fpcr\n\t"
432         "orr x1, x1, %[flags]\n\t"
433         "msr fpcr, x1\n\t"
434         : /* no output */ : [flags] "n" (1 << 26) : "x1" );
435 #endif
436 
437     printf("#### Enabling ARM Alternative Half Precision\n");
438 
439     for (i = 0; i < ARRAY_SIZE(round_flags); ++i) {
440         fesetround(round_flags[i].flag);
441         printf("### Rounding %s\n", round_flags[i].desc);
442         convert_single_to_half();
443         convert_single_to_double();
444         convert_double_to_half();
445         convert_double_to_single();
446         convert_half_to_single();
447         convert_half_to_double();
448     }
449 
450     /* convert to integer */
451     convert_single_to_integer();
452     convert_double_to_integer();
453     convert_half_to_integer();
454 
455     return 0;
456 }
457