1 /*
2 * Cast helpers.
3 *
4 * C99+ coercion is challenging portability-wise because out-of-range casts
5 * may invoke implementation defined or even undefined behavior. See e.g.
6 * http://blog.frama-c.com/index.php?post/2013/10/09/Overflow-float-integer.
7 *
8 * Provide explicit cast helpers which try to avoid implementation defined
9 * or undefined behavior. These helpers can then be simplified in the vast
10 * majority of cases where the implementation defined or undefined behavior
11 * is not problematic.
12 */
13
14 #include "duk_internal.h"
15
16 /* Portable double-to-integer cast which avoids undefined behavior and avoids
17 * relying on fmin(), fmax(), or other intrinsics. Out-of-range results are
18 * not assumed by caller, but here value is clamped, NaN converts to minval.
19 */
20 #define DUK__DOUBLE_INT_CAST1(tname,minval,maxval) do { \
21 if (DUK_LIKELY(x >= (duk_double_t) (minval))) { \
22 DUK_ASSERT(!DUK_ISNAN(x)); \
23 if (DUK_LIKELY(x <= (duk_double_t) (maxval))) { \
24 return (tname) x; \
25 } else { \
26 return (tname) (maxval); \
27 } \
28 } else { \
29 /* NaN or below minval. Since we don't care about the result \
30 * for out-of-range values, just return the minimum value for \
31 * both. \
32 */ \
33 return (tname) (minval); \
34 } \
35 } while (0)
36
37 /* Rely on specific NaN behavior for duk_double_{fmin,fmax}(): if either
38 * argument is a NaN, return the second argument. This avoids a
39 * NaN-to-integer cast which is undefined behavior.
40 */
41 #define DUK__DOUBLE_INT_CAST2(tname,minval,maxval) do { \
42 return (tname) duk_double_fmin(duk_double_fmax(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
43 } while (0)
44
45 /* Another solution which doesn't need C99+ behavior for fmin() and fmax(). */
46 #define DUK__DOUBLE_INT_CAST3(tname,minval,maxval) do { \
47 if (DUK_ISNAN(x)) { \
48 /* 0 or any other value is fine. */ \
49 return (tname) 0; \
50 } else \
51 return (tname) DUK_FMIN(DUK_FMAX(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
52 } \
53 } while (0)
54
55 /* C99+ solution: relies on specific fmin() and fmax() behavior in C99: if
56 * one argument is NaN but the other isn't, the non-NaN argument is returned.
57 * Because the limits are non-NaN values, explicit NaN check is not needed.
58 * This may not work on all legacy platforms, and also doesn't seem to inline
59 * the fmin() and fmax() calls (unless one uses -ffast-math which we don't
60 * support).
61 */
62 #define DUK__DOUBLE_INT_CAST4(tname,minval,maxval) do { \
63 return (tname) DUK_FMIN(DUK_FMAX(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
64 } while (0)
65
duk_double_to_int_t(duk_double_t x)66 DUK_INTERNAL duk_int_t duk_double_to_int_t(duk_double_t x) {
67 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
68 /* Real world solution: almost any practical platform will provide
69 * an integer value without any guarantees what it is (which is fine).
70 */
71 return (duk_int_t) x;
72 #else
73 DUK__DOUBLE_INT_CAST1(duk_int_t, DUK_INT_MIN, DUK_INT_MAX);
74 #endif
75 }
76
duk_double_to_uint_t(duk_double_t x)77 DUK_INTERNAL duk_uint_t duk_double_to_uint_t(duk_double_t x) {
78 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
79 return (duk_uint_t) x;
80 #else
81 DUK__DOUBLE_INT_CAST1(duk_uint_t, DUK_UINT_MIN, DUK_UINT_MAX);
82 #endif
83 }
84
duk_double_to_int32_t(duk_double_t x)85 DUK_INTERNAL duk_int32_t duk_double_to_int32_t(duk_double_t x) {
86 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
87 return (duk_int32_t) x;
88 #else
89 DUK__DOUBLE_INT_CAST1(duk_int32_t, DUK_INT32_MIN, DUK_INT32_MAX);
90 #endif
91 }
92
duk_double_to_uint32_t(duk_double_t x)93 DUK_INTERNAL duk_uint32_t duk_double_to_uint32_t(duk_double_t x) {
94 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
95 return (duk_uint32_t) x;
96 #else
97 DUK__DOUBLE_INT_CAST1(duk_uint32_t, DUK_UINT32_MIN, DUK_UINT32_MAX);
98 #endif
99 }
100
101 /* Largest IEEE double that doesn't round to infinity in the default rounding
102 * mode. The exact midpoint between (1 - 2^(-24)) * 2^128 and 2^128 rounds to
103 * infinity, at least on x64. This number is one double unit below that
104 * midpoint. See misc/float_cast.c.
105 */
106 #define DUK__FLOAT_ROUND_LIMIT 340282356779733623858607532500980858880.0
107
108 /* Maximum IEEE float. Double-to-float conversion above this would be out of
109 * range and thus technically undefined behavior.
110 */
111 #define DUK__FLOAT_MAX 340282346638528859811704183484516925440.0
112
duk_double_to_float_t(duk_double_t x)113 DUK_INTERNAL duk_float_t duk_double_to_float_t(duk_double_t x) {
114 /* Even a double-to-float cast is technically undefined behavior if
115 * the double is out-of-range. C99 Section 6.3.1.5:
116 *
117 * If the value being converted is in the range of values that can
118 * be represented but cannot be represented exactly, the result is
119 * either the nearest higher or nearest lower representable value,
120 * chosen in an implementation-defined manner. If the value being
121 * converted is outside the range of values that can be represented,
122 * the behavior is undefined.
123 */
124 #if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
125 return (duk_float_t) x;
126 #else
127 duk_double_t t;
128
129 t = DUK_FABS(x);
130 DUK_ASSERT((DUK_ISNAN(x) && DUK_ISNAN(t)) ||
131 (!DUK_ISNAN(x) && !DUK_ISNAN(t)));
132
133 if (DUK_LIKELY(t <= DUK__FLOAT_MAX)) {
134 /* Standard in-range case, try to get here with a minimum
135 * number of checks and branches.
136 */
137 DUK_ASSERT(!DUK_ISNAN(x));
138 return (duk_float_t) x;
139 } else if (t <= DUK__FLOAT_ROUND_LIMIT) {
140 /* Out-of-range, but rounds to min/max float. */
141 DUK_ASSERT(!DUK_ISNAN(x));
142 if (x < 0.0) {
143 return (duk_float_t) -DUK__FLOAT_MAX;
144 } else {
145 return (duk_float_t) DUK__FLOAT_MAX;
146 }
147 } else if (DUK_ISNAN(x)) {
148 /* Assumes double NaN -> float NaN considered "in range". */
149 DUK_ASSERT(DUK_ISNAN(x));
150 return (duk_float_t) x;
151 } else {
152 /* Out-of-range, rounds to +/- Infinity. */
153 if (x < 0.0) {
154 return (duk_float_t) -DUK_DOUBLE_INFINITY;
155 } else {
156 return (duk_float_t) DUK_DOUBLE_INFINITY;
157 }
158 }
159 #endif
160 }
161