1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright © 2021 Keith Packard
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above
14  *    copyright notice, this list of conditions and the following
15  *    disclaimer in the documentation and/or other materials provided
16  *    with the distribution.
17  *
18  * 3. Neither the name of the copyright holder nor the names of its
19  *    contributors may be used to endorse or promote products derived
20  *    from this software without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33  * OF THE POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #include <stdio.h>
37 #include <math.h>
38 #include <fenv.h>
39 #include "rounding-mode.h"
40 
41 #ifndef PICOLIBC_DOUBLE_NOROUND
42 static double
do_round_int(double value,int mode)43 do_round_int(double value, int mode)
44 {
45 	switch (mode) {
46 #ifdef FE_TONEAREST
47 	case FE_TONEAREST:
48 		return round(value);
49 #endif
50 #ifdef FE_UPWARD
51 	case FE_UPWARD:
52 		return ceil(value);
53 #endif
54 #ifdef FE_DOWNWARD
55 	case FE_DOWNWARD:
56 		return floor(value);
57 #endif
58 #ifdef FE_TOWARDZERO
59 	case FE_TOWARDZERO:
60 		return trunc(value);
61 #endif
62 	default:
63 		return value;
64 	}
65 }
66 #endif
67 
68 #ifndef PICOLIBC_FLOAT_NOROUND
69 static float
do_roundf_int(float value,int mode)70 do_roundf_int(float value, int mode)
71 {
72 	switch (mode) {
73 #ifdef FE_TONEAREST
74 	case FE_TONEAREST:
75 		return roundf(value);
76 #endif
77 #ifdef FE_UPWARD
78 	case FE_UPWARD:
79 		return ceilf(value);
80 #endif
81 #ifdef FE_DOWNWARD
82 	case FE_DOWNWARD:
83 		return floorf(value);
84 #endif
85 #ifdef FE_TOWARDZERO
86 	case FE_TOWARDZERO:
87 		return truncf(value);
88 #endif
89 	default:
90 		return value;
91 	}
92 }
93 #endif
94 
95 double
96 div(double a, double b);
97 
98 double
99 mul(double a, double b);
100 
101 double
102 sub(double a, double b);
103 
104 float
105 div_f(float a, float b);
106 
107 float
108 mul_f(float a, float b);
109 
110 float
111 sub_f(float a, float b);
112 
113 #if defined(FE_UPWARD) && defined(FE_DOWNWARD) && defined(FE_TOWARDZERO)
114 
115 #ifndef PICOLIBC_DOUBLE_NOROUND
116 
117 static double
div_mul_sub(double a,double b,double c,double d)118 div_mul_sub(double a, double b, double c, double d)
119 {
120 	return sub(mul(div(a,b), c), d);
121 }
122 
123 #define do_fancy(sign) div_mul_sub(sign 1.0, 3.0, 3.0, sign 1.0)
124 
125 #endif
126 
127 #ifndef PICOLIBC_FLOAT_NOROUND
128 
129 static float
div_f_mul_sub(float a,float b,float c,float d)130 div_f_mul_sub(float a, float b, float c, float d)
131 {
132 	return sub_f(mul_f(div_f(a,b), c), d);
133 }
134 
135 #define do_fancyf(sign) div_f_mul_sub(sign 1.0f, 3.0f, 3.0f, sign 1.0f)
136 
137 #endif
138 
139 #endif
140 
141 #define n4      nextafter(4.0, 5.0)
142 #define nn4     nextafter(nextafter(4.0, 5.0), 5.0)
143 #define n2      nextafter(2.0, 3.0)
144 #define nn2     nextafter(nextafter(2.0, 3.0), 3.0)
145 
146 #define n4f     nextafterf(4.0f, 5.0f)
147 #define nn4f    nextafterf(nextafterf(4.0f, 5.0f), 5.0f)
148 #define n2f     nextafterf(2.0f, 3.0f)
149 
150 static int
check(int mode,char * name,double value)151 check(int mode, char *name, double value)
152 {
153 	int ret = 0;
154 
155         (void) mode;
156         (void) name;
157         (void) value;
158 #ifndef PICOLIBC_DOUBLE_NOROUND
159 	double want, got;
160 	printf("test double %s for value %g \n", name, value);
161 	want = do_round_int(value, mode);
162 	if (fesetround(mode) != 0) {
163 		printf("ERROR fesetround %s failed\n", name);
164 		ret++;
165 	}
166         /* Make sure setting an invalid rounding mode fails and doesn't
167          * change the test results
168          */
169         if (fesetround(FE_ALL_EXCEPT+1) == 0) {
170                 printf("ERROR fesetround %d succeeded\n", FE_ALL_EXCEPT+1);
171                 ret++;
172         }
173         if (fegetround() != mode) {
174                 printf("ERROR fegetround() != %s\n", name);
175                 ret++;
176         }
177 	got = nearbyint(value);
178 	if (want != got) {
179 		printf("ERROR double %s: value %g want %g got %g\n", name, value, want, got);
180 		ret++;
181 	}
182 
183 	want = do_round_int(-value, mode);
184 	if (fesetround(mode) != 0) {
185 		printf("ERROR fesetround %s failed\n", name);
186 		ret++;
187 	}
188 	got = nearbyint(-value);
189 	if (want != got) {
190 		printf("ERROR double %s: -value %g want %g got %g\n", name, value, want, got);
191 		ret++;
192 	}
193 #endif
194 #ifndef PICOLIBC_FLOAT_NOROUND
195 	float valuef = value, wantf, gotf;
196 
197 	printf("test float %s for value %g \n", name, value);
198 	wantf = do_roundf_int(valuef, mode);
199 	if (fesetround(mode) != 0) {
200 		printf("ERROR fesetround %s failed\n", name);
201 		ret++;
202 	}
203         /* Make sure setting an invalid rounding mode fails and doesn't
204          * change the test results
205          */
206         if (fesetround(FE_ALL_EXCEPT+1) == 0) {
207                 printf("ERROR fesetround %d succeeded\n", FE_ALL_EXCEPT+1);
208                 ret++;
209         }
210         if (fegetround() != mode) {
211                 printf("ERROR fegetround() != %s\n", name);
212                 ret++;
213         }
214 	gotf = nearbyintf(valuef);
215 	if (wantf != gotf) {
216 		printf("ERROR float %s: value %g want %g got %g\n", name, (double) valuef, (double) wantf, (double) gotf);
217 		ret++;
218 	}
219 
220 	wantf = do_roundf_int(-valuef, mode);
221 	if (fesetround(mode) != 0) {
222 		printf("ERROR fesetround %s failed\n", name);
223 		ret++;
224 	}
225 	gotf = nearbyintf(-valuef);
226 	if (wantf != gotf) {
227 		printf("ERROR float %s: -value %g want %g got %g\n", name, (double) valuef, (double) wantf, (double) gotf);
228 		ret++;
229 	}
230 #endif
231 	if (ret)
232 		printf("ERROR %s failed\n", name);
233 	return ret;
234 }
235 
236 static double my_values[] = {
237 	1.0,
238 	1.0 / 3.0,
239 	3.0 / 2.0,	/* let either round out or round even work */
240 	2.0 / 3.0,
241 };
242 
243 #define NUM_VALUE (sizeof(my_values)/sizeof(my_values[0]))
244 
main(void)245 int main(void)
246 {
247 	unsigned i;
248 	int ret = 0;
249 
250 	for (i = 0; i < NUM_VALUE; i++) {
251 #ifdef FE_TONEAREST
252 		ret += check(FE_TONEAREST, "FE_TONEAREST", my_values[i]);
253 #endif
254 #ifdef FE_UPWARD
255 		ret += check(FE_UPWARD, "FE_UPWARD", my_values[i]);
256 #endif
257 #ifdef FE_DOWNWARD
258 		ret += check(FE_DOWNWARD, "FE_DOWNWARD", my_values[i]);
259 #endif
260 #ifdef FE_TOWARDZERO
261 		ret += check(FE_TOWARDZERO, "FE_TOWARDZERO", my_values[i]);
262 #endif
263 	}
264 #if defined(FE_UPWARD) && defined(FE_DOWNWARD) && defined(FE_TOWARDZERO)
265 
266 #define check_func(big, small, name) do {				\
267 		printf("testing %s\n", name); \
268 		if (big <= small) {					\
269 			printf("ERROR %s: %g is not > %g\n", name, (double) big, (double) small); \
270 			ret++;						\
271 		}							\
272 		if (big < 0) {						\
273 			printf("ERROR %s: %g is not >= 0\n", name, (double) big); \
274 			ret++;						\
275 		}							\
276 		if (small > 0) {					\
277 			printf("ERROR %s: %g is not <= 0\n", name, (double) small); \
278 			ret++;						\
279 		}							\
280 	} while(0)
281 
282 
283 #ifndef PICOLIBC_DOUBLE_NOROUND
284 	double up_plus, toward_plus, down_plus, up_minus, toward_minus, down_minus;
285 
286 	fesetround(FE_UPWARD);
287 	up_plus = do_fancy();
288 	up_minus = do_fancy(-);
289 
290 	fesetround(FE_DOWNWARD);
291 	down_plus = do_fancy();
292 	down_minus = do_fancy(-);
293 
294 	fesetround(FE_TOWARDZERO);
295 	toward_plus = do_fancy();
296 	toward_minus = do_fancy(-);
297 
298 	check_func(up_plus, down_plus, "up/down");
299 	check_func(up_plus, toward_plus, "up/toward");
300 	check_func(up_minus, down_minus, "-up/-down");
301 	check_func(toward_minus, down_minus, "-toward/-down");
302 
303 #define check_sqrt(mode, param, expect) do {                            \
304                 fesetround(mode);                                       \
305                 double __p = (param);                                   \
306                 double __e = (expect);                                  \
307                 printf("testing sqrt %s for value %a\n", #mode, __p);   \
308                 double __r = sqrt(__p);                                 \
309                 if (__r != __e) {                                       \
310                         printf("ERROR %s: sqrt(%a) got %a expect %a\n", #mode, __p, __r, __e); \
311                         ret++;                                          \
312                 }                                                       \
313         } while(0)
314 
315         check_sqrt(FE_TONEAREST, n4, 2.0);
316         check_sqrt(FE_TONEAREST, nn4, n2);
317 
318         check_sqrt(FE_UPWARD, n4, n2);
319         check_sqrt(FE_UPWARD, nn4, n2);
320 
321         check_sqrt(FE_DOWNWARD, n4, 2.0);
322         check_sqrt(FE_DOWNWARD, nn4, 2.0);
323 
324         check_sqrt(FE_TOWARDZERO, n4, 2.0);
325         check_sqrt(FE_TOWARDZERO, nn4, 2.0);
326 #endif
327 #ifndef PICOLIBC_FLOAT_NOROUND
328 	float fup_plus, ftoward_plus, fdown_plus, fup_minus, ftoward_minus, fdown_minus;
329 
330 	fesetround(FE_UPWARD);
331 	fup_plus = do_fancyf();
332 	fup_minus = do_fancyf(-);
333 
334 	fesetround(FE_DOWNWARD);
335 	fdown_plus = do_fancyf();
336 	fdown_minus = do_fancyf(-);
337 
338 	fesetround(FE_TOWARDZERO);
339 	ftoward_plus = do_fancyf();
340 	ftoward_minus = do_fancyf(-);
341 
342 	check_func(fup_plus, fdown_plus, "fup/fdown");
343 	check_func(fup_plus, ftoward_plus, "fup/ftoward");
344 	check_func(fup_minus, fdown_minus, "-fup/-fdown");
345 	check_func(ftoward_minus, fdown_minus, "-ftoward/-fdown");
346 
347 #define check_sqrtf(mode, param, expect) do {                           \
348                 fesetround(mode);                                       \
349                 float __p = (param);                                    \
350                 float __e = (expect);                                   \
351                 printf("testing sqrtf %s for value %a\n", #mode, (double) __p); \
352                 float __r = sqrtf(__p);                                 \
353                 if (__r != __e) {                                       \
354                     printf("ERROR %s: sqrtf(%a) got %a expect %a\n", #mode, (double) __p, (double) __r, (double) __e); \
355                         ret++;                                          \
356                 }                                                       \
357         } while(0)
358 
359         check_sqrtf(FE_TONEAREST, n4f, 2.0f);
360         check_sqrtf(FE_TONEAREST, nn4f, n2f);
361 
362         check_sqrtf(FE_UPWARD, n4f, n2f);
363         check_sqrtf(FE_UPWARD, nn4f, n2f);
364 
365         check_sqrtf(FE_DOWNWARD, n4f, 2.0f);
366         check_sqrtf(FE_DOWNWARD, nn4f, 2.0f);
367 
368         check_sqrtf(FE_TOWARDZERO, n4f, 2.0f);
369         check_sqrtf(FE_TOWARDZERO, nn4f, 2.0f);
370 
371 #endif
372 #endif
373 
374 	return ret;
375 }
376