mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 744965 - Implement mozilla::NumberEqualsInt32 in a way that doesn't depend on undefined behavior casting an out-of-range floating point number to int32_t. r=froydnj
--HG-- extra : rebase_source : dc4781e2a31ee0e75fc62951cbdc71eaa9fd7b1c
This commit is contained in:
parent
4bc57cc3ec
commit
1b16de9738
@ -13,8 +13,11 @@
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
#include "mozilla/MemoryChecking.h"
|
||||
#include "mozilla/Types.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
|
||||
#include <limits>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mozilla {
|
||||
@ -36,26 +39,26 @@ namespace mozilla {
|
||||
|
||||
struct FloatTypeTraits
|
||||
{
|
||||
typedef uint32_t Bits;
|
||||
using Bits = uint32_t;
|
||||
|
||||
static const unsigned kExponentBias = 127;
|
||||
static const unsigned kExponentShift = 23;
|
||||
static constexpr unsigned kExponentBias = 127;
|
||||
static constexpr unsigned kExponentShift = 23;
|
||||
|
||||
static const Bits kSignBit = 0x80000000UL;
|
||||
static const Bits kExponentBits = 0x7F800000UL;
|
||||
static const Bits kSignificandBits = 0x007FFFFFUL;
|
||||
static constexpr Bits kSignBit = 0x80000000UL;
|
||||
static constexpr Bits kExponentBits = 0x7F800000UL;
|
||||
static constexpr Bits kSignificandBits = 0x007FFFFFUL;
|
||||
};
|
||||
|
||||
struct DoubleTypeTraits
|
||||
{
|
||||
typedef uint64_t Bits;
|
||||
using Bits = uint64_t;
|
||||
|
||||
static const unsigned kExponentBias = 1023;
|
||||
static const unsigned kExponentShift = 52;
|
||||
static constexpr unsigned kExponentBias = 1023;
|
||||
static constexpr unsigned kExponentShift = 52;
|
||||
|
||||
static const Bits kSignBit = 0x8000000000000000ULL;
|
||||
static const Bits kExponentBits = 0x7ff0000000000000ULL;
|
||||
static const Bits kSignificandBits = 0x000fffffffffffffULL;
|
||||
static constexpr Bits kSignBit = 0x8000000000000000ULL;
|
||||
static constexpr Bits kExponentBits = 0x7ff0000000000000ULL;
|
||||
static constexpr Bits kSignificandBits = 0x000fffffffffffffULL;
|
||||
};
|
||||
|
||||
template<typename T> struct SelectTrait;
|
||||
@ -91,8 +94,8 @@ template<> struct SelectTrait<double> : public DoubleTypeTraits {};
|
||||
template<typename T>
|
||||
struct FloatingPoint : public SelectTrait<T>
|
||||
{
|
||||
typedef SelectTrait<T> Base;
|
||||
typedef typename Base::Bits Bits;
|
||||
using Base = SelectTrait<T>;
|
||||
using Bits = typename Base::Bits;
|
||||
|
||||
static_assert((Base::kSignBit & Base::kExponentBits) == 0,
|
||||
"sign bit shouldn't overlap exponent bits");
|
||||
@ -328,38 +331,134 @@ MinNumberValue()
|
||||
return BitwiseCast<T>(Bits(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* If aValue is equal to some int32_t value, set *aInt32 to that value and
|
||||
* return true; otherwise return false.
|
||||
*
|
||||
* Note that negative zero is "equal" to zero here. To test whether a value can
|
||||
* be losslessly converted to int32_t and back, use NumberIsInt32 instead.
|
||||
*/
|
||||
template<typename T>
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
NumberEqualsInt32(T aValue, int32_t* aInt32)
|
||||
namespace detail {
|
||||
|
||||
template<typename Float, typename SignedInteger>
|
||||
inline bool
|
||||
NumberEqualsSignedInteger(Float aValue, SignedInteger* aInteger)
|
||||
{
|
||||
/*
|
||||
* XXX Casting a floating-point value that doesn't truncate to int32_t, to
|
||||
* int32_t, induces undefined behavior. We should definitely fix this
|
||||
* (bug 744965), but as apparently it "works" in practice, it's not a
|
||||
* pressing concern now.
|
||||
*/
|
||||
return aValue == (*aInt32 = int32_t(aValue));
|
||||
static_assert(IsSame<Float, float>::value || IsSame<Float, double>::value,
|
||||
"Float must be an IEEE-754 floating point type");
|
||||
static_assert(IsSigned<SignedInteger>::value,
|
||||
"this algorithm only works for signed types: a different one "
|
||||
"will be required for unsigned types");
|
||||
static_assert(sizeof(SignedInteger) >= sizeof(int),
|
||||
"this function *might* require some finessing for signed types "
|
||||
"subject to integral promotion before it can be used on them");
|
||||
|
||||
MOZ_MAKE_MEM_UNDEFINED(aInteger, sizeof(*aInteger));
|
||||
|
||||
// NaNs and infinities are not integers.
|
||||
if (!IsFinite(aValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise do direct comparisons against the minimum/maximum |SignedInteger|
|
||||
// values that can be encoded in |Float|.
|
||||
|
||||
constexpr SignedInteger MaxIntValue =
|
||||
std::numeric_limits<SignedInteger>::max(); // e.g. INT32_MAX
|
||||
constexpr SignedInteger MinValue =
|
||||
std::numeric_limits<SignedInteger>::min(); // e.g. INT32_MIN
|
||||
|
||||
static_assert(IsPowerOfTwo(Abs(MinValue)),
|
||||
"MinValue should be is a small power of two, thus exactly "
|
||||
"representable in float/double both");
|
||||
|
||||
constexpr unsigned SignedIntegerWidth = CHAR_BIT * sizeof(SignedInteger);
|
||||
constexpr unsigned ExponentShift = FloatingPoint<Float>::kExponentShift;
|
||||
|
||||
// Careful! |MaxIntValue| may not be the maximum |SignedInteger| value that
|
||||
// can be encoded in |Float|. Its |SignedIntegerWidth - 1| bits of precision
|
||||
// may exceed |Float|'s |ExponentShift + 1| bits of precision. If necessary,
|
||||
// compute the maximum |SignedInteger| that fits in |Float| from IEEE-754
|
||||
// first principles. (|MinValue| doesn't have this problem because as a
|
||||
// [relatively] small power of two it's always representable in |Float|.)
|
||||
|
||||
// Per C++11 [expr.const]p2, unevaluated subexpressions of logical AND/OR and
|
||||
// conditional expressions *may* contain non-constant expressions, without
|
||||
// making the enclosing expression not constexpr. MSVC implements this -- but
|
||||
// it sometimes warns about undefined behavior in unevaluated subexpressions.
|
||||
// This bites us if we initialize |MaxValue| the obvious way including an
|
||||
// |uint64_t(1) << (SignedIntegerWidth - 2 - ExponentShift)| subexpression.
|
||||
// Pull that shift-amount out and give it a not-too-huge value when it's in an
|
||||
// unevaluated subexpression. 🙄
|
||||
constexpr unsigned PrecisionExceededShiftAmount =
|
||||
ExponentShift > SignedIntegerWidth - 1
|
||||
? 0
|
||||
: SignedIntegerWidth - 2 - ExponentShift;
|
||||
|
||||
constexpr SignedInteger MaxValue =
|
||||
ExponentShift > SignedIntegerWidth - 1
|
||||
? MaxIntValue
|
||||
: SignedInteger((uint64_t(1) << (SignedIntegerWidth - 1)) -
|
||||
(uint64_t(1) << PrecisionExceededShiftAmount));
|
||||
|
||||
if (static_cast<Float>(MinValue) <= aValue &&
|
||||
aValue <= static_cast<Float>(MaxValue))
|
||||
{
|
||||
auto possible = static_cast<SignedInteger>(aValue);
|
||||
if (static_cast<Float>(possible) == aValue) {
|
||||
*aInteger = possible;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename Float, typename SignedInteger>
|
||||
inline bool
|
||||
NumberIsSignedInteger(Float aValue, SignedInteger* aInteger)
|
||||
{
|
||||
static_assert(IsSame<Float, float>::value || IsSame<Float, double>::value,
|
||||
"Float must be an IEEE-754 floating point type");
|
||||
static_assert(IsSigned<SignedInteger>::value,
|
||||
"this algorithm only works for signed types: a different one "
|
||||
"will be required for unsigned types");
|
||||
static_assert(sizeof(SignedInteger) >= sizeof(int),
|
||||
"this function *might* require some finessing for signed types "
|
||||
"subject to integral promotion before it can be used on them");
|
||||
|
||||
MOZ_MAKE_MEM_UNDEFINED(aInteger, sizeof(*aInteger));
|
||||
|
||||
if (IsNegativeZero(aValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return NumberEqualsSignedInteger(aValue, aInteger);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* If d can be converted to int32_t and back to an identical double value,
|
||||
* set *aInt32 to that value and return true; otherwise return false.
|
||||
* If |aValue| is identical to some |int32_t| value, set |*aInt32| to that value
|
||||
* and return true. Otherwise return false, leaving |*aInt32| in an
|
||||
* indeterminate state.
|
||||
*
|
||||
* The difference between this and NumberEqualsInt32 is that this method returns
|
||||
* false for negative zero.
|
||||
* This method returns false for negative zero. If you want to consider -0 to
|
||||
* be 0, use NumberEqualsInt32 below.
|
||||
*/
|
||||
template<typename T>
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
NumberIsInt32(T aValue, int32_t* aInt32)
|
||||
{
|
||||
return !IsNegativeZero(aValue) && NumberEqualsInt32(aValue, aInt32);
|
||||
return detail::NumberIsSignedInteger(aValue, aInt32);
|
||||
}
|
||||
|
||||
/**
|
||||
* If |aValue| is equal to some int32_t value (where -0 and +0 are considered
|
||||
* equal), set |*aInt32| to that value and return true. Otherwise return false,
|
||||
* leaving |*aInt32| in an indeterminate state.
|
||||
*
|
||||
* |NumberEqualsInt32(-0.0, ...)| will return true. To test whether a value can
|
||||
* be losslessly converted to |int32_t| and back, use NumberIsInt32 above.
|
||||
*/
|
||||
template<typename T>
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
NumberEqualsInt32(T aValue, int32_t* aInt32)
|
||||
{
|
||||
return detail::NumberEqualsSignedInteger(aValue, aInt32);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,10 +116,10 @@ template<> struct AbsReturnType<long double> { typedef long double Type; };
|
||||
} // namespace detail
|
||||
|
||||
template<typename T>
|
||||
inline typename detail::AbsReturnType<T>::Type
|
||||
inline constexpr typename detail::AbsReturnType<T>::Type
|
||||
Abs(const T aValue)
|
||||
{
|
||||
typedef typename detail::AbsReturnType<T>::Type ReturnType;
|
||||
using ReturnType = typename detail::AbsReturnType<T>::Type;
|
||||
return aValue >= 0 ? ReturnType(aValue) : ~ReturnType(aValue) + 1;
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,10 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "mozilla/Compiler.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
|
||||
#include <float.h>
|
||||
#include <math.h>
|
||||
|
||||
using mozilla::ExponentComponent;
|
||||
@ -319,13 +321,42 @@ TestDoublesPredicates()
|
||||
A(i == INT32_MIN);
|
||||
A(NumberEqualsInt32(double(INT32_MAX), &i));
|
||||
A(i == INT32_MAX);
|
||||
|
||||
// MSVC seems to compile 2**-1075, which should be half of the smallest
|
||||
// IEEE-754 double precision value, to equal 2**-1074 right now. This might
|
||||
// be the result of a missing compiler flag to force more-accurate floating
|
||||
// point calculations; bug 1440184 has been filed as a followup to fix this,
|
||||
// so that only the first half of this condition is necessary.
|
||||
A(pow(2.0, -1075.0) == 0.0 ||
|
||||
(MOZ_IS_MSVC && pow(2.0, -1075.0) == pow(2.0, -1074.0)));
|
||||
|
||||
A(pow(2.0, -1074.0) != 0.0);
|
||||
A(!NumberIsInt32(pow(2.0, -1074.0), &i));
|
||||
A(!NumberIsInt32(2 * pow(2.0, -1074.0), &i));
|
||||
A(!NumberIsInt32(0.5, &i));
|
||||
A(1.0 - pow(2.0, -54.0) == 1.0);
|
||||
A(1.0 - pow(2.0, -53.0) != 1.0);
|
||||
A(!NumberIsInt32(1.0 - pow(2.0, -53.0), &i));
|
||||
A(!NumberIsInt32(1.0 - pow(2.0, -52.0), &i));
|
||||
A(1.0 + pow(2.0, -53.0) == 1.0f);
|
||||
A(1.0 + pow(2.0, -52.0) != 1.0f);
|
||||
A(!NumberIsInt32(1.0 + pow(2.0, -52.0), &i));
|
||||
A(!NumberIsInt32(1.5f, &i));
|
||||
A(!NumberIsInt32(-double(2147483649), &i));
|
||||
A(!NumberIsInt32(double(2147483648), &i));
|
||||
A(!NumberIsInt32(-double(1ULL << 52) + 0.5, &i));
|
||||
A(!NumberIsInt32(double(1ULL << 52) - 0.5, &i));
|
||||
A(!NumberIsInt32(double(2147483648), &i));
|
||||
A(!NumberIsInt32(double(INT32_MAX) + 0.1, &i));
|
||||
A(!NumberIsInt32(double(INT32_MIN) - 0.1, &i));
|
||||
A(!NumberIsInt32(NegativeInfinity<double>(), &i));
|
||||
A(!NumberIsInt32(PositiveInfinity<double>(), &i));
|
||||
A(!NumberIsInt32(UnspecifiedNaN<double>(), &i));
|
||||
A(!NumberEqualsInt32(0.5, &i));
|
||||
A(!NumberEqualsInt32(-double(2147483649), &i));
|
||||
A(!NumberEqualsInt32(double(2147483648), &i));
|
||||
A(!NumberEqualsInt32(-double(1ULL << 52) + 0.5, &i));
|
||||
A(!NumberEqualsInt32(double(1ULL << 52) - 0.5, &i));
|
||||
A(!NumberEqualsInt32(double(INT32_MAX) + 0.1, &i));
|
||||
A(!NumberEqualsInt32(double(INT32_MIN) - 0.1, &i));
|
||||
A(!NumberEqualsInt32(NegativeInfinity<double>(), &i));
|
||||
@ -401,18 +432,38 @@ TestFloatsPredicates()
|
||||
A(i == 0);
|
||||
A(NumberIsInt32(float(INT32_MIN), &i));
|
||||
A(i == INT32_MIN);
|
||||
A(NumberIsInt32(float(2147483648 - 128), &i)); // max int32_t fitting in float
|
||||
A(i == 2147483648 - 128);
|
||||
A(NumberIsInt32(float(BIG), &i));
|
||||
A(i == BIG);
|
||||
A(NumberEqualsInt32(float(INT32_MIN), &i));
|
||||
A(i == INT32_MIN);
|
||||
A(NumberEqualsInt32(float(BIG), &i));
|
||||
A(i == BIG);
|
||||
A(powf(2.0f, -150.0f) == 0.0f);
|
||||
A(powf(2.0f, -149.0f) != 0.0f);
|
||||
A(!NumberIsInt32(powf(2.0f, -149.0f), &i));
|
||||
A(!NumberIsInt32(2 * powf(2.0f, -149.0f), &i));
|
||||
A(!NumberIsInt32(0.5f, &i));
|
||||
A(1.0f - powf(2.0f, -25.0f) == 1.0f);
|
||||
A(1.0f - powf(2.0f, -24.0f) != 1.0f);
|
||||
A(!NumberIsInt32(1.0f - powf(2.0f, -24.0f), &i));
|
||||
A(!NumberIsInt32(1.0f - powf(2.0f, -23.0f), &i));
|
||||
A(1.0f + powf(2.0f, -24.0f) == 1.0f);
|
||||
A(1.0f + powf(2.0f, -23.0f) != 1.0f);
|
||||
A(!NumberIsInt32(1.0f + powf(2.0f, -23.0f), &i));
|
||||
A(!NumberIsInt32(1.5f, &i));
|
||||
A(!NumberIsInt32(-float(2147483648) - 256, &i));
|
||||
A(!NumberIsInt32(float(2147483648), &i));
|
||||
A(!NumberIsInt32(float(2147483648) + 256, &i));
|
||||
A(!NumberIsInt32(float(BIG) + 0.1f, &i));
|
||||
A(!NumberIsInt32(NegativeInfinity<float>(), &i));
|
||||
A(!NumberIsInt32(PositiveInfinity<float>(), &i));
|
||||
A(!NumberIsInt32(UnspecifiedNaN<float>(), &i));
|
||||
A(!NumberEqualsInt32(0.5f, &i));
|
||||
A(!NumberEqualsInt32(-float(2147483648 + 256), &i));
|
||||
A(!NumberEqualsInt32(float(2147483648), &i));
|
||||
A(!NumberEqualsInt32(float(2147483648 + 256), &i));
|
||||
A(!NumberEqualsInt32(float(BIG) + 0.1f, &i));
|
||||
A(!NumberEqualsInt32(NegativeInfinity<float>(), &i));
|
||||
A(!NumberEqualsInt32(PositiveInfinity<float>(), &i));
|
||||
|
Loading…
Reference in New Issue
Block a user