两个问题:
差异从何而来
Boost Multiprecision 使用模板表达式来推迟评估。
此外,您正在选择一些不能以 10 为底数精确表示的有理分数(cpp_dec_float 使用十进制,因此以 10 为底数)。
这意味着当你做
T x = 1/3;
T y = 1/7;
这实际上会不精确地近似这两个分数。
这样做:
T z = 1/3 * 1/7;
实际上会计算右侧的表达式 template,所以不是像以前那样计算临时x
变量y
,右侧有一个类型:
expression<detail::multiplies, detail::expression<?>, detail::expression<?>, [2 * ...]>
这是从实际类型缩短的:
boost::multiprecision::detail::expression<
boost::multiprecision::detail::multiplies,
boost::multiprecision::detail::expression<
boost::multiprecision::detail::divide_immediates,
boost::multiprecision::number<boost::multiprecision::backends::cpp_dec_float<50u,
int, void>, (boost::multiprecision::expression_template_option)1>, int,
void, void>,
boost::multiprecision::detail::expression<
boost::multiprecision::detail::divide_immediates,
boost::multiprecision::number<boost::multiprecision::backends::cpp_dec_float<50u,
int, void>, (boost::multiprecision::expression_template_option)1>, int,
void, void>,
void, void>
长话短说,这就是您想要的,因为它可以节省您的工作并保持更好的准确性,因为表达式首先被标准化为1/(3*7)
so 1/21
。
这就是你的差异首先来自哪里。通过以下任一方式修复它:
关闭表达式模板
using T = boost::multiprecision::number<
boost::multiprecision::cpp_dec_float<50>,
boost::multiprecision::et_off > >;
将表达式重写为等效于您的实现:
T expected_area = T(ONE / 7) * T(ONE / 2);
T expected_area = (ONE / 7).eval() * (ONE / 2).eval();
应用公差
我发现很难解析 Boost Unit Test 文档,但这里有经验数据:
BOOST_CHECK_EQUAL(expected_area, R.getArea());
T const eps = std::numeric_limits<T>::epsilon();
BOOST_CHECK_CLOSE(expected_area, R.getArea(), eps);
BOOST_TEST(expected_area == R.getArea(), tt::tolerance(eps));
这失败了第一个,并通过了最后两个。确实,除此之外,以下两个也失败了:
BOOST_CHECK_EQUAL(expected_area, R.getArea());
BOOST_TEST(expected_area == R.getArea());
所以看起来在utf::tolerance
装饰器生效之前必须做一些事情。使用本机双精度进行测试告诉我,仅BOOST_TEST
隐式应用容差。所以潜入了预处理扩展:
::boost::unit_test::unit_test_log.set_checkpoint(
::boost::unit_test::const_string(
"/home/sehe/Projects/stackoverflow/test.cpp",
sizeof("/home/sehe/Projects/stackoverflow/test.cpp") - 1),
static_cast<std::size_t>(42));
::boost::test_tools::tt_detail::report_assertion(
(::boost::test_tools::assertion::seed()->*a == b).evaluate(),
(::boost::unit_test::lazy_ostream::instance()
<< ::boost::unit_test::const_string("a == b", sizeof("a == b") - 1)),
::boost::unit_test::const_string(
"/home/sehe/Projects/stackoverflow/test.cpp",
sizeof("/home/sehe/Projects/stackoverflow/test.cpp") - 1),
static_cast<std::size_t>(42), ::boost::test_tools::tt_detail::CHECK,
::boost::test_tools::tt_detail::CHECK_BUILT_ASSERTION, 0);
} while (::boost::test_tools::tt_detail::dummy_cond());
深入挖掘,我遇到了:
/*!@brief Indicates if a type can be compared using a tolerance scheme
*
* This is a metafunction that should evaluate to @c mpl::true_ if the type
* @c T can be compared using a tolerance based method, typically for floating point
* types.
*
* This metafunction can be specialized further to declare user types that are
* floating point (eg. boost.multiprecision).
*/
template <typename T>
struct tolerance_based : tolerance_based_delegate<T, !is_array<T>::value && !is_abstract_class_or_function<T>::value>::type {};
我们有它!但不是,
static_assert(boost::math::fpc::tolerance_based<double>::value);
static_assert(boost::math::fpc::tolerance_based<cpp_dec_float_50>::value);
两者都已经通过了。唔。
看着装饰器,我注意到注入到夹具上下文中的容差是键入的。
实验上我得出的结论是,公差装饰器需要与比较中的操作数具有相同的静态类型参数才能生效。
这实际上可能非常有用(您可以对不同的浮点类型有不同的隐式容差),但这也令人惊讶。
TL;博士
这是固定的完整测试集,供您欣赏:
- 考虑评估顺序和对准确性的影响
- 使用静态类型
utf::tolerance(v)
来匹配您的操作数
- 不要使用 BOOST_CHECK_EQUAL 进行基于容差的比较
- 我建议使用显式
test_tools::tolerance
而不是依赖“环境”容差。毕竟,我们想要测试我们的代码,而不是测试框架
住在科利鲁
template <typename T> struct Rect {
Rect(const T &width, const T &height) : width(width), height(height){};
T getArea() const { return width * height; }
private:
T width, height;
};
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE RectTest
#include <boost/multiprecision/cpp_dec_float.hpp>
using DecFloat = boost::multiprecision::cpp_dec_float_50;
#include <boost/test/unit_test.hpp>
namespace utf = boost::unit_test;
namespace tt = boost::test_tools;
namespace {
template <typename T>
static inline const T Eps = std::numeric_limits<T>::epsilon();
template <typename T> struct Fixture {
T const epsilon = Eps<T>;
T const ONE = 1;
using Rect = ::Rect<T>;
void checkArea(int wdenom, int hdenom) const {
auto w = ONE/wdenom; // could be expression templates
auto h = ONE/hdenom;
Rect const R(w, h);
T expect = w*h;
BOOST_TEST(expect == R.getArea(), "1/" << wdenom << " x " << "1/" << hdenom);
// I'd prefer explicit toleranc
BOOST_TEST(expect == R.getArea(), tt::tolerance(epsilon));
}
};
}
BOOST_AUTO_TEST_SUITE(Rectangles)
BOOST_FIXTURE_TEST_SUITE(Double, Fixture<double>, *utf::tolerance(Eps<double>))
BOOST_AUTO_TEST_CASE(check2_3) { checkArea(2, 3); }
BOOST_AUTO_TEST_CASE(check7_2) { checkArea(7, 2); }
BOOST_AUTO_TEST_CASE(check57_31) { checkArea(57, 31); }
BOOST_AUTO_TEST_SUITE_END()
BOOST_FIXTURE_TEST_SUITE(MultiPrecision, Fixture<DecFloat>, *utf::tolerance(Eps<DecFloat>))
BOOST_AUTO_TEST_CASE(check2_3) { checkArea(2, 3); }
BOOST_AUTO_TEST_CASE(check7_2) { checkArea(7, 2); }
BOOST_AUTO_TEST_CASE(check57_31) { checkArea(57, 31); }
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
印刷