Boost.Test浮動小数点の比較

Boost.Testで使える浮動小数点比較用マクロの紹介


問題

アプリケーションコード内では浮動小数点 (doublefloat)を比較することはないと思いますが、テストコードでは期待値と比較することが必要です。ただし、浮動小数点の場合、精度の問題から微小の誤差による不一致は避けられません

例えば、以下のテストコードは0.15 + 0.15 == 0.1 + 0.2ですので、一見問題なさそうですが、私の環境では不一致となります

#include <boost/test/included/unit_test.hpp>
using namespace boost::test_tools;

template <class T>
T dut_add(const T& a, const T& b) {
  return a + b;
}

BOOST_AUTO_TEST_CASE(double_comparison) {
  double a = dut_add(0.15, 0.15);
  double b = dut_add(0.10, 0.20);
  BOOST_TEST(a == b);  // !エラー
}
% clang++ -Wall -g -O2 cpp/boost_test_float.cc -o boost_test_float
% ./boost_test_float
Running 1 test case...
cpp/boost_test_float.cc(12): error: in "double_comparison": check a == b has failed [0.29999999999999999 != 0.30000000000000004]

かといって、

  BOOST_TEST(abs(a - b) < 1E-10);  // Passするが汎用性がない

のような書き方は汎用性がありません。なぜならどの程度の誤差が許容できるかは比較する数字の大きさに依存するからです。例えばaやbの期待値が1E-15だった場合、上記のような1E-10の誤差は大きすぎます。


Boost.Testでの書き方

このような問題に対して、Boost.Testではboost::unit_test::toleranceで許容値を割合で指定することができます。(ドキュメント)

  a = 10.0;
  b = 10.2;
  BOOST_TEST(a == b, tolerance(0.05)); // PASS  (5%の誤差を許容)
  BOOST_TEST(a == b, tolerance(0.01)); // ERROR (1%の誤差を許容)

上記の例ではaとbの誤差は2%です。最初のBOOST_TESTtolerance(0.05)、すなわち5%の誤差を許容しているのでパスします。これに対して2番目の比較は1%の誤差しか許容していないため、エラーとなります。

テスト全体にまとめて指定

先ほどはBOOST_TEST()毎に許容誤差を指定していましたが、BOOST_AUTO_TEST_CASE()マクロの第二引数で指定することでテストケース全体にまとめて指定することも可能です

BOOST_AUTO_TEST_CASE(double_comparison_with_tolerance, * boost::unit_test::tolerance(1E-15)) {
  double a = dut_add(0.15, 0.15);
  double b = dut_add(0.10, 0.20);
  BOOST_TEST(a == b);
}

データ型毎の許容誤差

上記のboost::unit_test::tolerance(1E-15)double型の比較に対してのみ適用されます。float型に対して許容誤差を設定する場合は、

BOOST_AUTO_TEST_CASE(float_comparison_with_tolerance, * boost::unit_test::tolerance(1E-6f)) {
  float a = 0.15f + 0.15f;
  float b = 0.01f + 0.29f;
  BOOST_TEST(a == b);
}

tolerance(1E-6f)tolerance()float型を設定します。その他にもlong doubleやユーザ定義型に対するしても同様です

おすすめ