Bug 1438212 - Implement mozilla::IsFloat32Representable using an algorithm that handles NaN correctly and doesn't sometimes invoke undefined behavior. r=froydnj
authorJeff Walden <jwalden@mit.edu>
Wed, 06 Jun 2018 16:03:47 -0700
changeset 421862 b1521154cfec6b35f8266af53efb24ebac260173
parent 421861 9b5279a09e13b33f1e4e29e40e5aa968ef0f8fa6
child 421863 11c5f8019bca30e59f0a99df346b796d6d6f1aa6
push id104136
push userjwalden@mit.edu
push dateFri, 08 Jun 2018 05:56:42 +0000
treeherdermozilla-inbound@f4c9e858aff0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1438212
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1438212 - Implement mozilla::IsFloat32Representable using an algorithm that handles NaN correctly and doesn't sometimes invoke undefined behavior. r=froydnj
mfbt/FloatingPoint.cpp
mfbt/FloatingPoint.h
mfbt/tests/TestFloatingPoint.cpp
--- a/mfbt/FloatingPoint.cpp
+++ b/mfbt/FloatingPoint.cpp
@@ -3,19 +3,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 /* Implementations of FloatingPoint functions */
 
 #include "mozilla/FloatingPoint.h"
 
+#include <cfloat> // for FLT_MAX
+
 namespace mozilla {
 
 bool
-IsFloat32Representable(double aFloat32)
+IsFloat32Representable(double aValue)
 {
-  float asFloat = static_cast<float>(aFloat32);
-  double floatAsDouble = static_cast<double>(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<float>(aValue);
+
+  // Per [conv.fpprom] this never changes value.
+  auto valueAsFloatAsDouble = static_cast<double>(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 */
--- a/mfbt/FloatingPoint.h
+++ b/mfbt/FloatingPoint.h
@@ -557,22 +557,20 @@ FuzzyEqualsMultiplicative(T aValue1, T a
 {
   static_assert(IsFloatingPoint<T>::value, "floating point type required");
   // can't use std::min because of bug 965340
   T smaller = Abs(aValue1) < Abs(aValue2) ? Abs(aValue1) : Abs(aValue2);
   return Abs(aValue1 - aValue2) <= aEpsilon * smaller;
 }
 
 /**
- * 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 */
 
 #endif /* mozilla_FloatingPoint_h */
--- a/mfbt/tests/TestFloatingPoint.cpp
+++ b/mfbt/tests/TestFloatingPoint.cpp
@@ -10,16 +10,17 @@
 #include <float.h>
 #include <math.h>
 
 using mozilla::ExponentComponent;
 using mozilla::FloatingPoint;
 using mozilla::FuzzyEqualsAdditive;
 using mozilla::FuzzyEqualsMultiplicative;
 using mozilla::IsFinite;
+using mozilla::IsFloat32Representable;
 using mozilla::IsInfinite;
 using mozilla::IsNaN;
 using mozilla::IsNegative;
 using mozilla::IsNegativeZero;
 using mozilla::IsPositiveZero;
 using mozilla::NegativeInfinity;
 using mozilla::NumberEqualsInt32;
 using mozilla::NumberIsInt32;
@@ -625,19 +626,94 @@ TestDoublesAreApproximatelyEqual()
 
 static void
 TestAreApproximatelyEqual()
 {
   TestFloatsAreApproximatelyEqual();
   TestDoublesAreApproximatelyEqual();
 }
 
+static void
+TestIsFloat32Representable()
+{
+  // Zeroes are representable.
+  A(IsFloat32Representable(+0.0));
+  A(IsFloat32Representable(-0.0));
+
+  // NaN and infinities are representable.
+  A(IsFloat32Representable(UnspecifiedNaN<double>()));
+  A(IsFloat32Representable(SpecificNaN<double>(0, 1)));
+  A(IsFloat32Representable(SpecificNaN<double>(0, 71389)));
+  A(IsFloat32Representable(SpecificNaN<double>(0, (uint64_t(1) << 52) - 2)));
+  A(IsFloat32Representable(SpecificNaN<double>(1, 1)));
+  A(IsFloat32Representable(SpecificNaN<double>(1, 71389)));
+  A(IsFloat32Representable(SpecificNaN<double>(1, (uint64_t(1) << 52) - 2)));
+  A(IsFloat32Representable(PositiveInfinity<double>()));
+  A(IsFloat32Representable(NegativeInfinity<double>()));
+
+  // 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<double>::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
 main()
 {
   TestAreIdentical();
   TestExponentComponent();
   TestPredicates();
   TestAreApproximatelyEqual();
+  TestIsFloat32Representable();
   return 0;
 }