356 lines
11 KiB
C++
356 lines
11 KiB
C++
#pragma once
|
|
|
|
#ifndef _GEAR_TESTING__MATH__FLOATINGPOINT_H_
|
|
#define _GEAR_TESTING__MATH__FLOATINGPOINT_H_
|
|
|
|
#include <gear_testing/gear_testing.h>
|
|
#include <ealdef.h>
|
|
#include <assert.h>
|
|
|
|
#if ((G4_PLATFORM == G4_PLATFORM_LINUX) || (G4_PLATFORM == G4_PLATFORM_MAC) || (G4_PLATFORM == G4_PLATFORM_IOS))
|
|
#include <cstdlib>
|
|
#include <cmath>
|
|
#endif
|
|
|
|
#ifdef G4_PROD_DEFAULT_EPSILON_F32
|
|
static const float G4_TESTS_EPSILON_F32 = G4_PROD_DEFAULT_EPSILON_F32;
|
|
#else
|
|
static const float G4_TESTS_EPSILON_F32 = 0.000001f;
|
|
#endif
|
|
|
|
#ifdef G4_PROD_DEFAULT_EPSILON_F64
|
|
static const double G4_TESTS_EPSILON_F64 = G4_PROD_DEFAULT_EPSILON_F64;
|
|
#else
|
|
static const double G4_TESTS_EPSILON_F64 = 0.00000001;
|
|
#endif
|
|
|
|
#ifdef G4_PROD_DEFAULT_EPSILON_SIMD_F32
|
|
static const float G4_TESTS_EPSILON_SIMD_F32 = G4_PROD_DEFAULT_EPSILON_SIMD_F32;
|
|
#else
|
|
static const float G4_TESTS_EPSILON_SIMD_F32 = 0.000001f;
|
|
#endif
|
|
|
|
namespace G4
|
|
{
|
|
namespace Private
|
|
{
|
|
template <class T>
|
|
struct FloatingPointBaseTraits;
|
|
|
|
template <>
|
|
struct FloatingPointBaseTraits<double>
|
|
{
|
|
typedef double FloatType;
|
|
typedef eal_u64 UnsignedType;
|
|
typedef eal_s64 SignedType;
|
|
static const size_t s_BITS = sizeof(UnsignedType) * 8;
|
|
static const size_t s_EXPONENTBITS = 11;
|
|
static const size_t s_FRACTIONBITS = s_BITS - s_EXPONENTBITS - 1;
|
|
static G4_CONSTEXPR double GetTestsEpsilon() { return G4_TESTS_EPSILON_F64; }
|
|
};
|
|
|
|
template <>
|
|
struct FloatingPointBaseTraits<float>
|
|
{
|
|
typedef float FloatType;
|
|
typedef eal_u32 UnsignedType;
|
|
typedef eal_s32 SignedType;
|
|
static const size_t s_BITS = sizeof(UnsignedType) * 8;
|
|
static const size_t s_EXPONENTBITS = 8;
|
|
static const size_t s_FRACTIONBITS = s_BITS - s_EXPONENTBITS - 1;
|
|
static G4_CONSTEXPR float GetTestsEpsilon() { return G4_TESTS_EPSILON_F32; }
|
|
};
|
|
}
|
|
|
|
template <class T>
|
|
class FloatingPoint
|
|
{
|
|
public:
|
|
typedef Private::FloatingPointBaseTraits<T> BaseTraits;
|
|
typedef typename BaseTraits::FloatType FloatType;
|
|
typedef typename BaseTraits::UnsignedType UnsignedType;
|
|
typedef typename BaseTraits::SignedType SignedType;
|
|
|
|
static const size_t s_BITS = BaseTraits::s_BITS;
|
|
static const size_t s_EXPONENTBITS = BaseTraits::s_EXPONENTBITS;
|
|
static const size_t s_FRACTIONBITS = BaseTraits::s_FRACTIONBITS;
|
|
|
|
static const UnsignedType s_SIGNMASK = UnsignedType(1) << (s_BITS-1);
|
|
static const UnsignedType s_EXPONENTMASK = ((UnsignedType(1) << s_EXPONENTBITS) - 1) << (s_BITS - s_EXPONENTBITS - 1);
|
|
static const UnsignedType s_FRACTIONMASK = (UnsignedType(1) << s_FRACTIONBITS) - 1;
|
|
static const SignedType s_BIAS = (SignedType(1) << (s_EXPONENTBITS - 1)) - 1;
|
|
|
|
static const UnsignedType s_INFINITY = s_EXPONENTMASK;
|
|
static const UnsignedType s_NINFINITY = s_EXPONENTMASK | s_SIGNMASK;
|
|
static const UnsignedType s_NAN = s_EXPONENTMASK | UnsignedType(1);
|
|
|
|
explicit FloatingPoint(FloatType v = FloatType(0))
|
|
{
|
|
m_value.m_float = v;
|
|
}
|
|
explicit FloatingPoint(UnsignedType v)
|
|
{
|
|
m_value.m_unsigned = v;
|
|
}
|
|
explicit FloatingPoint(SignedType v)
|
|
{
|
|
if (v < 0)
|
|
{
|
|
m_value.m_unsigned = -v;
|
|
m_value.m_unsigned |= s_SIGNMASK;
|
|
}
|
|
else
|
|
m_value.m_unsigned = v;
|
|
}
|
|
|
|
UnsignedType GetUnsigned() const
|
|
{
|
|
return m_value.m_unsigned;
|
|
}
|
|
|
|
SignedType GetSigned() const
|
|
{
|
|
// Float used a bit-signed representation.
|
|
// The valid signed representation
|
|
UnsignedType u = GetUnsigned();
|
|
if (u & s_SIGNMASK)
|
|
return (~u) + 1;
|
|
return u;
|
|
}
|
|
FloatType GetReal() const
|
|
{
|
|
return m_value.m_float;
|
|
}
|
|
operator UnsignedType() const
|
|
{
|
|
return m_value.m_unsigned;
|
|
}
|
|
operator SignedType() const
|
|
{
|
|
return GetSigned();
|
|
}
|
|
operator FloatType() const
|
|
{
|
|
return m_value.m_float;
|
|
}
|
|
|
|
bool IsPositive() const
|
|
{
|
|
return (GetUnsigned() & s_SIGNMASK) == 0u;
|
|
}
|
|
|
|
bool IsNAN() const
|
|
{
|
|
UnsignedType v = GetUnsigned();
|
|
return
|
|
((v & s_EXPONENTMASK) == s_EXPONENTMASK)
|
|
&& ((v & s_FRACTIONMASK) != 0);
|
|
}
|
|
|
|
bool IsInfinite() const
|
|
{
|
|
UnsignedType v = GetUnsigned();
|
|
return
|
|
((v & s_EXPONENTMASK) == s_EXPONENTMASK)
|
|
&& ((v & s_FRACTIONMASK) == 0);
|
|
}
|
|
|
|
bool IsSubnominal() const
|
|
{
|
|
UnsignedType v = GetUnsigned();
|
|
return (v & s_EXPONENTMASK) == 0;
|
|
}
|
|
|
|
UnsignedType GetBiasedExponent() const
|
|
{
|
|
return (GetUnsigned() & s_EXPONENTMASK) >> s_FRACTIONBITS;
|
|
}
|
|
|
|
SignedType GetExponent() const
|
|
{
|
|
SignedType exp = GetBiasedExponent();
|
|
assert(exp <= 2*s_BIAS+1);
|
|
assert(exp >= 0);
|
|
return exp - s_BIAS;
|
|
}
|
|
|
|
void SetBiasedExponent(UnsignedType newExp)
|
|
{
|
|
m_value.m_unsigned &= ~s_EXPONENTMASK;
|
|
m_value.m_unsigned |= newExp << s_FRACTIONBITS;
|
|
}
|
|
|
|
void SetExponent(SignedType newExp)
|
|
{
|
|
assert(newExp + s_BIAS >= 0);
|
|
assert(newExp + s_BIAS <= static_cast<SignedType>(s_EXPONENTMASK >> s_FRACTIONBITS));
|
|
SetBiasedExponent(newExp + s_BIAS);
|
|
}
|
|
|
|
bool IsValid() const
|
|
{
|
|
// No infinite, no NAN
|
|
if (IsInfinite() || IsNAN())
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Return true if v is atmost the maxULPth neighborhood of this.
|
|
/// Always return false if this or v is NAN.
|
|
/// Please, note that, by this definition, FLOAT_MAX is very near INFINITY.
|
|
/// maxULP stands for maximum Units in Last Position, one unit being the maximum represent precision
|
|
/// of this float for this magnitude. More explanation is available here:
|
|
/// http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
|
|
bool AlmostEquals(const FloatingPoint<T>& v, const SignedType maxULP = 4) const
|
|
{
|
|
assert(maxULP > 0);
|
|
if (IsNAN() || v.IsNAN()) return false;
|
|
return std::abs(v.GetSigned() - GetSigned()) < maxULP;
|
|
}
|
|
|
|
/// Return an approximate relative error for the specified maxULP.
|
|
static FloatType ULPToRelative(UnsignedType maxULP)
|
|
{
|
|
// 1.0f convert exactly in double, so no precautions need here.
|
|
static const UnsignedType One = FloatingPoint(1.0f).GetUnsigned();
|
|
return FloatingPoint(One | maxULP).GetReal()-FloatType(1.0f);
|
|
}
|
|
|
|
FloatType ULPToAbsolute(UnsignedType maxULP) const
|
|
{
|
|
return ULPToRelative(maxULP) * std::abs(GetReal());
|
|
}
|
|
|
|
static UnsignedType RelativeToULP(FloatType err)
|
|
{
|
|
return FloatingPoint(err).GetUnsigned() & s_FRACTIONMASK;
|
|
}
|
|
|
|
UnsignedType AbsoluteToULP(FloatType err) const
|
|
{
|
|
return RelativeToULP((std::abs(GetReal()) + err)/std::abs(GetReal()));
|
|
}
|
|
|
|
static FloatingPoint GetNAN()
|
|
{
|
|
return FloatingPoint(s_NAN);
|
|
}
|
|
|
|
static FloatingPoint GetInfinity()
|
|
{
|
|
return FloatingPoint(s_INFINITY);
|
|
}
|
|
|
|
static FloatingPoint GetNInfinity()
|
|
{
|
|
return FloatingPoint(s_NINFINITY);
|
|
}
|
|
|
|
static FloatType GetTestsEpsilon()
|
|
{
|
|
return BaseTraits::GetTestsEpsilon();
|
|
}
|
|
|
|
private:
|
|
union {
|
|
FloatType m_float;
|
|
UnsignedType m_unsigned;
|
|
} m_value;
|
|
};
|
|
|
|
template <class FType>
|
|
inline FloatingPoint<FType> AsFloatingPoint(FType v)
|
|
{
|
|
return FloatingPoint<FType>(v);
|
|
}
|
|
|
|
inline FloatingPoint<float> AsFloatingPoint(FloatingPoint<float> v)
|
|
{
|
|
return v;
|
|
}
|
|
|
|
inline FloatingPoint<double> AsFloatingPoint(FloatingPoint<double> v)
|
|
{
|
|
return v;
|
|
}
|
|
|
|
// Stream operations are not supported on SPU
|
|
#if !defined(__SPU__)
|
|
template <class T>
|
|
std::ostream& operator<<(std::ostream& os, const FloatingPoint<T>& v)
|
|
{
|
|
return os << v.GetReal() << " [0x" << std::hex << v.GetUnsigned() << std::dec << "]";
|
|
}
|
|
#endif
|
|
|
|
template <class T>
|
|
bool FPAlmostEquals(const FloatingPoint<T>& v1, const FloatingPoint<T>& v2,
|
|
const typename FloatingPoint<T>::UnsignedType maxULP)
|
|
{
|
|
const typename FloatingPoint<T>::UnsignedType realULP = maxULP*32/4;
|
|
if (v1.AlmostEquals(v2, realULP))
|
|
return true;
|
|
static const T minEpsilon = FloatingPoint<T>(1.0f).ULPToAbsolute(maxULP);
|
|
return std::abs(v1.GetReal() - v2.GetReal()) < minEpsilon;
|
|
};
|
|
|
|
template <typename FType>
|
|
inline typename FloatingPoint<FType>::UnsignedType
|
|
FPToBits(FType f)
|
|
{
|
|
FloatingPoint<FType> conv(f);
|
|
|
|
return conv.GetUnsigned();
|
|
}
|
|
|
|
// Stream operations are not supported on SPU
|
|
#if !defined(__SPU__)
|
|
template <typename FType>
|
|
inline std::ostream& PrintFloat(std::ostream& os, FType f)
|
|
{
|
|
if (f == f)
|
|
return os << f << " [" << std::hex << FPToBits(f) << std::dec << "]";
|
|
return os << "INV." << std::hex << FPToBits(f) << std::dec;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !defined(__SPU__)
|
|
|
|
#define EXPECT_FP_NEAR(T, V1, V2, ULP) EXPECT_PRED3(G4::FPAlmostEquals<T>, G4::FloatingPoint<T>(V1), G4::FloatingPoint<T>(V2), ULP)
|
|
#define EXPECT_FP_EQ(T, V1, V2) EXPECT_FP_NEAR(T, V1, V2, 4)
|
|
|
|
#else
|
|
|
|
namespace SPUTesting
|
|
{
|
|
struct FPAlmostEquals
|
|
{
|
|
template <typename T>
|
|
::testing::AssertionResult operator()(
|
|
const ::G4::FloatingPoint<T>& v1,
|
|
const ::G4::FloatingPoint<T>& v2,
|
|
const typename ::G4::FloatingPoint<T>::UnsignedType maxULP)
|
|
{
|
|
if(::G4::FPAlmostEquals<T>(v1, v2, maxULP))
|
|
return ::testing::AssertionSuccess();
|
|
|
|
return ::testing::AssertionFailure();
|
|
}
|
|
};
|
|
}
|
|
|
|
#define EXPECT_FP_NEAR(T, V1, V2, ULP) \
|
|
__EXPECT_CALL( \
|
|
SPUTesting::FPAlmostEquals(), \
|
|
G4::FloatingPoint<T>((V1)), \
|
|
G4::FloatingPoint<T>((V2)), \
|
|
(ULP))
|
|
|
|
#define EXPECT_FP_EQ(T, V1, V2) EXPECT_FP_NEAR(T, V1, V2, 4)
|
|
|
|
#endif // #if !defined(__SPU__)
|
|
|
|
#endif
|