JD2022-TU1/main/extern/gear4/gear_testing/math/multiprecision.h

310 lines
10 KiB
C++

#pragma once
#ifndef _GEAR_TESTING__MATH__MULTIPRECISION_H_
#define _GEAR_TESTING__MATH__MULTIPRECISION_H_
#include <gear_testing/math/floatingpoint.h>
#if defined(min)
# undef min
#endif
#if defined(max)
# undef max
#endif
namespace G4
{
class MPNumeric
{
public:
MPNumeric(double value = 0.0)
: m_value(value)
{
// Note that we somewhat arbitrarily choose to give 0 the same error as 1
m_error = AsFloatingPoint(value!=0.0?value:1.0).ULPToAbsolute(2);
}
MPNumeric(float value)
: m_value(value)
{
// Note that we somewhat arbitrarily choose to give 0 the same error as 1
m_error = AsFloatingPoint(value!=0.0f?value:1.0f).ULPToAbsolute(2);
}
MPNumeric operator-() const
{
return MPNumeric(-m_value, m_error);
}
MPNumeric& operator+=(const MPNumeric& value)
{
m_value += value.m_value;
m_error += value.m_error;
return *this;
}
MPNumeric operator+(const MPNumeric& value) const
{
MPNumeric t(*this);
t += value;
return t;
}
MPNumeric& operator-=(const MPNumeric& value)
{
m_value -= value.m_value;
m_error += value.m_error;
return *this;
}
MPNumeric operator-(const MPNumeric& value) const
{
MPNumeric t(*this);
t -= value;
return t;
}
MPNumeric& operator*=(const MPNumeric& value)
{
m_error = AddRelativeError(value) * 1.2;
m_value *= value.m_value;
m_error *= std::abs(m_value);
return *this;
}
MPNumeric operator*(const MPNumeric& value) const
{
MPNumeric t(*this);
t *= value;
return t;
}
MPNumeric& operator/=(const MPNumeric& value)
{
if ( AsFloatingPoint( value.m_value ).IsInfinite() )
{
m_value = 0.0;
// Note that we somewhat arbitrarily choose to give 0 the same error as 1
m_error = AsFloatingPoint( 1.0 ).ULPToAbsolute(2);
return( *this );
}
// This is basically doing the same thing as interval arithmetic in an attempt to find good bounds
double lower[2];
lower[0] = m_value - m_error;
lower[1] = value.m_value - value.m_error;
double upper[2];
upper[0] = m_value + m_error;
upper[1] = value.m_value + value.m_error;
double interval[2];
double val = lower[0] / lower[ 1 ];
interval[ 0 ] = val;
interval[ 1 ] = val;
val = lower[0] / upper[ 1 ];
interval[ 0 ] = std::min( interval[ 0 ], val );
interval[ 1 ] = std::max( interval[ 1 ], val );
val = upper[0] / lower[ 1 ];
interval[ 0 ] = std::min( interval[ 0 ], val );
interval[ 1 ] = std::max( interval[ 1 ], val );
val = upper[0] / upper[ 1 ] ;
interval[ 0 ] = std::min( interval[ 0 ], val );
interval[ 1 ] = std::max( interval[ 1 ], val );
m_error = AddRelativeError(value);
m_value /= value.m_value;
m_error *= std::abs(m_value);
double intervalError = std::max( std::abs( m_value - interval[0] ), std::abs( m_value - interval[1] ) );
m_error = std::max( m_error, intervalError ); // Use nominal error value if the error determined by interval arithmetic is not tight enough
return *this;
}
MPNumeric operator/(const MPNumeric& value) const
{
MPNumeric t(*this);
t /= value;
return t;
}
MPNumeric Abs() const
{
return( MPNumeric( std::abs( m_value ), m_error ) );
}
MPNumeric Cosine() const
{
double value = std::cos(m_value);
double err1 = std::abs( value - std::cos( m_value + m_error ) );
double err2 = std::abs( value - std::cos( m_value - m_error ) );
double error = std::max( err1, err2 ) * 2.0;
double err3 = AsFloatingPoint(1.0f).ULPToAbsolute(2);
error = std::max( error, err3 ) * 2.0; // Use nominal error value if the error determined by interval arithmetic is too tight
return( MPNumeric( value, error ) );
}
MPNumeric Sine() const
{
double value = std::sin(m_value);
double err1 = std::abs( value - std::sin( m_value + m_error ) );
double err2 = std::abs( value - std::sin( m_value - m_error ) );
double error = std::max( err1, err2 ) * 2.0;
double err3 = AsFloatingPoint(1.0f).ULPToAbsolute(2);
error = std::max( error, err3 ) * 2.0; // Use nominal error value if the error determined by interval arithmetic is too tight
return( MPNumeric( value, error ) );
}
MPNumeric ArcCos() const
{
double value = std::acos(m_value);
double err1 = std::abs( value - std::acos( std::min( m_value + m_error, 1.0 ) ) );
double err2 = std::abs( value - std::acos( std::max( m_value - m_error, -1.0 ) ) );
double error = std::max( err1, err2 ) * 2.0;
double err3 = AsFloatingPoint(G4PI).ULPToAbsolute(2);
error = std::max( error, err3 ); // Use nominal error value if the error determined by interval arithmetic is too tight
return( MPNumeric( value, error ) );
}
MPNumeric ArcTan()
{
double value = std::atan( m_value );
// Determine error bounds in an interval arithmetic like manner
double error = std::abs( value - std::atan( m_value + m_error ) );
double err2 = std::abs( value - std::atan( m_value - m_error ) );
error = std::max ( error, err2 );
return( MPNumeric( value, error ) );
}
template < typename _SCALAR_TYPE >
static MPNumeric ArcTan2( const MPNumeric& numer, const MPNumeric& denom )
{
// The multiprecision ArcTan2 does not use standard library atan2. It is too difficult to determine a reasonable error bounds using only that function.
// Instead, we use the implementation below which is (basically) the same as the internals of some standard library implementations.
// This allows us to more accurately accumulate the error bounds.
MPNumeric temp = ( ( numer * numer + denom * denom ).Sqrt() - denom ) / numer;
MPNumeric result = MPNumeric( _SCALAR_TYPE( 2 ) ) * temp.ArcTan();
return( result );
}
MPNumeric Sqrt() const
{
// rsqrte usually return a 12 bits precision in the mantissa.
// This correspond to a relative error of 1/4096
// Note: we usually get a better precision however, because of
// Newton-Raphson iteration add to it.
double error = 2*GetRelativeError();
static const double sqrterr = 1./4096.;
if (error < sqrterr)
error = sqrterr;
double value = std::sqrt(m_value);
error *= std::abs( value );
if ( !IsTypeValid( value ) )
{
return( MPNumeric( value, error ) );
}
// This is basically doing the same thing as interval arithmetic in an attempt to find better bounds
double err1 = std::abs( value - std::sqrt( m_value + m_error ) );
double err2 = std::abs( value - std::sqrt( std::max( m_value - m_error, 0.0 ) ) );
double intervalError = std::max( err1, err2 ) * 2.0;
error = std::max( error, intervalError );
return( MPNumeric( value, error ) );
}
template <class T>
T GetValue() const
{
return static_cast<T>(m_value);
}
double GetRelativeError() const
{
if ( !IsTypeValid( m_value ) )
{
return( m_value );
}
// Not sure if we're doing the right thing with zero values here
return ( m_value != 0.0 ) ? m_error/std::abs(m_value) : m_error;
}
double GetAbsoluteError() const
{
return m_error;
}
template <class T>
T GetError() const
{
FloatingPoint<T> v(std::max<T>(std::abs(static_cast<T>(m_value)), 1.0f));
return std::max(static_cast<T>(m_error), v.ULPToAbsolute(1));
}
protected:
MPNumeric(double value, double error)
: m_value(value)
, m_error(error)
{}
double AddRelativeError(const MPNumeric& value) const
{
return GetRelativeError()+value.GetRelativeError();
}
private:
double m_value;
double m_error;
};
} // namspace G4
// Stream operations are not supported on SPU
namespace G4
{
#if !defined(__SPU__)
inline std::ostream& operator<<(std::ostream& os, const MPNumeric& value)
{
return os << "{ value: " << AsFloatingPoint(value.GetValue<double>())
<< ", error: " << value.GetAbsoluteError() << " }";
}
#endif
struct MPNumericPredFormat
{
template <class T>
::testing::AssertionResult operator()(const char* expExpr,
const char* resExpr,
G4::MPNumeric expected,
T result)
{
T diff = std::abs(expected.GetValue<T>()- result);
T err = expected.GetError<T>();
if (diff <= err)
return ::testing::AssertionSuccess();
return ::testing::AssertionFailure()
<< resExpr << " (" << AsFloatingPoint(result)
<< ") differed from " << expExpr << " (" << AsFloatingPoint(expected.GetValue<T>())
<< ") by " << diff
<< " which is greater than " << AsFloatingPoint(expected.GetError<T>());
}
};
} // namspace G4
#if !defined(__SPU__)
# define EXPECT_MP_EQ(TYPE, MP1, V2) ASSERT_PRED_FORMAT2(::G4::MPNumericPredFormat(), MP1, V2)
#else
# define EXPECT_MP_EQ(TYPE, MP1, V2) __EXPECT_CALL( ::G4::MPNumericPredFormat(), "", "", (MP1), (V2) )
# define ASSERT_MP_EQ(TYPE, MP1, V2) __ASSERT_CALL( EXPECT_MP_EQ, (MP1), (V2))
#endif
#endif // #ifndef _GEAR_TESTING__MATH__MULTIPRECISION_H_