浮動小数点数の比較
16言語で確認しましたが、trueを返すのはGo言語とD言語だけでした。#Golang #Dlang #CPP #CSharp #javascript #Ruby #PHP #swift #java #python #rust #kuin #なでしこ #haskell pic.twitter.com/QPrl8unl0E
— KST (@KASServerTF2) 2017年10月26日
このような話が話題になりました。さて、今回の記事では、D言語でなぜこのような結果になるかについて…はお話しません
浮動小数点数を ==
で比較するのは禁じ手もいいところですので、誤差範囲を決めたうえで比較を行うのが定石というものです。
D言語では std.math.approxEqual を使用します。
import std.math;
auto a = 3.0 - 2.0;
auto b = 2.0 - 1.0;
auto c = a.approxEqual(b);
writeln(c);
さて、このapploxEqualですが、誤差範囲を指定することができます。
指定の仕方は、比較対象に続けて、相対誤差、絶対誤差を指定できます。
import std.math;
auto a = 3.0 - 2.0;
auto b = 2.0 - 1.0;
auto c = a.approxEqual(b, 0.0001, 0.001);
writeln(c);
関数の定義は以下のようになっています。
公式ドキュメント
/** Params:
* lhs = 値1
* rhs = 値2
* maxRelDiff = rhsに対して許容される最大の相対誤差
* maxAbsDiff = 最大の絶対誤差
*/
bool approxEqual(T, U, V)(T lhs, U rhs, V maxRelDiff, V maxAbsDiff = 1e-05);
ところで、この相対誤差と絶対誤差って、なんのことなのでしょうか。
相対誤差と絶対誤差
一般的には、相対誤差と絶対誤差は、以下のように定義されます。
絶対誤差 = 値 - 真値
相対誤差 = 絶対誤差 / 真値
ここで、値と真値というのが出てきますが、そもそも「誤差」っていうのは、「こうあるべき」「基準となる」「本当の値」っていうようなものがあって、それに対してどれだけずれがあるか、というものですので、基準があるはずなんですね。
approxEqualの挙動
approxEqualでは、lhsとrhsで、どのようにして絶対誤差と相対誤差を出しているのでしょうか。基準となっているのはlhsとrhsどちらなのでしょう。
さらに、絶対誤差の範囲内に入っていて、相対誤差では範囲外だとどうなるのでしょうか。その逆は?
以下、実験です。
import std.math, std.stdio;
void main()
{
real x = 100;
real y = 100.1;
// 絶対誤差
writeln(abs(x - y)); // -> 0.1
// (xを真値とした)相対誤差1
writeln(abs(x - y) / x); // -> 0.001
// (yを真値とした)相対誤差2
writeln(abs(x - y) / y); // -> 0.000999
// 相対誤差はNG、絶対誤差はNG
writeln(approxEqual(x, y, 0.0001, 0.001)); // -> false
// 相対誤差はOK、絶対誤差はNG
writeln(approxEqual(x, y, 0.002, 0.001)); // -> true
// 相対誤差はNG、絶対誤差はOK
writeln(approxEqual(x, y, 0.0001, 0.2)); // -> true
// 相対誤差1はNG、相対誤差2はOK、絶対誤差はNG
writeln(approxEqual(x, y, 0.0009995, 0.001)); // -> true
x = 100.1;
y = 100;
// 絶対誤差
writeln(abs(x - y)); // -> 0.1
// 相対誤差1
writeln(abs(x - y) / x); // -> 0.000999
// 相対誤差2
writeln(abs(x - y) / y); // -> 0.001
// 相対誤差はNG、絶対誤差はNG
writeln(approxEqual(x, y, 0.0001, 0.001)); // -> false
// 相対誤差はOK、絶対誤差はNG
writeln(approxEqual(x, y, 0.002, 0.001)); // -> true
// 相対誤差はNG、絶対誤差はOK
writeln(approxEqual(x, y, 0.0001, 0.2)); // -> true
// 相対誤差1はOK、相対誤差2はNG、絶対誤差はNG
writeln(approxEqual(x, y, 0.0009995, 0.001)); // -> false
}
上記の結果から分かる通り、相対誤差で真値(基準)としているのは、 rhs
のようです。
また、絶対誤差か相対誤差、どちらか一方でも条件を満たせば、真と判定するようです。