dlang
D言語Day 15

浮動小数点数の比較で許容する相対誤差と絶対誤差

浮動小数点数の比較

このような話が話題になりました。さて、今回の記事では、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 のようです。
また、絶対誤差か相対誤差、どちらか一方でも条件を満たせば、真と判定するようです。