cosmopolitan/third_party/duktape/duk_util_cast.c

161 lines
5.6 KiB
C

/*
* Cast helpers.
*
* C99+ coercion is challenging portability-wise because out-of-range casts
* may invoke implementation defined or even undefined behavior. See e.g.
* http://blog.frama-c.com/index.php?post/2013/10/09/Overflow-float-integer.
*
* Provide explicit cast helpers which try to avoid implementation defined
* or undefined behavior. These helpers can then be simplified in the vast
* majority of cases where the implementation defined or undefined behavior
* is not problematic.
*/
#include "third_party/duktape/duk_internal.h"
/* Portable double-to-integer cast which avoids undefined behavior and avoids
* relying on fmin(), fmax(), or other intrinsics. Out-of-range results are
* not assumed by caller, but here value is clamped, NaN converts to minval.
*/
#define DUK__DOUBLE_INT_CAST1(tname,minval,maxval) do { \
if (DUK_LIKELY(x >= (duk_double_t) (minval))) { \
DUK_ASSERT(!DUK_ISNAN(x)); \
if (DUK_LIKELY(x <= (duk_double_t) (maxval))) { \
return (tname) x; \
} else { \
return (tname) (maxval); \
} \
} else { \
/* NaN or below minval. Since we don't care about the result \
* for out-of-range values, just return the minimum value for \
* both. \
*/ \
return (tname) (minval); \
} \
} while (0)
/* Rely on specific NaN behavior for duk_double_{fmin,fmax}(): if either
* argument is a NaN, return the second argument. This avoids a
* NaN-to-integer cast which is undefined behavior.
*/
#define DUK__DOUBLE_INT_CAST2(tname,minval,maxval) do { \
return (tname) duk_double_fmin(duk_double_fmax(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
} while (0)
/* Another solution which doesn't need C99+ behavior for fmin() and fmax(). */
#define DUK__DOUBLE_INT_CAST3(tname,minval,maxval) do { \
if (DUK_ISNAN(x)) { \
/* 0 or any other value is fine. */ \
return (tname) 0; \
} else \
return (tname) DUK_FMIN(DUK_FMAX(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
} \
} while (0)
/* C99+ solution: relies on specific fmin() and fmax() behavior in C99: if
* one argument is NaN but the other isn't, the non-NaN argument is returned.
* Because the limits are non-NaN values, explicit NaN check is not needed.
* This may not work on all legacy platforms, and also doesn't seem to inline
* the fmin() and fmax() calls (unless one uses -ffast-math which we don't
* support).
*/
#define DUK__DOUBLE_INT_CAST4(tname,minval,maxval) do { \
return (tname) DUK_FMIN(DUK_FMAX(x, (duk_double_t) (minval)), (duk_double_t) (maxval)); \
} while (0)
DUK_INTERNAL duk_int_t duk_double_to_int_t(duk_double_t x) {
#if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
/* Real world solution: almost any practical platform will provide
* an integer value without any guarantees what it is (which is fine).
*/
return (duk_int_t) x;
#else
DUK__DOUBLE_INT_CAST1(duk_int_t, DUK_INT_MIN, DUK_INT_MAX);
#endif
}
DUK_INTERNAL duk_uint_t duk_double_to_uint_t(duk_double_t x) {
#if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
return (duk_uint_t) x;
#else
DUK__DOUBLE_INT_CAST1(duk_uint_t, DUK_UINT_MIN, DUK_UINT_MAX);
#endif
}
DUK_INTERNAL duk_int32_t duk_double_to_int32_t(duk_double_t x) {
#if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
return (duk_int32_t) x;
#else
DUK__DOUBLE_INT_CAST1(duk_int32_t, DUK_INT32_MIN, DUK_INT32_MAX);
#endif
}
DUK_INTERNAL duk_uint32_t duk_double_to_uint32_t(duk_double_t x) {
#if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
return (duk_uint32_t) x;
#else
DUK__DOUBLE_INT_CAST1(duk_uint32_t, DUK_UINT32_MIN, DUK_UINT32_MAX);
#endif
}
/* Largest IEEE double that doesn't round to infinity in the default rounding
* mode. The exact midpoint between (1 - 2^(-24)) * 2^128 and 2^128 rounds to
* infinity, at least on x64. This number is one double unit below that
* midpoint. See misc/float_cast.c.
*/
#define DUK__FLOAT_ROUND_LIMIT 340282356779733623858607532500980858880.0
/* Maximum IEEE float. Double-to-float conversion above this would be out of
* range and thus technically undefined behavior.
*/
#define DUK__FLOAT_MAX 340282346638528859811704183484516925440.0
DUK_INTERNAL duk_float_t duk_double_to_float_t(duk_double_t x) {
/* Even a double-to-float cast is technically undefined behavior if
* the double is out-of-range. C99 Section 6.3.1.5:
*
* If the value being converted is in the range of values that can
* be represented but cannot be represented exactly, the result is
* either the nearest higher or nearest lower representable value,
* chosen in an implementation-defined manner. If the value being
* converted is outside the range of values that can be represented,
* the behavior is undefined.
*/
#if defined(DUK_USE_ALLOW_UNDEFINED_BEHAVIOR)
return (duk_float_t) x;
#else
duk_double_t t;
t = DUK_FABS(x);
DUK_ASSERT((DUK_ISNAN(x) && DUK_ISNAN(t)) ||
(!DUK_ISNAN(x) && !DUK_ISNAN(t)));
if (DUK_LIKELY(t <= DUK__FLOAT_MAX)) {
/* Standard in-range case, try to get here with a minimum
* number of checks and branches.
*/
DUK_ASSERT(!DUK_ISNAN(x));
return (duk_float_t) x;
} else if (t <= DUK__FLOAT_ROUND_LIMIT) {
/* Out-of-range, but rounds to min/max float. */
DUK_ASSERT(!DUK_ISNAN(x));
if (x < 0.0) {
return (duk_float_t) -DUK__FLOAT_MAX;
} else {
return (duk_float_t) DUK__FLOAT_MAX;
}
} else if (DUK_ISNAN(x)) {
/* Assumes double NaN -> float NaN considered "in range". */
DUK_ASSERT(DUK_ISNAN(x));
return (duk_float_t) x;
} else {
/* Out-of-range, rounds to +/- Infinity. */
if (x < 0.0) {
return (duk_float_t) -DUK_DOUBLE_INFINITY;
} else {
return (duk_float_t) DUK_DOUBLE_INFINITY;
}
}
#endif
}