E - Trapezium Editorial by rsk0315

浮動小数点数の誤差について

実は、(それなりに自明でないプロセスを経て)下記の命題が成り立つことが示せます。

Lemma 1: \(0\) でない任意の整数 \(|a|, |b|, |c|, |d|\le 2^{26}\) に対し、

\[ \frac ab\ne\frac cd \implies a\oslash b \ne c\oslash d \]

が成り立つ。

Proof. こちら に書きました。\(\square\)

ここで、\(\oslash\)double 型における除算を表すとし、丸めモードはデフォルトの tiesToEven とします。 すなわち、無限精度での計算で比が等しくならないペアについては、double で扱っても等しくならない(誤差によってたまたま等しくなってしまうことがない)ということです。

また、\(a/b=c/d\) ならば \(a\oslash b = c\oslash d\) なので、無限精度で比が等しいペアを等しくないとしてしまうこともないです(こちらを示すのは容易です)。

公式解説 の方法を参考にして、double を用いることにした自然な実装では \((x_i-x_j)\oslash(y_i-y_j)\) のようなものを計算すると思います。今回の制約においては、\(x_i-x_j\)\(y_i-y_j\) は絶対値が \(2\cdot 10^7\) 以下の整数であるので、\(0\) 除算の扱いに注意すれば Lemma 1 の前提が成り立ちます。

note: \(2\cdot 10^7 \lt 6.7\cdot 10^7 \lt 2^{26} = 67108864\) が成り立ちます。

よって、提出 #68473579 のように double (f64) 型を使った実装は正当です。f64BTreeMap の key として使えないことから OrderedFloat を使っていますが、誤差関連の事情とは関係ありません。


このような種類の補題を予め知っておくのは、よっぽど浮動小数点型に興味がある人でないと厳しいと思いますし、ましてやわざわざコンテスト中に示すようなものでもないですから、初心者がまず身につけるべきなのは整数型に帰着して解く癖だとは思います。

一方で、いざ浮動小数点型が避けられない問題が出たときに、誤った直感により妥当でない操作をしてしまう人もしばしば見かけます。復習などの際に適度に浮動小数点型の勉強をするのは(頻出アルゴリズムなどよりは優先度は低いですが)ありだと思います。

posted:
last update: