Skip to content
This repository has been archived by the owner on Aug 4, 2022. It is now read-only.

Commit

Permalink
Bug 1438212 - Implement mozilla::IsFloat32Representable using an algo…
Browse files Browse the repository at this point in the history
…rithm that handles NaN correctly and doesn't sometimes invoke undefined behavior. r=froydnj
  • Loading branch information
jswalden committed Jun 6, 2018
1 parent 9b91c65 commit 097a0aa
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 11 deletions.
30 changes: 26 additions & 4 deletions mfbt/FloatingPoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,36 @@

#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 */
12 changes: 5 additions & 7 deletions mfbt/FloatingPoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
76 changes: 76 additions & 0 deletions mfbt/tests/TestFloatingPoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<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
Expand All @@ -639,5 +714,6 @@ main()
TestExponentComponent();
TestPredicates();
TestAreApproximatelyEqual();
TestIsFloat32Representable();
return 0;
}

0 comments on commit 097a0aa

Please sign in to comment.