5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GoogleTestで浮動小数点数を比較する

Posted at

目的

GoogleTestを使って、浮動小数点数を比較する際の要点を紹介します。
本記事ではFloat型(単精度)について説明しますが、Double型(倍精度)でも考え方は同じです。
#Floatについて

Floatは32bitの内仮数部が24bitしかないため、有効数字の上限は約7桁となります。
参考文献
#テストマクロの種類
GoogleTestに用意されている、値一致比較のマクロは以下の3種類

  • EXPECT_EQ(ASSERT_EQ)
  • EXPECT_FLOAT_EQ(ASSERT_FLOAT_EQ)
  • EXPECT_NEAR(ASSERT_NEAR)

EXPECT_EQは使えない

公式ドキュメントにも記載がある通り、
EXPECT_EQは完全一致比較を行うため、浮動小数点数の比較には使用できません。
よって、下記のテストは失敗します。

test_float.cpp
TEST(FloatTest, FailureTestForSimpleCalculation){

  float a, b;

  a =  555555.5;
  b =  0.055555;

  EXPECT_EQ(555555.555555, a+b); //失敗する
}
テストログ
----------------------------------------------------------
1/4 Testing: FloatTest.FailureTestForSimpleCalculation
1/4 Test: FloatTest.FailureTestForSimpleCalculation
Command: ".../AllTests" "--gtest_filter=FloatTest.FailureTestForSimpleCalculation"
Directory: .../build/test
"FloatTest.FailureTestForSimpleCalculation" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../googletest/src/gtest_main.cc
Note: Google Test filter = FloatTest.FailureTestForSimpleCalculation
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN      ] FloatTest.FailureTestForSimpleCalculation
/.../test/test_float.cpp:10: Failure
Expected equality of these values:
  555555.555555
    Which is: 555556
  a+b
    Which is: 555556
[  FAILED  ] FloatTest.FailureTestForSimpleCalculation (0 ms)
[----------] 1 test from FloatTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FloatTest.FailureTestForSimpleCalculation

 1 FAILED TEST
<end of output>
Test time =   0.00 sec
----------------------------------------------------------
Test Failed.
"FloatTest.FailureTestForSimpleCalculation" end time: Aug 05 19:11 JST
"FloatTest.FailureTestForSimpleCalculation" time elapsed: 00:00:00
----------------------------------------------------------

単純な演算であればEXPECT_FLOAT_EQが使える

先ほど失敗したテストは、**EXPECT_FLOAT_EQ(ASSERT_FLOAT_EQ)**を使えば等しいと判断されます。
EXPECT_FLOAT_EQは、4ULPs(1ULPs:float型の最下位桁数単位)を基準に、2値の差が許容できる誤差範囲かを判定します。
公式ドキュメント
浮動小数点数の誤差について詳しく知りたい方はこちら

test_float.cpp
TEST(FloatTest, SuccessfulTestForSimpleCalculation){

  float a, b;

  a =  555555.5;
  b =  0.055555;

  EXPECT_FLOAT_EQ(555555.555555, a+b); //成功する
}
テストログ
2/4 Testing: FloatTest.SuccessfulTestForSimpleCalculation
2/4 Test: FloatTest.SuccessfulTestForSimpleCalculation
Command: "/.../build/test/AllTests" "--gtest_filter=FloatTest.SuccessfulTestForSimpleCalculation"
Directory: /.../build/test
"FloatTest.SuccessfulTestForSimpleCalculation" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../src/gtest_main.cc
Note: Google Test filter = FloatTest.SuccessfulTestForSimpleCalculation
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN      ] FloatTest.SuccessfulTestForSimpleCalculation
[       OK ] FloatTest.SuccessfulTestForSimpleCalculation (0 ms)
[----------] 1 test from FloatTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.
<end of output>
Test time =   0.00 sec
----------------------------------------------------------
Test Passed.
"FloatTest.SuccessfulTestForSimpleCalculation" end time: Aug 05 19:11 JST
"FloatTest.SuccessfulTestForSimpleCalculation" time elapsed: 00:00:00
----------------------------------------------------------

丸め誤差が発生する演算には、EXPECT_NEARを使おう

