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

# 浮動小数点数の比較

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">16言語で確認しましたが、trueを返すのはGo言語とD言語だけでした。<a href="https://twitter.com/hashtag/Golang?src=hash&amp;ref_src=twsrc%5Etfw">#Golang</a> <a href="https://twitter.com/hashtag/Dlang?src=hash&amp;ref_src=twsrc%5Etfw">#Dlang</a> <a href="https://twitter.com/hashtag/CPP?src=hash&amp;ref_src=twsrc%5Etfw">#CPP</a> <a href="https://twitter.com/hashtag/CSharp?src=hash&amp;ref_src=twsrc%5Etfw">#CSharp</a> <a href="https://twitter.com/hashtag/javascript?src=hash&amp;ref_src=twsrc%5Etfw">#javascript</a> <a href="https://twitter.com/hashtag/Ruby?src=hash&amp;ref_src=twsrc%5Etfw">#Ruby</a> <a href="https://twitter.com/hashtag/PHP?src=hash&amp;ref_src=twsrc%5Etfw">#PHP</a> <a href="https://twitter.com/hashtag/swift?src=hash&amp;ref_src=twsrc%5Etfw">#swift</a> <a href="https://twitter.com/hashtag/java?src=hash&amp;ref_src=twsrc%5Etfw">#java</a> <a href="https://twitter.com/hashtag/python?src=hash&amp;ref_src=twsrc%5Etfw">#python</a> <a href="https://twitter.com/hashtag/rust?src=hash&amp;ref_src=twsrc%5Etfw">#rust</a> <a href="https://twitter.com/hashtag/kuin?src=hash&amp;ref_src=twsrc%5Etfw">#kuin</a> <a href="https://twitter.com/hashtag/%E3%81%AA%E3%81%A7%E3%81%97%E3%81%93?src=hash&amp;ref_src=twsrc%5Etfw">#なでしこ</a> <a href="https://twitter.com/hashtag/haskell?src=hash&amp;ref_src=twsrc%5Etfw">#haskell</a> <a href="https://t.co/QPrl8unl0E">pic.twitter.com/QPrl8unl0E</a></p>&mdash; KST (@KASServerTF2) <a href="https://twitter.com/KASServerTF2/status/923623437037330432?ref_src=twsrc%5Etfw">2017年10月26日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このような話が話題になりました。さて、今回の記事では、D言語でなぜこのような結果になるかについて…は**お話しません**

浮動小数点数を `==` で比較するのは禁じ手もいいところですので、誤差範囲を決めたうえで比較を行うのが定石というものです。
D言語では [std.math.approxEqual](https://dlang.org/phobos/std_math.html#.approxEqual) を使用します。

```d
import std.math;
auto a = 3.0 - 2.0;
auto b = 2.0 - 1.0;
auto c = a.approxEqual(b);
writeln(c);
```

さて、このapploxEqualですが、誤差範囲を指定することができます。
指定の仕方は、比較対象に続けて、相対誤差、絶対誤差を指定できます。

```d
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);
```

関数の定義は以下のようになっています。
[公式ドキュメント](https://dlang.org/phobos/std_math.html#.approxEqual)

```d
/** 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どちらなのでしょう。
さらに、絶対誤差の範囲内に入っていて、相対誤差では範囲外だとどうなるのでしょうか。その逆は？

以下、実験です。

```d
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` のようです。
また、絶対誤差か相対誤差、どちらか一方でも条件を満たせば、真と判定するようです。