diff --git a/mfbt/FloatingPoint.cpp b/mfbt/FloatingPoint.cpp index 1ccae46002eb..97476784a301 100644 --- a/mfbt/FloatingPoint.cpp +++ b/mfbt/FloatingPoint.cpp @@ -8,14 +8,36 @@ #include "mozilla/FloatingPoint.h" +#include // for FLT_MAX + namespace mozilla { bool -IsFloat32Representable(double aFloat32) +IsFloat32Representable(double aValue) { - float asFloat = static_cast(aFloat32); - double floatAsDouble = static_cast(asFloat); - return floatAsDouble == aFloat32; + // NaNs and infinities are representable. + if (!IsFinite(aValue)) { + return true; + } + + // If it exceeds finite |float| range, casting to |double| is always undefined + // behavior per C++11 [conv.double]p1 last sentence. + if (Abs(aValue) > FLT_MAX) { + return false; + } + + // But if it's within finite range, then either it's 1) an exact value and so + // representable, or 2) it's "between two adjacent destination values" and + // safe to cast to "an implementation-defined choice of either of those + // values". + auto valueAsFloat = static_cast(aValue); + + // Per [conv.fpprom] this never changes value. + auto valueAsFloatAsDouble = static_cast(valueAsFloat); + + // Finally, in 1) exact representable value equals exact representable value, + // or 2) *changed* value does not equal original value, ergo unrepresentable. + return valueAsFloatAsDouble == aValue; } } /* namespace mozilla */ diff --git a/mfbt/FloatingPoint.h b/mfbt/FloatingPoint.h index 7f1d6e2f22ed..60e621b8ea6f 100644 --- a/mfbt/FloatingPoint.h +++ b/mfbt/FloatingPoint.h @@ -562,16 +562,14 @@ FuzzyEqualsMultiplicative(T aValue1, T aValue2, } /** - * Returns true if the given value can be losslessly represented as an IEEE-754 - * single format number, false otherwise. All NaN values are considered - * representable (notwithstanding that the exact bit pattern of a double format - * NaN value can't be exactly represented in single format). - * - * This function isn't inlined to avoid buggy optimizations by MSVC. + * Returns true if |aValue| can be losslessly represented as an IEEE-754 single + * precision number, false otherwise. All NaN values are considered + * representable (even though the bit patterns of double precision NaNs can't + * all be exactly represented in single precision). */ MOZ_MUST_USE extern MFBT_API bool -IsFloat32Representable(double aFloat32); +IsFloat32Representable(double aValue); } /* namespace mozilla */ diff --git a/mfbt/tests/TestFloatingPoint.cpp b/mfbt/tests/TestFloatingPoint.cpp index 0f54e69f4376..7cf4044b3781 100644 --- a/mfbt/tests/TestFloatingPoint.cpp +++ b/mfbt/tests/TestFloatingPoint.cpp @@ -15,6 +15,7 @@ using mozilla::FloatingPoint; using mozilla::FuzzyEqualsAdditive; using mozilla::FuzzyEqualsMultiplicative; using mozilla::IsFinite; +using mozilla::IsFloat32Representable; using mozilla::IsInfinite; using mozilla::IsNaN; using mozilla::IsNegative; @@ -630,6 +631,80 @@ TestAreApproximatelyEqual() TestDoublesAreApproximatelyEqual(); } +static void +TestIsFloat32Representable() +{ + // Zeroes are representable. + A(IsFloat32Representable(+0.0)); + A(IsFloat32Representable(-0.0)); + + // NaN and infinities are representable. + A(IsFloat32Representable(UnspecifiedNaN())); + A(IsFloat32Representable(SpecificNaN(0, 1))); + A(IsFloat32Representable(SpecificNaN(0, 71389))); + A(IsFloat32Representable(SpecificNaN(0, (uint64_t(1) << 52) - 2))); + A(IsFloat32Representable(SpecificNaN(1, 1))); + A(IsFloat32Representable(SpecificNaN(1, 71389))); + A(IsFloat32Representable(SpecificNaN(1, (uint64_t(1) << 52) - 2))); + A(IsFloat32Representable(PositiveInfinity())); + A(IsFloat32Representable(NegativeInfinity())); + + // 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(powf(2.0f, -150.0f) == 0.0); + A(powf(2.0f, -149.0f) != 0.0); + + for (double littleExp = -1074.0; littleExp < -149.0; littleExp++) { + // Powers of two below the available range aren't representable. + A(!IsFloat32Representable(pow(2.0, littleExp))); + } + + // Exact powers of two within the available range are representable. + for (double exponent = -149.0; exponent < 128.0; exponent++) { + A(IsFloat32Representable(pow(2.0, exponent))); + } + + // Powers of two above the available range aren't representable. + for (double bigExp = 128.0; bigExp < 1024.0; bigExp++) { + A(!IsFloat32Representable(pow(2.0, bigExp))); + } + + // Various denormal (i.e. super-small) doubles with MSB and LSB as far apart + // as possible are representable (but taken one bit further apart are not + // representable). + // + // Note that the final iteration tests non-denormal with exponent field + // containing (biased) 1, as |oneTooSmall| and |widestPossible| happen still + // to be correct for that exponent due to the extra bit of precision in the + // implicit-one bit. + double oneTooSmall = pow(2.0, -150.0); + for (double denormExp = -149.0; + denormExp < 1 - double(FloatingPoint::kExponentBias) + 1; + denormExp++) + { + double baseDenorm = pow(2.0, denormExp); + double tooWide = baseDenorm + oneTooSmall; + A(!IsFloat32Representable(tooWide)); + + double widestPossible = baseDenorm; + if (oneTooSmall * 2.0 != baseDenorm) { + widestPossible += oneTooSmall * 2.0; + } + + A(IsFloat32Representable(widestPossible)); + } + + // Finally, check certain interesting/special values for basic sanity. + A(!IsFloat32Representable(2147483647.0)); + A(!IsFloat32Representable(-2147483647.0)); +} + #undef A int @@ -639,5 +714,6 @@ main() TestExponentComponent(); TestPredicates(); TestAreApproximatelyEqual(); + TestIsFloat32Representable(); return 0; }