**EXPECT_FLOAT_EQ(ASSERT_FLOAT_EQ)**は、繰り返し演算によって発生する、情報落ちには対応できないため、
下記のようなテストは失敗します。

test_float.cpp
TEST(FloatTest, FailureTestForLossOfTrailingDigits){

  float a, b;

  a =  155000.0;
  b =  0.550000;

  //a=100000.0になることを期待した演算
  for(int i=0; i<100000; i++){
    a -= b;
  }

  EXPECT_FLOAT_EQ(100000.0, a); //失敗する
}
テストログ
3/4 Testing: FloatTest.FailureTestForLossOfTrailingDigits
3/4 Test: FloatTest.FailureTestForLossOfTrailingDigits
Command: ".../build/test/AllTests" "--gtest_filter=FloatTest.FailureTestForLossOfTrailingDigits"
Directory: .../build/test
"FloatTest.FailureTestForLossOfTrailingDigits" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../src/gtest_main.cc
Note: Google Test filter = FloatTest.FailureTestForLossOfTrailingDigits
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN      ] FloatTest.FailureTestForLossOfTrailingDigits
/.../test/test_float.cpp:35: Failure
Expected equality of these values:
  100000.0
    Which is: 100000
  a
    Which is: 100312.5
[  FAILED  ] FloatTest.FailureTestForLossOfTrailingDigits (0 ms)
[----------] 1 test from FloatTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FloatTest.FailureTestForLossOfTrailingDigits

 1 FAILED TEST
<end of output>
Test time =   0.00 sec
----------------------------------------------------------
Test Failed.
"FloatTest.FailureTestForLossOfTrailingDigits" end time: Aug 05 19:11 JST
"FloatTest.FailureTestForLossOfTrailingDigits" time elapsed: 00:00:00
----------------------------------------------------------

上記例では、真値に対して312.5もの誤差が発生しています。
丸め誤差を考慮したテストを記述する場合は、**EXPECT_NEAR(ASSERT_NEAR)**を使うと、許容誤差範囲を引数によって指定することができます。

test_float.cpp
TEST(FloatTest, SuccessfulTestForLossOfTrailingDigits){

  float a, b;

  a =  155000.0;
  b =  0.550000;

  //a=100000.0になることを期待した演算
  for(int i=0; i<100000; i++){
    a -= b;
  }

  EXPECT_NEAR(1.0e+5, a, 5.0e+2); //成功する
}
テストログ
4/4 Testing: FloatTest.SuccessfulTestForLossOfTrailingDigits
4/4 Test: FloatTest.SuccessfulTestForLossOfTrailingDigits
Command: "/.../build/test/AllTests" "--gtest_filter=FloatTest.SuccessfulTestForLossOfTrailingDigits"
Directory: /.../build/test
"FloatTest.SuccessfulTestForLossOfTrailingDigits" start time: Aug 05 19:11 JST
Output:
----------------------------------------------------------
Running main() from /.../src/gtest_main.cc
Note: Google Test filter = FloatTest.SuccessfulTestForLossOfTrailingDigits
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FloatTest
[ RUN      ] FloatTest.SuccessfulTestForLossOfTrailingDigits
[       OK ] FloatTest.SuccessfulTestForLossOfTrailingDigits (1 ms)
[----------] 1 test from FloatTest (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (1 ms total)
[  PASSED  ] 1 test.
<end of output>
Test time =   0.00 sec
----------------------------------------------------------
Test Passed.
"FloatTest.SuccessfulTestForLossOfTrailingDigits" end time: Aug 05 19:11 JST
"FloatTest.SuccessfulTestForLossOfTrailingDigits" time elapsed: 00:00:00
----------------------------------------------------------

End testing: Aug 05 19:11 JST

#まとめ

  • 浮動小数数の比較にEXPECT_EQは使えない
  • 単純な演算であればEXPECT_FLOAT_EQが使える
  • 丸め誤差が発生する演算にはEXPECT_NEARを使おう

#参考文献
http://www.cc.kyoto-su.ac.jp/~yamada/programming/float.html#float
http://opencv.jp/googletestdocs/index.html
https://qiita.com/tobira-code/items/d9c40b650dec40d3c2ed

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?