1 /*++
2 Copyright (c) 2011 Microsoft Corporation
3 
4 Module Name:
5 
6     hwf.cpp
7 
8 Abstract:
9 
10     Hardware Floating Point Numbers
11 
12 Author:
13 
14     Christoph Wintersteiger (cwinter) 2012-07-30.
15 
16 Revision History:
17 
18 --*/
19 #include<math.h>
20 #include<float.h>
21 #include<sstream>
22 
23 #ifdef _WINDOWS
24 #if defined(_MSC_VER)
25 #pragma float_control( except, on )   // exception semantics; this does _not_ mean that exceptions are enabled (we want them off!)
26 #pragma float_control( precise, on )  // precise semantics (no guessing!)
27 #pragma fp_contract(off)              // contractions off (`contraction' means x*y+z is turned into a fused-mul-add).
28 #pragma fenv_access(on)               // fpu environment sensitivity (needed to be allowed to make FPU mode changes).
29 #endif
30 #else
31 #include<fenv.h>
32 #endif
33 
34 #if defined(__x86_64__) || defined(_M_X64) ||    \
35     defined(__i386) || defined(_M_IX86)
36 #define USE_INTRINSICS
37 #endif
38 
39 #include "util/hwf.h"
40 
41 // Note:
42 // Which FPU will be used is determined by compiler settings. On x64 it's always SSE2,
43 // on x86 we have to chose SSE2 by enabling /arch:SSE2 (otherwise the x87 FPU will be used).
44 // Christoph has decided that we don't want to use the x87; this makes everything a lot easier.
45 
46 
47 // For SSE2, it is best to use compiler intrinsics because this makes it completely
48 // clear to the compiler what instructions should be used. E.g., for sqrt(), the Windows compiler selects
49 // the x87 FPU, even when /arch:SSE2 is on.
50 // Luckily, these are kind of standardized, at least for Windows/Linux/macOS.
51 #if defined(__clang__) || defined(_M_ARM) && defined(_M_ARM64)
52 #undef USE_INTRINSICS
53 #endif
54 
55 #ifdef USE_INTRINSICS
56 #include <emmintrin.h>
57 #if defined(_MSC_VER) || defined(__SSE4_1__)
58 #include <smmintrin.h>
59 #endif
60 #endif
61 
hwf_manager()62 hwf_manager::hwf_manager() :
63     m_mpz_manager(m_mpq_manager)
64 {
65 #ifdef _WINDOWS
66 #if defined(_WIN64)
67     // Precision control is not supported on x64.
68     // See: http://msdn.microsoft.com/en-us/library/e9b52ceh(VS.110).aspx
69     // CMW: I think this is okay though, the compiler will chose the right instructions
70     // (the x64/SSE2 FPU has separate instructions for different precisions).
71 #else
72     // Setting the precision should only be required on the x87, but it won't hurt to do it anyways.
73     // _PC_53 means double precision (53 significand bits). For extended precision use _PC_64.
74 
75 #ifndef USE_INTRINSICS
76     __control87_2(_PC_53, _MCW_PC, &x86_state, &sse2_state);
77 #endif
78 #endif
79 #else
80     // macOS/Linux: Nothing.
81 #endif
82 
83     // We only set the precision of the FPU here in the constructor. At the moment, there are no
84     // other parts of the code that could overwrite this, and Windows takes care of context switches.
85 
86     // CMW: I'm not sure what happens on CPUs with hyper-threading (since the FPU is shared).
87     // I have yet to discover whether Linux and macOS save the FPU state when switching context.
88     // As long as we stick to using the SSE2 FPU though, there shouldn't be any problems with respect
89     // to the precision (not sure about the rounding modes though).
90 }
91 
~hwf_manager()92 hwf_manager::~hwf_manager()
93 {
94 }
95 
RAW(double X)96 uint64_t RAW(double X) { uint64_t tmp; memcpy(&tmp, &(X), sizeof(uint64_t)); return tmp; }
DBL(uint64_t X)97 double DBL(uint64_t X) { double tmp; memcpy(&tmp, &(X), sizeof(double)); return tmp; }
98 
set(hwf & o,int value)99 void hwf_manager::set(hwf & o, int value) {
100     o.value = (double) value;
101 }
102 
set(hwf & o,mpf_rounding_mode rm,int n,int d)103 void hwf_manager::set(hwf & o, mpf_rounding_mode rm, int n, int d) {
104     set_rounding_mode(rm);
105     o.value = ((double) n)/((double) d);
106 }
107 
set(hwf & o,double value)108 void hwf_manager::set(hwf & o, double value) {
109     o.value = value;
110 }
111 
set(hwf & o,float value)112 void hwf_manager::set(hwf & o, float value) {
113     o.value = (double)value;
114 }
115 
set(hwf & o,mpf_rounding_mode rm,mpq const & value)116 void hwf_manager::set(hwf & o, mpf_rounding_mode rm, mpq const & value) {
117     set_rounding_mode(rm);
118     o.value = m_mpq_manager.get_double(value);
119 }
120 
set(hwf & o,mpf_rounding_mode rm,char const * value)121 void hwf_manager::set(hwf & o, mpf_rounding_mode rm, char const * value) {
122     // We expect [i].[f]P[e], where P means that the exponent is interpreted as 2^e instead of 10^e.
123 
124     std::string v(value);
125     size_t e_pos = v.find('p');
126     if (e_pos == std::string::npos) e_pos = v.find('P');
127 
128     std::string f, e;
129 
130     f = (e_pos != std::string::npos) ? v.substr(0, e_pos) : v;
131     e = (e_pos != std::string::npos) ? v.substr(e_pos+1) : "0";
132 
133     TRACE("mpf_dbg", tout << " f = " << f << " e = " << e << std::endl;);
134 
135     mpq q;
136     m_mpq_manager.set(q, f.c_str());
137 
138     mpz ex;
139     m_mpz_manager.set(ex, e.c_str());
140 
141     set(o, rm, q, ex);
142 
143     TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;);
144 }
145 
set(hwf & o,mpf_rounding_mode rm,mpq const & significand,mpz const & exponent)146 void hwf_manager::set(hwf & o, mpf_rounding_mode rm, mpq const & significand, mpz const & exponent) {
147     // Assumption: this represents significand * 2^exponent.
148     set_rounding_mode(rm);
149 
150     mpq sig;
151     m_mpq_manager.set(sig, significand);
152     int64_t exp = m_mpz_manager.get_int64(exponent);
153 
154     if (m_mpq_manager.is_zero(significand))
155         o.value = 0.0;
156     else
157     {
158         while (m_mpq_manager.lt(sig, 1))
159         {
160             m_mpq_manager.mul(sig, 2, sig);
161             exp--;
162         }
163 
164         hwf s; s.value = m_mpq_manager.get_double(sig);
165         uint64_t r = (RAW(s.value) & 0x800FFFFFFFFFFFFFull) | ((exp + 1023) << 52);
166         o.value = DBL(r);
167     }
168 }
169 
set(hwf & o,bool sign,uint64_t significand,int exponent)170 void hwf_manager::set(hwf & o, bool sign, uint64_t significand, int exponent) {
171     // Assumption: this represents (sign * -1) * (significand/2^sbits) * 2^exponent.
172     SASSERT(significand <= 0x000FFFFFFFFFFFFFull);
173     SASSERT(-1022 <= exponent && exponent <= 1023);
174     uint64_t raw = (sign?0x8000000000000000ull:0);
175     raw |= (((uint64_t)exponent) + 1023) << 52;
176     raw |= significand;
177     memcpy(&o.value, &raw, sizeof(double));
178 }
179 
set(hwf & o,hwf const & x)180 void hwf_manager::set(hwf & o, hwf const & x) {
181     o.value = x.value;
182 }
183 
abs(hwf & o)184 void hwf_manager::abs(hwf & o) {
185     o.value = fabs(o.value);
186 }
187 
abs(hwf const & x,hwf & o)188 void hwf_manager::abs(hwf const & x, hwf & o) {
189     o.value = fabs(x.value);
190 }
191 
neg(hwf & o)192 void hwf_manager::neg(hwf & o) {
193     o.value = -o.value;
194 }
195 
neg(hwf const & x,hwf & o)196 void hwf_manager::neg(hwf const & x, hwf & o) {
197     o.value = -x.value;
198 }
199 
eq(hwf const & x,hwf const & y)200 bool hwf_manager::eq(hwf const & x, hwf const & y) {
201     return (x.value == y.value);
202 }
203 
lt(hwf const & x,hwf const & y)204 bool hwf_manager::lt(hwf const & x, hwf const & y) {
205     return (x.value < y.value);
206 }
207 
lte(hwf const & x,hwf const & y)208 bool hwf_manager::lte(hwf const & x, hwf const & y) {
209     return (x.value <= y.value);
210 }
211 
gt(hwf const & x,hwf const & y)212 bool hwf_manager::gt(hwf const & x, hwf const & y) {
213     return (x.value > y.value);
214 }
215 
gte(hwf const & x,hwf const & y)216 bool hwf_manager::gte(hwf const & x, hwf const & y) {
217     return (x.value >= y.value);
218 }
219 
add(mpf_rounding_mode rm,hwf const & x,hwf const & y,hwf & o)220 void hwf_manager::add(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) {
221     set_rounding_mode(rm);
222 #ifdef USE_INTRINSICS
223     _mm_store_sd(&o.value, _mm_add_sd(_mm_set_sd(x.value), _mm_set_sd(y.value)));
224 #else
225     o.value = x.value + y.value;
226 #endif
227 }
228 
sub(mpf_rounding_mode rm,hwf const & x,hwf const & y,hwf & o)229 void hwf_manager::sub(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) {
230     set_rounding_mode(rm);
231 #ifdef USE_INTRINSICS
232     _mm_store_sd(&o.value, _mm_sub_sd(_mm_set_sd(x.value), _mm_set_sd(y.value)));
233 #else
234     o.value = x.value - y.value;
235 #endif
236 }
237 
238 #define DBL_SCALE 15360
239 
mul(mpf_rounding_mode rm,hwf const & x,hwf const & y,hwf & o)240 void hwf_manager::mul(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) {
241     set_rounding_mode(rm);
242 #ifdef USE_INTRINSICS
243     _mm_store_sd(&o.value, _mm_mul_sd(_mm_set_sd(x.value), _mm_set_sd(y.value)));
244 #else
245     o.value = x.value * y.value;
246 #endif
247 }
248 
div(mpf_rounding_mode rm,hwf const & x,hwf const & y,hwf & o)249 void hwf_manager::div(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) {
250     set_rounding_mode(rm);
251 #ifdef USE_INTRINSICS
252     _mm_store_sd(&o.value, _mm_div_sd(_mm_set_sd(x.value), _mm_set_sd(y.value)));
253 #else
254     o.value = x.value / y.value;
255 #endif
256 }
257 
fma(mpf_rounding_mode rm,hwf const & x,hwf const & y,hwf const & z,hwf & o)258 void hwf_manager::fma(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf const &z, hwf & o) {
259     set_rounding_mode(rm);
260     o.value = ::fma(x.value, y.value, z.value);
261 }
262 
sqrt(mpf_rounding_mode rm,hwf const & x,hwf & o)263 void hwf_manager::sqrt(mpf_rounding_mode rm, hwf const & x, hwf & o) {
264     set_rounding_mode(rm);
265 #ifdef USE_INTRINSICS
266     _mm_store_sd(&o.value, _mm_sqrt_pd(_mm_set_sd(x.value)));
267 #else
268     o.value = ::sqrt(x.value);
269 #endif
270 }
271 
round_to_integral(mpf_rounding_mode rm,hwf const & x,hwf & o)272 void hwf_manager::round_to_integral(mpf_rounding_mode rm, hwf const & x, hwf & o) {
273     set_rounding_mode(rm);
274     // CMW: modf is not the right function here.
275     // modf(x.value, &o.value);
276 
277     // According to the Intel Architecture manual, the x87-instruction FRNDINT is the
278     // same in 32-bit and 64-bit mode. The _mm_round_* intrinsics are SSE4 extensions.
279 #if defined(_WINDOWS) && !defined(_M_ARM) && !defined(_M_ARM64)
280 #if defined( __MINGW32__ ) && ( defined( __GNUG__ ) || defined( __clang__ ) )
281     o.value = nearbyint(x.value);
282 #else
283   #if defined(USE_INTRINSICS) &&                                           \
284     (defined(_WINDOWS) && (defined(__AVX__) || defined(_M_X64))) || \
285     (!defined(_WINDOWS) && defined(__SSE4_1__))
286     switch (rm) {
287     case 0: _mm_store_sd(&o.value, _mm_round_pd(_mm_set_sd(x.value), _MM_FROUND_TO_NEAREST_INT)); break;
288     case 2: _mm_store_sd(&o.value, _mm_round_pd(_mm_set_sd(x.value), _MM_FROUND_TO_POS_INF)); break;
289     case 3: _mm_store_sd(&o.value, _mm_round_pd(_mm_set_sd(x.value), _MM_FROUND_TO_NEG_INF)); break;
290     case 4: _mm_store_sd(&o.value, _mm_round_pd(_mm_set_sd(x.value), _MM_FROUND_TO_ZERO)); break;
291     case 1:
292         UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware!
293         break;
294     default:
295         UNREACHABLE(); // Unknown rounding mode.
296     }
297   #else
298     double xv = x.value;
299     double & ov = o.value;
300 
301     __asm {
302         fld     xv
303         frndint
304         fstp    ov // Store result away.
305     }
306   #endif
307 #endif
308 #else
309     // Linux, macOS.
310     o.value = nearbyint(x.value);
311 #endif
312 }
313 
rem(hwf const & x,hwf const & y,hwf & o)314 void hwf_manager::rem(hwf const & x, hwf const & y, hwf & o) {
315 #if defined(_WINDOWS) && _MSC_VER <= 1700
316     o.value = fmod(x.value, y.value);
317     if (o.value >= (y.value/2.0))
318         o.value -= y.value;
319 #else
320     o.value = remainder(x.value, y.value);
321 #endif
322 }
323 
maximum(hwf const & x,hwf const & y,hwf & o)324 void hwf_manager::maximum(hwf const & x, hwf const & y, hwf & o) {
325 #ifdef USE_INTRINSICS
326     _mm_store_sd(&o.value, _mm_max_sd(_mm_set_sd(x.value), _mm_set_sd(y.value)));
327 #else
328     // use __max ?
329     if (is_nan(x))
330         o.value = y.value;
331     else if (is_nan(y))
332         o.value = x.value;
333     else if (lt(x, y))
334         o.value = y.value;
335     else
336         o.value = x.value;
337 #endif
338 }
339 
minimum(hwf const & x,hwf const & y,hwf & o)340 void hwf_manager::minimum(hwf const & x, hwf const & y, hwf & o) {
341 #ifdef USE_INTRINSICS
342     _mm_store_sd(&o.value, _mm_min_sd(_mm_set_sd(x.value), _mm_set_sd(y.value)));
343 #else
344     // use __min ?
345     if (is_nan(x))
346         o.value = y.value;
347     else if (is_nan(y))
348         o.value = x.value;
349     else if (lt(x, y))
350         o.value = x.value;
351     else
352         o.value = y.value;
353 #endif
354 }
355 
to_string(hwf const & x)356 std::string hwf_manager::to_string(hwf const & x) {
357     std::stringstream ss("");
358     ss << std::scientific << x.value;
359     return ss.str();
360 }
361 
to_rational_string(hwf const & a)362 std::string hwf_manager::to_rational_string(hwf const & a) {
363     // temporary hack
364     unsynch_mpq_manager qm;
365     scoped_mpq q(qm);
366     to_rational(a, q);
367     return qm.to_string(q);
368 }
369 
display_decimal(std::ostream & out,hwf const & a,unsigned k)370 void hwf_manager::display_decimal(std::ostream & out, hwf const & a, unsigned k) {
371     // temporary hack
372     unsynch_mpq_manager qm;
373     scoped_mpq q(qm);
374     to_rational(a, q);
375     qm.display_decimal(out, q, k);
376 }
377 
display_smt2(std::ostream & out,hwf const & a,bool decimal)378 void hwf_manager::display_smt2(std::ostream & out, hwf const & a, bool decimal) {
379     // temporary hack
380     unsynch_mpq_manager qm;
381     scoped_mpq q(qm);
382     to_rational(a, q);
383     qm.display_smt2(out, q, decimal);
384 }
385 
to_rational(hwf const & x,unsynch_mpq_manager & qm,mpq & o)386 void hwf_manager::to_rational(hwf const & x, unsynch_mpq_manager & qm, mpq & o) {
387     SASSERT(is_normal(x) || is_denormal(x) || is_zero(x));
388     scoped_mpz n(qm), d(qm);
389 
390     if (is_normal(x))
391         qm.set(n, (uint64_t)(sig(x) | 0x0010000000000000ull));
392     else
393         qm.set(n, sig(x));
394     if (sgn(x))
395         qm.neg(n);
396     qm.set(d, (uint64_t)0x0010000000000000ull);
397     int e = exp(x);
398     if (e >= 0)
399         qm.mul2k(n, (unsigned)e);
400     else
401         qm.mul2k(d, (unsigned)-e);
402     qm.set(o, n, d);
403 }
404 
is_zero(hwf const & x)405 bool hwf_manager::is_zero(hwf const & x) {
406     uint64_t t = RAW(x.value) & 0x7FFFFFFFFFFFFFFFull;
407     return (t == 0x0ull);
408     // CMW: I tried, and these are slower:
409     // return (t != 0x0ull) ? false  : true;
410     // return (x.value == 0.0 || x.value == -0.0); // [uses SSE2].
411 }
412 
is_neg(hwf const & x)413 bool hwf_manager::is_neg(hwf const & x) {
414     // [Leo]: I added !is_nan(x)
415     return sgn(x) && !is_nan(x);
416 }
417 
is_pos(hwf const & x)418 bool hwf_manager::is_pos(hwf const & x) {
419     return !sgn(x) && !is_nan(x);
420 }
421 
is_nzero(hwf const & x)422 bool hwf_manager::is_nzero(hwf const & x) {
423     return RAW(x.value) == 0x8000000000000000ull;
424 }
425 
is_pzero(hwf const & x)426 bool hwf_manager::is_pzero(hwf const & x) {
427     return RAW(x.value) == 0x0000000000000000ull;
428 }
429 
is_one(hwf const & x)430 bool hwf_manager::is_one(hwf const & x) {
431     return RAW(x.value) == 0x3FF0000000000000ull;
432 }
433 
is_nan(hwf const & x)434 bool hwf_manager::is_nan(hwf const & x) {
435     bool r = ((RAW(x.value) & 0x7FF0000000000000ull) == 0x7FF0000000000000ull) &&
436              ((RAW(x.value) & 0x000FFFFFFFFFFFFFull) != 0x0);
437 #ifdef _WINDOWS
438     SASSERT( !r || (_fpclass(x.value) == _FPCLASS_SNAN || _fpclass(x.value) == _FPCLASS_QNAN));
439 #endif
440     return r;
441 }
442 
is_inf(hwf const & x)443 bool hwf_manager::is_inf(hwf const & x) {
444     bool r = ((RAW(x.value) & 0x7FF0000000000000ull) == 0x7FF0000000000000ull) &&
445              ((RAW(x.value) & 0x000FFFFFFFFFFFFFull) == 0x0);
446 #ifdef _WINDOWS
447     SASSERT( !r || (_fpclass(x.value) == _FPCLASS_NINF || _fpclass(x.value) == _FPCLASS_PINF));
448 #endif
449     return r;
450 }
451 
is_pinf(hwf const & x)452 bool hwf_manager::is_pinf(hwf const & x) {
453     return !sgn(x) && is_inf(x);
454 }
455 
is_ninf(hwf const & x)456 bool hwf_manager::is_ninf(hwf const & x) {
457     return sgn(x) && is_inf(x);
458 }
459 
is_normal(hwf const & x)460 bool hwf_manager::is_normal(hwf const & x) {
461     uint64_t t = RAW(x.value) & 0x7FF0000000000000ull;
462     return (t != 0x0ull && t != 0x7FF0000000000000ull);
463 }
464 
is_denormal(hwf const & x)465 bool hwf_manager::is_denormal(hwf const & x) {
466     uint64_t t = RAW(x.value);
467     return ((t & 0x7FF0000000000000ull) == 0x0 &&
468             (t & 0x000FFFFFFFFFFFFFull) != 0x0);
469 }
470 
is_regular(hwf const & x)471 bool hwf_manager::is_regular(hwf const & x) {
472     // Everything that doesn't have the top-exponent is considered regular.
473     // Note that +-0.0 and denormal numbers have exponent==0; these are regular.
474     // All normal numbers are also regular. What remains is +-Inf and NaN, they are
475     // not regular and they are the only numbers that have exponent 7FF.
476     uint64_t e = RAW(x.value) & 0x7FF0000000000000ull; // the exponent
477     return (e != 0x7FF0000000000000ull);
478 }
479 
is_int(hwf const & x)480 bool hwf_manager::is_int(hwf const & x) {
481     if (!is_normal(x))
482         return false;
483 
484     const int e = exp(x);
485     if (e >= 52)
486         return true;
487     else if (e < 0)
488         return false;
489     else
490     {
491         uint64_t t = sig(x);
492         unsigned shift = 52 - ((unsigned)e);
493         uint64_t mask = (0x1ull << shift) - 1;
494         return (t & mask) == 0;
495     }
496 }
497 
mk_nzero(hwf & o)498 void hwf_manager::mk_nzero(hwf & o) {
499     uint64_t raw = 0x8000000000000000ull;
500     o.value = DBL(raw);
501 }
502 
mk_pzero(hwf & o)503 void hwf_manager::mk_pzero(hwf & o) {
504     o.value = 0;
505 }
506 
mk_zero(bool sign,hwf & o)507 void hwf_manager::mk_zero(bool sign, hwf & o) {
508     if (sign)
509         mk_nzero(o);
510     else
511         mk_pzero(o);
512 }
513 
mk_nan(hwf & o)514 void hwf_manager::mk_nan(hwf & o) {
515     uint64_t raw = 0x7FF0000000000001ull;
516     o.value = DBL(raw);
517 }
518 
mk_inf(bool sign,hwf & o)519 void hwf_manager::mk_inf(bool sign, hwf & o) {
520     uint64_t raw = (sign) ? 0xFFF0000000000000ull : 0x7FF0000000000000ull;
521     o.value = DBL(raw);
522 }
523 
mk_pinf(hwf & o)524 void hwf_manager::mk_pinf(hwf & o) {
525     uint64_t raw = 0x7FF0000000000000ull;
526     o.value = DBL(raw);
527 }
528 
mk_ninf(hwf & o)529 void hwf_manager::mk_ninf(hwf & o) {
530     uint64_t raw = 0xFFF0000000000000ull;
531     o.value = DBL(raw);
532 }
533 
534 #ifdef _WINDOWS
535 #if defined(_WIN64)
536 #ifdef USE_INTRINSICS
537 #define SETRM(RM) _MM_SET_ROUNDING_MODE(RM)
538 #else
539 #define SETRM(RM) _controlfp_s(&sse2_state, RM, _MCW_RC);
540 #endif
541 #else
542 #ifdef USE_INTRINSICS
543 #define SETRM(RM) _MM_SET_ROUNDING_MODE(RM)
544 #else
545 #define SETRM(RM) __control87_2(RM, _MCW_RC, &x86_state, &sse2_state)
546 #endif
547 #endif
548 #else
549 #define SETRM(RM) fesetround(RM)
550 #endif
551 
prev_power_of_two(hwf const & a)552 unsigned hwf_manager::prev_power_of_two(hwf const & a) {
553     SASSERT(!is_nan(a) && !is_pinf(a) && !is_ninf(a));
554     if (!is_pos(a))
555         return 0;
556     if (exp(a) <= -52)
557         return 0;
558     return 51 + exp(a);
559 }
560 
set_rounding_mode(mpf_rounding_mode rm)561 void hwf_manager::set_rounding_mode(mpf_rounding_mode rm)
562 {
563 #ifdef _WINDOWS
564 #ifdef USE_INTRINSICS
565     switch (rm) {
566     case MPF_ROUND_NEAREST_TEVEN:
567         SETRM(_MM_ROUND_NEAREST);
568         break;
569     case MPF_ROUND_TOWARD_POSITIVE:
570         SETRM(_MM_ROUND_UP);
571         break;
572     case MPF_ROUND_TOWARD_NEGATIVE:
573         SETRM(_MM_ROUND_DOWN);
574         break;
575     case MPF_ROUND_TOWARD_ZERO:
576         SETRM(_MM_ROUND_TOWARD_ZERO);
577         break;
578     case MPF_ROUND_NEAREST_TAWAY:
579     default:
580         UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware!
581     }
582 #else
583     switch (rm) {
584     case MPF_ROUND_NEAREST_TEVEN:
585         SETRM(_RC_NEAR);
586         break;
587     case MPF_ROUND_TOWARD_POSITIVE:
588         SETRM(_RC_UP);
589         break;
590     case MPF_ROUND_TOWARD_NEGATIVE:
591         SETRM(_RC_DOWN);
592         break;
593     case MPF_ROUND_TOWARD_ZERO:
594         SETRM(_RC_CHOP);
595         break;
596     case MPF_ROUND_NEAREST_TAWAY:
597     default:
598         UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware!
599     }
600 #endif
601 #else // macOS/Linux
602     switch (rm) {
603     case MPF_ROUND_NEAREST_TEVEN:
604         SETRM(FE_TONEAREST);
605         break;
606     case MPF_ROUND_TOWARD_POSITIVE:
607         SETRM(FE_UPWARD);
608         break;
609     case MPF_ROUND_TOWARD_NEGATIVE:
610         SETRM(FE_DOWNWARD);
611         break;
612     case MPF_ROUND_TOWARD_ZERO:
613         SETRM(FE_TOWARDZERO);
614         break;
615     case MPF_ROUND_NEAREST_TAWAY:
616     default:
617         UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware!
618     }
619 #endif
620 }
621