えびちゃんの日記

えびちゃん(競プロ)の日記です。

コメントアウトと脚註

下記を書きます。

<!--
They veil main text, but ((a footnote leaks!))
-->

↓ ここに書いています。

↑ ここに書いています。

脚註を見に行くと...?*2


没パートを隠したつもりでも晒されてしまうことがあるので注意しましょう(少なくとも 1 敗)。

*1:a footnote leaks!

*2:執筆時点と挙動が変わったら意味不明になります。この脚註が *2 でなければ挙動が変わっています。

AHC 048 の浮動小数点演算について

浮には興味があるものの、ヒュにはないので見落としてしまいます。

atcoder.jp

本編

コード読み

src/lib.rs を見ます。

まずは gen(..) 以外を読みます。 四則演算以外の関数として .sqrt(), .round(), .powi(2), .max(0.0) が使われていますが、これは問題ないでしょう。

続いて gen() を読みます。 .powf(_) や .ln() などは可搬性があまり期待できなさそうです。Iterator の .sum::<f64>() についてはどうなんでしょう。 Python だと sum() のアルゴリズムが 3.12 で変わり、sum([0.1] * 10) の結果が変わるようになりましたが、Rust では現状は特にそういうことはしていなさそうです。

github.com

mix() の中の内積の計算の部分で勝手に FMA 命令が使われると破綻しますが、ちょっと試した感じだと問題なさそうな気がします。LLVM 側がどうしているかのちゃんとしたソースは知りませんが、IEEE 754 準拠ならそういうことをしてはいけないので信じてもいいような気もします。

godbolt.org

.ln() を経由して作った数 1000 個についての和の定数倍の .round() は、環境(というかライブラリ)に応じて値が変わるケースがありそうですが、乱数で作る前提だと言われるとちょっと構成するのは厳しい気もします。

撃墜?

$D$ についてのケースは見つかりました。

$\gdef\libmpowf#1#2{\operatorname{\texttt{powf}}(#1, #2)}$

$x = \texttt{1}.\texttt{C3B16FE640F44}_{(16)}\times 2^1$ について考えます。 $$ 10^x = \texttt{1}.\texttt{{\small A66FFFFFFFFF}{\footnotesize F803DA175AE6}{\scriptsize 54D2C3F47F41}{\tiny\ldots}}_{(16)}\times 2^{11} $$ です。AtCoder 環境では $\libmpowf{10}{x} = \texttt{1}.\texttt{A66FFFFFFFFFF}_{(16)}\times 2^{11}$ ですが、$\libmpowf{10}{x} = \texttt{1}.\texttt{A67}_{(16)}\times 2^{11}$ になる環境が確認できました。これにより、$D=3379$ になる場合と $D=3380$ になる場合に分かれます。

下記のコードをコードテストで実行すると、計算結果が異なっていることが確かめられます。言語は Zsh です。

長いので折り畳み

cat <<\EOF | base64 -d > powf_10_0x1c3b16fe640f44p-51
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
EOF
chmod u+x powf_10_0x1c3b16fe640f44p-51
./powf_10_0x1c3b16fe640f44p-51
GLIBC_TUNABLES=glibc.cpu.hwcaps=-FMA ./powf_10_0x1c3b16fe640f44p-51

コードおわり $\eod$

GLIBC_TUNABLES=glibc.cpu.hwcaps=-FMA については下記の記事に書きました。「AtCoder 環境で変なことをしたら計算結果が変わったよ」ということではなくて、「FMA 命令がサポートされていない環境では実際にそうなる」という感じです。

rsk0315.hatenablog.com

元となるコード自体は下記です。

fn main() {
    let x: f64 = "10.0".parse().unwrap();
    let y: f64 = "3.528852450779512".parse().unwrap();
    println!("{}", x.powf(y));
}

10.0_f64.powf(3.528852450779512) と書くとコンパイル時に計算された値が埋め込まれてしまったので、それを防ぐためのステップを加えました。パースの部分で差異が起きているわけではないことは、デバッガを使いながら __ieee754_pow_fma や __ieee754_pow_sse2 を直接呼んだりすることなどで確かめられます。文字列からの変換は correctly rounded を仮定できると思っていますが、嫌な人は f64::from_bits(_) とかを使って確かめるといいかもしれません。

下記についてふんわりしているので、撃墜と呼べるのかはわかりません。

  • gen() の部分までは「配布ツールもジャッジプログラムと完全に同一の挙動をするように実装されている」が保証されているわけではない?
  • rand_chacha::ChaCha20Rng::seed_from_u64(seed) で該当のケースを生成するような seed の存在は謎

本ではない編

気になった部分を挙げていきます。

重箱つんつん

コード中で x * 1e-5 という書き方がしばしば出てきますが、これは $x \otimes \roundcirc{10^{-5}}$ の意味であって、$x \oslash 10^5 = \roundcirc{x\cdot 10^{-5}}$ とは異なります。本来は避けられる丸めが余分に一回あるということです。

実際、たとえば $$ 3 \oslash {10^5} = (3 + 22432\cdot 2^{-68})\cdot 10^{-5} $$ ですが、 $$ 3 \otimes \roundcirc{10^{-5}} = (3 + 122432\cdot 2^{-68})\cdot 10^{-5} $$ です。気にするほどの誤差ではないという立場もありつつ、相対誤差で言えば $5.46$ 倍程度に大きくなっているので、なんだかな〜という気持ちがあります。 精度よりも速度を重視していますと言われれば(少なくともこの文脈では)納得はできます。


次です。

操作可否の判定

$v\lt 1-10^{-6}$ の場合 [...] 操作 2 は実行できない。

$1-10^{-6}$ と書いている部分は実際には $1\ominus \roundcirc{10^{-6}}$ や $\roundcirc{1-10^{-6}}$ の意味なのか?というところが気になります*1。 というのも、コード中で v < 1.0 - 1e-6 と判定していますが、この右辺の実際の値は $$ 1 \ominus \roundcirc{10^{-6}} = (999999-4047\cdot 2^{-47})\cdot 10^{-6} \lt 1-10^{-6} $$ です。よって、$v = 1 \ominus \roundcirc{10^{-6}}$ だった場合、$v\lt 1-10^{-6}$ でありながらも操作 2 が実行できることになります。

これも「そういうところを厳密にやって気持ちよくなるのはえびちゃんだけですよ笑」と言われると、険しい気持ちになりつつ納得は一応できます。


三つ目です。

倍精度浮動小数(double)

浮動小数点 (floating-point) 数 (number) なので、浮動小数という呼び方には共感できないところがあります。「浮動小数点数」なのか「浮動小数点数」なのかどっちですか? 浮動点数*2とか浮点数とかの方が納得できます。実際、中文では浮点数(浮點數)と呼ぶみたいです (cf. 浮点数运算 - 维基百科)。

floating-point datum や floating-point number といった用語についても IEEE 754 で定義されています。過去に書いた記事でも少し触れました。

rsk0315.hatenablog.com


四つ目です。

$\mathrm{rand\_double}(L,U)$: $L$ 以上 $U$ 以下の実数値を一様ランダムに生成する。

[...] $x_k = -\ln \mathrm{rand\_double}(0, 1)$ により生成する。

$0$ が生成されたとき $-{\ln 0}$ になってこわれそうだなと思いました。

なお、rng.gen::<f64>() の内部で使われている rand::distributions::Standard を見ると、$[0\lldot 1]$ ではなく $[0\lldot 1)$ に見えました。

あとがき

境界付近の値を全探索して、FMA 起因で変わるケースがたまたま出てきたときが一番テンションが高くて、あとは (-’’-;) という感じでした。 libc の実装次第で、もっといろいろ見つかるかもしれません? $T$ の方は見つかりませんでしたが、$D$ の方で M2 Mac と差異があるケースとしては下記がありました。 $$ \begin{aligned} 10^{\texttt{1}.\texttt{7057235B3EE78}_{(16)}\times 2^1} &= \texttt{1}.\texttt{{\small 794000000000}{\footnotesize 07F9E18EE17C}{\scriptsize AEF86997B0EA}{\tiny\ldots}}_{(16)}\times 2^9 \\ 10^{\texttt{1}.\texttt{FE70F8B86E1B1}_{(16)}\times 2^1} &= \texttt{1}.\texttt{{\small 2FDBFFFFFFFF}{\footnotesize F8055416F30B}{\scriptsize CB808AF4D765}{\tiny\ldots}}_{(16)}\times 2^{13} \\ \end{aligned} $$ .round() の結果、前者は $(754, 755)$ で後者は $(9723, 9724)$ でした。

探索に使ったコード

py='
from binascii import hexlify
from hashlib import sha256
from math import inf, log10, log2, nextafter
from struct import pack

# let mut D = f64::round(f64::powf(10.0, rng.gen_range(1.0..4.0))) as i32;
# let mut T = f64::round(4000.0 * f64::powf(2.0, rng.gen_range(0.0..4.0))) as usize;

nextdown = lambda x: nextafter(x, -inf)
nextup = lambda x: nextafter(x, inf)

xs = []
n = 10**4
# n = 4000 * 2**4
for i in range(n):
    y = i + 0.5
    x = log10(y)
    # x = log2(y)
    xs.extend([nextdown(x), x, nextup(x)])

xs = [*filter(lambda x: 1.0 <= x < 4.0, xs)]
# xs = [*filter(lambda x: 0.0 <= x < 4.0, xs)]
# print(len(xs))

ys = []
for x in xs:
    y = round(10**x)
    # y = round(4000 * 2**x)
    ys.append((x, y))

# b = pack(f"<{len(ys)}d", *ys)
# digest = sha256(b).digest()
# print(hexlify(digest).decode().upper())
print(*ys, sep="\n")
'

python3 -c "$py" > out-fma.txt
GLIBC_TUNABLES=glibc.cpu.hwcaps=-FMA python3 -c "$py" > out-no-fma.txt

diff out-fma.txt out-no-fma.txt

log2 や log10 の出力が違うと面倒な感じになりますが、そういう場合は別途よしなにします。$\eod$

あまり M2 Mac のことをわかっていないのですが、libsystem_m.dylib`pow とか /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0) とか書いてありました。

$\log_e(x)$ や $e^x$ については TMD が解決済みだったと記憶していますが、二変数であるところの $x^y$ はとても大変で、まだまだ未解決だったと記憶しています。記憶でしゃべりすぎ。解決される日は来るのでしょうか。

おわり

おわりです。

*1:これら二つの値は(たまたま)一致しています。

*2:不動点と紛らわしくて嫌われそう。

手元環境と AtCoder 環境で sin の出力が異なるのはな〜んでだ?

人「浮動小数点数の演算なんだから環境によって結果が変わりうるのは当然だろ」

そうか...?

前提

$\gdef\libmsin#1{\operatorname{\texttt{s\hspace{-0.03em}i\hspace{-0.03em}n}}(#1)}$ $\gdef\libmcos#1{\operatorname{\texttt{c\hspace{-0.03em}o\hspace{-0.03em}s}}(#1)}$

次のような前提があります。

  • IEEE 754 的には、correctly rounded (c.r.) な値を返すことを要求している
    • 無限精度で計算した値を正確に丸めた場合と同じ値ということ
    • これが各環境で正しく実装されているなら、環境ごとの差異はなくなる
  • 実際には「C 言語で提供されている関数」と「IEEE 754 で定められている関数」は異なる
    • 単に名前が一致しているだけで、後者の機能を提供することにはなっていない
    • Annex F の章で一部の関数では対応付けが明記されていて、それらに関しては c.r. が保証されることになる
  • C 言語の処理系が IEEE 754 に準拠しているとは限らない
    • 処理系は精度の保証をしなくてもいいという記述が C の規格にある 🤪
    • 広く使われている処理系においては準拠していると思ってよいはず

特に、処理系が IEEE 754 準拠である前提で、四則演算・平方根・融合積和については c.r. が保証されていることを前提にできます。ここで、融合積和 (fused multiply-add; FMA) は浮動小数点数 $x$, $y$, $z$ に対して $\roundcirc{xy+z}$ を返す演算です。$\roundcirc{\roundcirc{xy}+z}$ とは一般に一致しません。また、$\roundcirc{x}$ は実数 $x$ を浮動小数点型に丸めた値を意味します。

ところで、table-maker’s dilemma という名前で知られている問題があり、三角関数を始めとする数学関数たちの c.r. な値を得るのは自明ではありません。

rsk0315.hatenablog.com

そのため、sin のような関数は c.r. であることを期待できません。実際多くの処理系で c.r. でない値が返ってくるケースが存在しますし、たとえば double における sin の TMD は(えびちゃんの知る限り)未解決です*1。

一方で、上記で挙げた一部の演算たちは c.r. であることに注意します。たとえば cos を融合積和を用いて $\libmcos x = \roundcirc{(-x)(x/2) + 1}$ として定義しているライブラリがあったとしましょう*2。使っている演算に可搬性があるので、当然 cos 自体にも(同じライブラリを使っている環境同士なら)可搬性があります。実際のライブラリにおいても(ここまで単純ではないものの)四則演算などを用いて近似をしているため、同様の主張はできます。

なので、共通のライブラリを使っている環境同士で差異があった場合、あまり自然に「当然だろ」とは言えないでしょうという気持ちがあります。

補足

コンパイル時の計算と実行時の計算では精度が異なる場合があります。そのため、実行時の精度について調べようとしているときは、コンパイル時に計算した値が使われていないことを(適宜 disassemble しつつ)確かめておく必要があります。

たとえば、立方根を計算する以下のコードを考えます。

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    printf("%.60g\n", cbrt(3375.0));
    printf("%.60g\n", cbrt(strtod("3375.0", NULL)));
}

前者はコンパイル時に計算され、後者は(文字列から変換する部分が実行時に行われて)実行時に計算されて、下記の出力になる場合があります (wandbox)。

15
14.9999999999999982236431605997495353221893310546875

事象

さて、次の入力を考えます。

$$ \begin{aligned} x &= \texttt{1}.\texttt{\small 00000000004AD}_{(16)}\times 2^{0} \\ &= {\small 1.000000000000265}{\footnotesize 787392095262475}{\scriptsize 777417421340942}{\tiny 3828125}. \end{aligned} $$

AtCoder 環境では次のようになりました。 $$ \begin{aligned} \libmsin x &= \texttt{1}.\texttt{\small AED548F0911FB}_{(16)}\times 2^{-1} \\ &= {\small 0.841470984808040}{\footnotesize 056712741261435}{\scriptsize 439810156822204}{\tiny 58984375}. \end{aligned} $$ 手元環境は M2 Mac 上の colima で動かしている Ubuntu です*3。

FROM ubuntu:25.04@sha256:dc4565c7636f006c26d54c988faae576465e825ea349fef6fd3af6bf5100e8b6
% colima start --cpu 2 --memory 2 --disk 30

この環境では次のようになりました。 $$ \begin{aligned} \libmsin x &= \texttt{1}.\texttt{\small AED548F0911FC}_{(16)}\times 2^{-1} \\ &= {\small 0.841470984808040}{\footnotesize 167735043723951}{\scriptsize 093852519989013}{\tiny 671875}. \end{aligned} $$

真の値は $$ \sin(x) = \texttt{1}.\texttt{{\small AED548F0911FB}{\footnotesize 7FEDF452F4881}{\scriptsize \ldots}}_{(16)}\times 2^{-1} $$ なので、AtCoder 側では c.r. な値が得られていることになります。

note: $1.0$ から $2^{-52}$ ずつ増やしていって最初に見つかったケースがこれだったというだけで、一致しないケースはたくさんあります。

note: M2 Mac 側が c.r. になるケースとしては $x = \texttt{1}.\texttt{00000000088BC}_{(16)}\times 2^{0}$ がありました。

う〜〜〜ん。この差異はどのようにして生じるものなのでしょう?

仮説と検証

実際に調べた手順に沿って書いていきます。目次でネタバレになるのを避けるため番号で章立てしています。

仮説 1

安直な予想として、libc のバージョンが違うのではないか?というのが最初に思い当たる部分でした。 実際には sin の実体は libm の中にあるはずですが、コードのバージョンとしては libc と共通だと思われるので、libc のバージョンで調べることにします。

AtCoder のコードテストで下記を実行しました。

cp /lib/x86_64-linux-gnu/libc.so.6 .
chmod u+x libc.so.6
./libc.so.6

元の libc.so.6 の実行権限がなかったことと、それに対しての chmod ができなかったので、コピーしてきたものを実行しました。あるいは下記を実行するのでもよかったかもしれません。

/usr/lib64/ld-linux-x86-64.so.2 /lib/x86_64-linux-gnu/libc.so.6

得られた結果は次の通りです。

GNU C Library (Ubuntu GLIBC 2.36-0ubuntu4) stable release version 2.36.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 12.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 3.2.0
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

手元では次の通りでした。

% /lib/x86_64-linux-gnu/libc.so.6
GNU C Library (Ubuntu GLIBC 2.41-6ubuntu1) stable release version 2.41.
Copyright (C) 2025 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 14.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 3.2.0
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

たしかにバージョンは違っていますが、このバージョンの違いで挙動に差が出るのかはわかりません。そこで、別のジャッジでも調べてみることにします。

Library Checker ではコードテストがない(と思う)ので、A + B を用いて調べます*4。標準エラー出力が見れるので助かります。

#include <stdio.h>
#include <stdlib.h>

int main() {
    int a, b;
    scanf("%d %d", &a, &b);
    printf("%d\n", a + b);

    system("/lib/x86_64-linux-gnu/libc.so.6 >&2");
}

得られた結果は次の通りです。

GNU C Library (Debian GLIBC 2.36-9+deb12u10) stable release version 2.36.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 12.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 3.2.0
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

また、同ジャッジサーバの Dockerfile.GCC を手元で動かしてみます。

% /lib/x86_64-linux-gnu/libc.so.6

同じ出力が得られました。

GNU C Library (Debian GLIBC 2.36-9+deb12u10) stable release version 2.36.
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 12.2.0.
libc ABIs: UNIQUE IFUNC ABSOLUTE
Minimum supported kernel: 3.2.0
For bug reporting instructions, please see:
<http://www.debian.org/Bugs/>.

加えて、各環境で sin(0x1.00000000004ADp+0) の出力も確認しました。 手元のものは最初に見ていたものを「手元 ①」、Dockerfile.GCC を使ったものを「手元 ②」として記載します。

環境 libc のバージョン sin(0x1.00000000004ADp+0)
AtCoder Ubuntu GLIBC 2.36-0ubuntu4 0x1.AED548F0911FBp-1
Library Checker Debian GLIBC 2.36-9+deb12u10 0x1.AED548F0911FBp-1
手元 ① Ubuntu GLIBC 2.41-6ubuntu1 0x1.AED548F0911FCp-1
手元 ② Debian GLIBC 2.36-9+deb12u10 0x1.AED548F0911FCp-1

Library Checker と手元 ② は libc のバージョンは同じですが、sin の計算結果は異なっているため、libc のバージョン起因ではないと考えるのが妥当そうです。ということで仮説 1 は棄却です。

仮説 2

コードは sin_0x100000000004adp-52.c として下記を想定しています。

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
  double x = strtod("1.0000000000002658", NULL);
  printf("%.13a\n", sin(x));
}

これを手元でコンパイルしたときと AtCoder 環境で実行したときとで、別のバイナリができているのではないか?というのを疑いました(各種都合で別のバイナリになることはあり得ると思うが、今回の計算に影響する部分に差異があるのでは?ということ)。そこで、手元でコンパイルしたものを AtCoder 環境で実行してみます。

% gcc -o sin_0x100000000004adp-52{,.c} -O2 -lm
% printf 'cat <<\\EOC | base64 -d | gzip -d > a.out\n%s\nEOC\nchmod u+x a.out\n./a.out\n' "$(cat sin_0x100000000004adp-52 | gzip -9 | base64)"

下記が得られました。

0x1.aed548f0911fbp-1

これは元々 AtCoder 環境でコンパイルしたときと同じ結果ですから、仮説 2 も棄却できます。

仮説 3

libc のバージョンは同じでも、ビルド時の設定(?)(./configure でなんかするやつ)によって差異があるのでは?と思いました。Library Checker と手元 ② で下記を実行します。

% sha256sum /lib/x86_64-linux-gnu/libc.so.6

どちらも同じ値になったので、同じファイルであると思われます*5。

1d25fd63234b59e4c581564c7a6d8f5c6cf36eee757e3d26f4b0808dd36a4896  /lib/x86_64-linux-gnu/libc.so.6

よって、仮説 3 も棄却です。libc に差異があるわけではないということですね。ここを最初に見ていれば仮説 1 を調べる必要はなかったかもですが、まぁそのへんは仕方ないでしょう。

仮説 4

途方に暮れつつ、gdb でステップ実行してみます。

(gdb) file ./sin_0x100000000004adp-52
(gdb) b sin@plt
(gdb) r
(gdb) si 2
__sin_sse2 (x=1.0000000000002658) at ../sysdeps/ieee754/dbl-64/s_sin.c:201

どうやら、__sin_sse2 というのが呼ばれているみたいです。AtCoder 環境では違うものが呼ばれているのかな? 仮説 3 から libc の実体は共通であろうので、libc の中に __sin_sse2 や __sin_??? などがあり、実行時に何らかの基準によって適切と思われるものを呼んでいるのかな?という気持ちになってきます。

そういえば colima で AVX2 とかを有効にすることができたような...?というのを思い出したので、有効にしてみます。

% colima stop
% colima start --cpu 2 --memory 2 --disk 30 --cpu-type max,+avx,+avx2

からの、実行です。

% ./sin_0x100000000004adp-52
#> 0x1.aed548f0911fbp-1

おっと......?? これは AtCoder 環境での出力と同じものですね。さっきと同様に gdb でも確認してみると、下記のものが呼ばれているようです。

__sin_fma (x=1.0000000000002658) at ../sysdeps/ieee754/dbl-64/s_sin.c:201

どうやら、__sin_fma と __sin_sse2 というのがあり、ハードウェアの機能に応じて別のものが呼ばれていそうです。FMA を使う場合とそうでない場合で計算結果は一般に一致しないので、計算結果が異なるのは納得できます。

ということで、どういう関数が用意されていて、どういう条件で誰が呼び分けているのか?というところが気になってきます。

調査

関数定義

glibc のコードを見ていきます。タグは最新の glibc-2.41.9000 を見ましたが、他もたぶんあまり変わらないでしょう*6。手元 ① の環境と合わせたバージョンですね。

まず、__sin_??? は下記のファイルで定義されています。

  • __sin_sse2 in sysdeps/x86_64/fpu/multiarch/s_sin.c
  • __sin_fma in sysdeps/x86_64/fpu/multiarch/s_sin-fma.c
  • __sin_fma4 in sysdeps/x86_64/fpu/multiarch/s_sin-fma4.c
  • __sin_avx in sysdeps/x86_64/fpu/multiarch/s_sin-avx.c

それぞれ、下記のような記述があります。

#define __sin __sin_fma
#define SECTION __attribute__ ((section (".text.fma")))
#include <sysdeps/ieee754/dbl-64/s_sin.c>

適宜端折りますが、sysdeps/ieee754/dbl-64/s_sin.c では次のようになっています。

double SECTION __sin (double x) { /* ... */ }
libm_alias_double (__sin, sin)

マクロを展開すると次のような感じですね。

double __attribute__ ((section (".text.fma"))) __sin_fma (double x) { /* ... */ }
libm_alias_double (__sin_fma, sin)

sysdeps/x86_64/fpu/multiarch/Makefile 内で下記のような記述があり、それぞれに合わせた最適化をしているのだろうと思っています。

CFLAGS-s_sin-fma.c = -mfma -mavx2
CFLAGS-s_sin-fma4.c = -mfma4
CFLAGS-s_sin-avx.c = -msse2avx -DSSE2AVX

また、sysdeps/x86_64/fpu/multiarch/s_sin.c に下記の記述があります。

extern double __redirect_sin (double);
# define SYMBOL_NAME sin
# include "ifunc-avx-fma4.h"
libc_ifunc_redirected (__redirect_sin, __sin, IFUNC_SELECTOR ());
libm_alias_double (__sin, sin)

libm_alias_double (__sin, sin) に関しては、sysdeps/generic/libm-alias-double.h などを見つつ、結局 weak_alias(__sin, sin) などが呼ばれていそうです。 libc_ifunc_redirected (...) に関しては、include/libc-symbols.h に説明のコメントが書かれています。“The following macros are used for indirect function symbols in libc.so.” から始まる、サンプルつきで 80 行程度のものです。

int foo (int __bar) という関数に対して、libc.so が使われる全部の環境で使えるとは限らない(実行時には判定できる)ハードウェア機能を用いた実装 int __foo_special (int __bar) と、フォールバックの実装 int __foo_default (int __bar) を提供する場合のことが書かれています。まさに〜?という感じがします。

sysdeps/generic/ifunc-init.h に、REDIRECT_NAME, OPTIMIZE, IFUNC_SELECTOR の #define があります。また、IFUNC_SELECTOR (sin_ifunc_selector?) の定義については sysdeps/x86_64/fpu/multiarch/ifunc-avx-fma4.h に記述があります。

CPU_FEATURE_USABLE_P (cpu_features, FMA) については、sysdeps/x86/include/cpu-features.h に定義があり、諸々あって

((ptr->features[index_cpu_FMA].active.reg_FMA & bit_cpu_FMA) != 0)

からの

((ptr->features[0].active.ecx & (1u << 12)) != 0)

に展開されていそうです。

__get_cpu_features () については、ざっくり飛ばしつつ、関連するファイルは下記たちです。

sysdeps/x86/include/cpu-features.h

#define __get_cpu_features() _dl_x86_get_cpu_features()

sysdeps/x86/dl-get-cpu-features.c

__ifunc (__x86_cpu_features, __x86_cpu_features, NULL, void,
     _dl_x86_init_cpu_features);

const struct cpu_features *
_dl_x86_get_cpu_features (void)
{
  return &GLRO(dl_x86_cpu_features);
}

sysdeps/generic/ldsodefs.h

# define GLRO(name) _##name

sysdeps/x86/cpu-features.c

static inline void
init_cpu_features (struct cpu_features *cpu_features)

実行時の処理

実行ファイルを実行するとき、実際には program interpreter と呼ばれるプログラムが実行されていたりします。./foo.sh を実行するときに shebang に書いたプログラムが実行されるのと似ている気がします?*7 なんのプログラムが実行されるかは、readelf -l の出力の Program Headers の INTERP の部分で確認することができます。

% readelf -l sin_0x100000000004adp-52
#:
#> Program Headers:
#>   Type           Offset             VirtAddr           PhysAddr
#>                  FileSiz            MemSiz              Flags  Align
#:
#>   INTERP         0x00000000000003a4 0x00000000000003a4 0x00000000000003a4
#>                  0x000000000000001c 0x000000000000001c  R      0x1
#>       [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
#:

これはシンボリックリンクになっており、/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 を指しています。ld.so も同様のファイルを指しているようなので、名前の短いこちらを使っていくことにします。実際、下記でも同じ出力が得られることが確かめられますね。

% ld.so ./sin_0x100000000004adp-52
#> 0x1.aed548f0911fbp-1

ということで、ld.so にあたる部分のコードを追っていきましょう(gdb を使いつつ調べています)。これも、コードとしては glibc と一緒に提供されています。

sysdeps/x86_64/dl-machine.h の RTLD_START の部分(関数としては _start にあたる)に下記のコメントがあります。

Initial entry point code for the dynamic linker. The C function ‘_dl_start’ is the real entry point; its return value is the user program's entry point.

_dl_start は elf/rtld.c にあります。ファイル冒頭に “Run time dynamic linker.” とあるので、名前はそういう意味だと思います。下記のような呼び出しがあり、CPU の各機能が使えるかが調べられます。

  • _dl_start (elf/rtld.c:581)
  • _dl_start_final (elf/rtld.c:496)
  • _dl_sysdep_start (sysdeps/unix/sysv/linux/dl-sysdep.c:119)
  • dl_platform_init (sysdeps/x86_64/dl-machine.h:209)
  • _dl_x86_init_cpu_features (sysdeps/x86/dl-get-cpu-features.c:41)
  • init_cpu_features (sysdeps/x86/cpu-features.c:754)

note: dl_start 内部で dl_start_final を呼び、その内部で dl_sysdep_start を呼び、... という感じです。

こうして前半パートで見た _dl_x86_cpu_features を初期化します。dl_sysdep_start 内部から、下記の呼び出しが続きます。

  • _dl_start (elf/rtld.c:581)
  • _dl_start_final (elf/rtld.c:496)
  • _dl_sysdep_start (sysdeps/unix/sysv/linux/dl-sysdep.c:141)
  • dl_main (elf/rtld.c:2267)
  • _dl_relocate_object (elf/dl-reloc.c:346)
  • _dl_relocate_object_no_relro (elf/dl-reloc.c:296)
  • elf_dynamic_do_Rela (elf/do-rel.h:138)
  • elf_machine_rela (sysdeps/x86_64/dl-machine.h:309)
    • value = ((ElfW(Addr) (*) (void)) value) ();

おそらく、libm.so.6 から取得できる sin は、

libc_ifunc_redirected (__redirect_sin, __sin, sin_ifunc_selector ());
libm_alias_double (__sin, sin)

のおかげで

extern __typeof (__redirect_sin) __sin;
__typeof (__redirect_sin) *__sin_ifunc (void) __asm__ ("__sin");
__attribute__ ((__optimize__ ("-fno-stack-protector")))
__typeof (__redirect_sin) *__sin_ifunc (void)
  {
    INIT_ARCH ();
    __typeof (__redirect_sin) *res = sin_ifunc_selector ();
    return res;
  }
__asm__ (".type " "__sin" ", %gnu_indirect_function");

extern __typeof (__sin) sin __attribute__ ((weak, alias ("__sin"))) __attribute_copy__ (__sin);

のようになっており、sin(という名前で公開されているやつ)を呼んで返ってきたアドレスを [email protected] に入れるという感じになっているように見えます (cf. 6.12.5 Controlling Names Used in Assembler Code)。

実際に [email protected] に __sin_fma が入るのは下記です。RELRO の設定によるかも。今回は full RELRO になっています。

  • _dl_start (elf/rtld.c:581)
  • _dl_start_final (elf/rtld.c:496)
  • _dl_sysdep_start (sysdeps/unix/sysv/linux/dl-sysdep.c:141)
  • dl_main (elf/rtld.c:2267)
  • _dl_relocate_object (elf/dl-reloc.c:346)
  • _dl_relocate_object_no_relro (elf/dl-reloc.c:296)
  • elf_dynamic_do_Rela (elf/do-rel.h:138)
  • elf_machine_rela (sysdeps/x86_64/dl-machine.h:418)
    • *(Elf64_Addr *) reloc_addr = (Elf64_Addr) value + reloc->r_addend;

こうして、条件に応じて __sin_sse2 ã‚„ __sin_fma などが [email protected] にお届けされるわけですね。

おまけ

ld.so(8) や 38.6 Hardware Capability Tunables を眺めていると、glibc.cpu.hwcaps というのの存在に気づきます。環境変数 GLIBC_TUNABLES によって指定できるようです。

% ./sin_0x100000000004adp-52 
#> 0x1.aed548f0911fbp-1

% GLIBC_TUNABLES=glibc.cpu.hwcaps=-FMA,-AVX ./sin_0x100000000004adp-52
#> 0x1.aed548f0911fcp-1

あらあら、結果が変わることが確かめられましたね。オプション --list-diagnostics によって情報を確認できるので、差分も見てみましょう。

% diff <(ld.so --list-diagnostics) <(GLIBC_TUNABLES=glibc.cpu.hwcaps=-FMA,-AVX ld.so --list-diagnostics)
#:
#> 93c94
#> < x86.cpu_features.features[0x0].active[0x2]=0x7ed83203
#> ---
#> > x86.cpu_features.features[0x0].active[0x2]=0x6ed82203

たしかに、該当するビット (cf. sysdeps/x86/include/cpu-features.h) に影響があることがわかりました。

補足

GCC でも Clang でも、FMA が使える環境向けには(-ffast-math なしでも)x * y + z の形の式に対して FMA を使うような最適化をするようです。 今回触れた __sin_sse2 と __sin_fma で値が変わる理由として、本質的な部分はこの最適化のせいですね。 そんなことしていいのか...?

あとがき

いや〜満足満足。調査から記事まで、土日一回分で済んでうれしいです。他の進捗はありません。 GOT, PLT, RELRO などの説明を省きましたが、興味がある人は適宜調べてくださればと思います。CTF の pwn とかをすると詳しさが増すかも?という気もします。

C で呼び出す場合に限らず Rust や Python などの言語からでも(あるいはもちろん、GCC でコンパイルする場合に限らず Clang を使う場合でも)libm に依存していると影響を受ける部分なので、気をつけなきゃだなと思いました。

おわり

おわりです。

*1:定義域を十分小さく絞った場合については解決されていると認識しています。数十万ビット?とかのめちゃでかい上界は示されていたような気もします。

*2:これでは精度が使い物にならないですが、あくまで例です。

*3:この時点でいろいろ細かく書いてしまうとネタバレ感があっちゃうので、ふんわりめに書いています。

*4:詳細を説明するのが面倒なので避けますが、こんな感じ で比較的お手軽に好きなコマンドを叩けます。そもそも普通の提出でも(権限のある範囲で)任意コード実行は可能なので、特に問題はないと思っています。

*5:同じでないなら、SHA-256 のそういう例を見つけたということなので、それはそれでうれしいですね。

*6:こういうところ雑なのよくなくない? そうかも。

*7:そのプログラムによって各命令が解釈されて云々という点では異なるはずですが、別のプログラムが呼び出されている点に着目しています。

ABC 405 E についての雑談

apple, banana, cherry, durian とかにしてほしかった...

atcoder.jp

考察

コンテスト中にやった考察に沿って話を進めます。

立式

一番右の A と一番右の B に注目します。それらの間に C が含まれるケースは次のようになります。

AAAABBBABBA ++ A ++ BCCBB ++ B ++ CDCCCCDCDD

すなわち、次を連結したものになります。

  • $A-1$ 個の A と、$i$ ($0\le i\lt B$) 個の B を任意の順に並べたもの
  • $1$ 個の A
  • $B-i-1$ 個の B と、$j$ ($1\le j\le C$) 個の C を任意の順に並べたもの
  • $1$ 個の B
  • $C-j$ 個の C と $D$ 個の D を任意の順に並べたもの

よって、この通り数は次の式で表せます。 $$ \begin{aligned} &\phantom{{}={}} \sum_{i=0}^{B-1} \sum_{j=1}^C \binom{A-1+i}{i} \binom{B-i-1+j}{j} \binom{C-j+D}{D} \\ &= \sum_{i=0}^{B-1} \binom{A-1+i}{i} \sum_{j=1}^C \binom{B-i-1+j}{j} \binom{C-j+D}{D} \end{aligned} $$

また、それらの間に C が含まれないケースは次のようになります。

AABABBAABAABABBA ++ CDDCDCDCCCCDCC

すなわち、次を連結したものになります。

  • $A$ 個の A と、$B$ 個の B を任意の順に並べたもの
  • $C$ 個の C と、$D$ 個の D を任意の順に並べたもの

特に、A と C の制約から、一番右の B よりも一番右の A の方が右に来るケースもこちらに含まれます。 この通り数は次のようになります。

$$ \binom{A+B}{B} \binom{C+D}{D} $$

計算量改善

後者のケースはいいとして、前者のケースの内側の $\sum$ を高速に計算する必要があります。 $$ \sum_{i=0}^{B-1} \binom{A-1+i}{i} \underbrace{\sum_{j=1}^C \binom{B-i-1+j}{j} \binom{C-j+D}{D}}_{b_i} $$ すなわち、この $b_i$ の部分です。とりあえず Wolfram|Alpha に投げます。 $$ b_i = \frac{C+D}C\binom{C+D-1}{D} \cdot({}_2 F_1(-C, B-i; -C-D; 1)-1) $$ 二項係数と階乗の部分を整理すると下記になります。 $$ b_i = \frac{(C+D)!}{D!\,C!} \cdot({}_2 F_1(-C, B-i; -C-D; 1)-1) $$

ここで、${}_2 F_1(a, b; c; z)$ は 超幾何関数 (hypergeometric function) と呼ばれ、下記で与えられるらしいです*1。 $$ {}_2 F_1(a, b; c; z) = \sum_{n=0}^{\infty} \frac{(a)_n\,(b)_n}{(c)_n}\cdot\frac{z^n}{n!} $$ $(a)_n$ は Pochhammer の上昇階乗冪で、$(a)_n = a\cdot(a+1)\cdot\cdots\cdot(a+n-1)$ です。Wolfram|Alpha では Hypergeometric2F1 という名前で定義されています。

困っているようす

$c\lt a\lt 0\le b$ として、 $$ \begin{aligned} {}_2 F_1(a, b; c; 1) &= \sum_{n=0}^{\infty} \frac{(a)_n\,(b)_n}{(c)_n}\cdot\frac{1}{n!} \\ &= \sum_{n=0}^{|a|} \frac{(a)_n\,(b)_n}{(c)_n}\cdot\frac{1}{n!} \\ &= \sum_{n=0}^{|a|} \frac{(a)_n}{(c)_n}\cdot\frac{(b)_n}{n!} \\ &= \sum_{n=0}^{|a|} \frac{a\cdot(a+1)\cdot\cdots\cdot(a+n-1)}{c\cdot(c+1)\cdot\cdots\cdot(c+n-1)}\cdot\frac{(b)_n}{n!} \\ &= \sum_{n=0}^{|a|} \frac{|a|\cdot(|a|-1)\cdot\cdots\cdot(|a|-(n-1) )}{|c|\cdot(|c|+1)\cdot\cdots\cdot(|c|-(n-1) )}\cdot\frac{(b)_n}{n!} \\ &= \sum_{n=0}^{|a|} \frac{|a|!}{(|a|-n)!} \frac{(b+n-1)!}{(b-1)!} \frac{(|c|-n)!}{|c|!} \frac1{n!} \\ &= \sum_{n=0}^{|a|} \frac{|a|!}{(|a|-n)!\,n!} \frac{(b+n-1)!}{(b-1)!\,n!} \frac{(|c|-n)!\,n!}{|c|!} \\ &= \sum_{n=0}^{|a|} \binom{|a|}{n} \binom{b+n-1}{n} \binom{|c|}{n}^{-1} \end{aligned} $$ ここからどうすれば...? リンク先の式 (74) によれば $$ {}_2 F_1(a, b; c; 1) = \frac{\Gamma(c)\,\Gamma(c-a-b)}{\Gamma(c-a)\,\Gamma(c-b)} $$ らしいです。ここでは $c$ が負なのでハチャメチャに発散しそうです。困った。

$a\lt 0$ や $\Gamma(n) = (n-1)\,\Gamma(n-1)$ などから $$ \begin{aligned} {}_2 F_1(a, b; c; 1) &= \frac{\Gamma(c)\,\Gamma(c-a-b)}{\Gamma(c-a)\,\Gamma(c-b)} \\ &= \frac{\Gamma(c)}{\Gamma(c-a)}\frac{\Gamma(c-a-b)}{\Gamma(c-b)} \\ &= \frac{\Gamma(c)}{(c-a-1)\,\Gamma(c-a-1)}\frac{(c-a-b-1)\,\Gamma(c-a-b-1)}{\Gamma(c-b)} \\ &= \cdots \\ &= \frac{(c-a-b-1)\cdot\cdots\cdot(c-b)}{(c-a-1)\cdot\cdots\cdot c} \end{aligned} $$ としてよいのであれば、計算はできそうです。今回計算したいのは ${}_2 F_1(-C, B-i; -C-D; 1)$ だったので、 $$ \begin{aligned} &\phantom{{}={}} {}_2 F_1(-C, B-i; -C-D; 1) \\ &= \frac{-C-D-(-C)-(B-i)-1}{-C-D-(-C)-1}\cdot\cdots\cdot\frac{-C-D-(B-i)}{-C-D} \\ &= \frac{-D-B+i-1}{-D-1}\cdot\cdots\cdot\frac{-C-D-B+i}{-C-D} \\ &= \frac{(B-i)+(D+1)}{D+1}\cdot\cdots\cdot\frac{(B-i)+(D+C)}{D+C} \\ &= \frac{( (B-i)+(D+C) )!}{( (B-i)+D)!}\cdot\frac{D!}{(D+C)!} \end{aligned} $$ となりそうです。

たとえば、$(B, C, D, i) = (10, 5, 2, 3)$ としてみると、${}_2 F_1(-5, 7; -7; 1) = \frac{286}3$ となり*2、 $$ \begin{aligned} b_i &= \frac{7!}{2!\,5!}\cdot({}_2 F_1(-5, 7; -7; 1)-1) \\ &= 21\cdot \left(\frac{286}3-1\right) \\ &= 7\cdot(286-3) \\ &= 1981 \end{aligned} $$ となり、元々の $\sum$ で計算した値と一致します。$\eod$

コンテスト本番では、超幾何関数はどうしようもないと思っていたので別の方針を考えました。 $B$, $C$, $D$ を $1$ ずつ変えながら値を眺めることで、下記に気づきます。

Observation 1: $b_B = 0$ かつ、各 $0\le i\lt B$ に対して下記が成り立つ。 $$ b_i = b_{i+1} + \binom{(B-1)-i+C+D}{C-1} $$

$(B-1)-i$ の部分や漸化式の向きなどから、$b'_i$ を下記で定義します。 $$ b'_i = \sum_{j=1}^C \binom{i+j}{i} \binom{C-j+D}{D} $$ なお、$b'_i = b_{(B-1)-i}$ です。

Conjecture 2: $b_{-1} = 0$ かつ、各 $0\le i\le B-1$ に対して下記が成り立つ。 $$ b'_i = b'_{i-1} + \binom{i+C+D}{C-1} $$

More observations

$i=0$ のとき、Hockey-stick identity から $$ \begin{aligned} b'_0 &= \sum_{j=1}^C \binom jj\binom{C-j+D}D \\ &= \sum_{j=1}^C \binom{C-j+D}D \\ &= \sum_{j'=0}^{C-1} \binom{D+j'}D \\ &= \binom{D+C}{D+1} \end{aligned} $$ が成り立つ。一方、 $$ \begin{aligned} b'_0 &= b'_{-1} + \binom{C+D}{C-1} \\ &= 0 + \binom{D+C}{D+1} \end{aligned} $$ より、$i = 0$ のときは成り立つ。

$$ \begin{aligned} b'_i - b'_{i-1} &= \sum_{j=1}^C \binom{i+j}j\binom{C-j+D}D - \sum_{j=1}^C \binom{i-1+j}j\binom{C-j+D}D \\ &= \sum_{j=1}^C \left( \binom{i+j}j - \binom{i-1+j}j\right) \binom{C-j+D}D \\ % &= \sum_{j=1}^C \left( \frac{(i+j)!}{i!\, j!} - \frac{(i+j-1)!}{(i-1)!\, j!}\right) \binom{C-j+D}D \\ % &= \sum_{j=1}^C \left( \frac{(i+j)!}{i!\, j!} - \frac{i}{i+j}\frac{(i+j)!}{i!\, j!}\right) \binom{C-j+D}D \\ % &= \sum_{j=1}^C \frac{j}{i+j}\frac{(i+j)!}{i!\, j!} \binom{C-j+D}D \\ % &= \sum_{j=1}^C \frac{(i+j-1)!}{i!\, (j-1)!} \binom{C-j+D}D \\ &= \sum_{j=1}^C \binom{i-1+j}{j-1} \binom{C-j+D}D \\ &= \sum_{j=0}^{C-1} \binom{i+j}{j} \binom{C-1-j+D}D \\ &= \sum_{j=0}^{C-1} \binom{i+j}{i} \binom{C-1-j+D}D \\ % &= \sum_{j=0}^{C-1} \binom{i+j}{i} \binom{C-1-j+D}D \\ % &= \binom{i+C+D}{i+D+1} \\ % &= \binom{i+C+D}{C-1}. \quad\qed \end{aligned} $$

よって、下記が成り立てばよい。 $$ \sum_{j=0}^{C-1} \binom{i+j}{i} \binom{C-1-j+D}D = \binom{i+D+C}{i+D+1}. $$

Lemma 3: 下記が成り立つ。

$$ \sum_{i=0}^{k-1} \binom{n+i}{n} \binom{m+k-1-i}{m} = \binom{n+m+k}{n+m+1}. $$

note: Pascal の三角形の $n$ 列目と $m$ 列目から $k$ 個持ってきたものの畳み込みに相当する。

                           .  
                         .   .  
                       .   .   A  
                     .   .   B   .  
                   .   .   C   .   .  
                 .   .   D   .   .   .  
               .   .   E   .   .   .   e  
             .   .   .   .   .   .   d   .  
           .   .   .   .   .   .   c   .   .  
         .   .   .   .   .   .   b   .   .   .  
       .   .   .   .   .   .   a   .   .   .   .  
     .   .   .   .   .   .   .   .   .   .   .   .  
   .   .   .   .   .   .   .   .   .   .   .   .   .  
 .   .   .   .   .   .   .   .   .  [*]  .   .   .   .  

Proof

$m$, $k$ に関する帰納法で示す。

$$ P(m, k) \iff \ForallL{n}{\sum_{i=0}^{k-1} \binom{n+i}{n} \binom{m+k-1-i}{m} = \binom{n+m+k}{n+m+1}} $$ とする。

To-be-proved 1: $P(m, 1)$

$$ \begin{aligned} \sum_{i=0}^{1-1} \binom{n+i}{n} \binom{m+1-1-i}{m} &= \binom{n}{n} \binom{m}{m} \\ &= 1, \\ \binom{n+m+1}{n+m+1} &= 1. \end{aligned} $$

To-be-proved 2: $P(m, k+1) \wedge P(m+1, k) \implies P(m+1, k+1)$

$$ \begin{aligned} &\phantom{{}={}} \sum_{i=0}^{(k+1)-1} \binom{n+i}{n} \binom{(m+1)+(k+1)-1-i}{m+1} \\ &= \sum_{i=0}^{(k+1)-1} \binom{n+i}{n} \left(\binom{(m+1)+(k+1)-1-i-1}{(m+1)-1} \right. \\ & \qquad\qquad\qquad\qquad\quad {} + \left. \binom{(m+1)+(k+1)-1-i-1}{m+1}\right) \\ &= \sum_{i=0}^{(k+1)-1} \binom{n+i}{n} \binom{(m+1)+(k+1)-1-i-1}{(m+1)-1} \\ &\qquad\qquad {} + \sum_{i=0}^{k-1} \binom{n+i}{n} \binom{(m+1)+(k+1)-1-i-1}{m+1} \\ &\qquad\qquad {} + \binom{n+k}{n} \binom{(m+1)+(k+1)-1-k-1}{m+1} \\ &= \sum_{i=0}^{(k+1)-1} \binom{n+i}{n} \binom{m+(k+1)-1-i}{m} + \sum_{i=0}^{k-1} \binom{n+i}{n} \binom{(m+1)+k-1-i}{m+1} + 0 \\ &= \binom{n+m+(k+1)}{n+m+1} + \binom{n+(m+1)+k}{n+(m+1)+1} \\ &= \binom{n+(m+1)+(k+1)}{n+(m+1)+1}. \quad\qed \end{aligned} $$

なんかふつうにお出しされた。最初から教えてよ〜 www.wolframalpha.com

変数を $k$ から $d$ に変えたら出してくれなくなり、これマジ?となりました。 www.wolframalpha.com

比較のために見ておくと、Vandermonde identity は下記のような畳み込みです。うまいこと帰着できたりするのかな?

                               .  
                             .   .  
                           .   .   .  
                         .   .   .   .  
                       A   B   C   D   .  
                     .   .   .   .   .   .  
                   .   .   .   .   .   .   .  
                 d   c   b   a   .   .   .   .  
               .   .   .   .   .   .   .   .   .  
             .   .   .   .   .   .   .   .   .   .  
           .   .   .   .   .   .   .   .   .   .   .  
         .   .   .  [*]  .   .   .   .   .   .   .   .  

Corollary 4: 任意の $n\ge 1$ に対して下記が成り立つ。

$$ \sum_{i=0}^{k-1} \binom{n+i}{n} \binom{m+k-1-i}{m} = \sum_{i=0}^{k-1} \binom{n-1+i}{n-1} \binom{m+k-i}{m+1} $$

Proof: Lemma 3 より明らか。

これを示して $n = 0$ に帰着させることで Hockey-stick identity から Lemma 3 を示す方針を考えていましたが、こちらが Corollary になりました。

おまけ

ここまでの考察で、$b_i$ を incremental に求めることで $i$ ごとに定数時間で求められることがわかりました。コンテスト中はそれで十分でしたが、もう少し進めてみます。

下記が成り立ちます。 $$ \begin{aligned} b_i &= \sum_{j=1}^C \binom{B-i-1+j}{j} \binom{C-j+D}{D} \\ &= \sum_{j=1}^C \binom{B-i+j-1}{B-i-1} \binom{C-j+D}{D} \\ &= \sum_{j=0}^{C-1} \binom{B-i+j}{B-i-1} \binom{C-1-j+D}{D} \\ &= \left( \sum_{j=0}^{C} \binom{B-i-1+j}{B-i-1} \binom{C-j+D}{D} - \binom{B-i-1}{B-i-1} \binom{C+D}{D} \right) \\ &= \binom{(B-i-1)+D+(C+1)}{(B-i-1)+D+1} - \binom{C+D}{D} \\ &= \binom{B+C+D-i}{B+D-i} - \binom{C+D}{D} \\ &= \binom{B+C+D-i}{C} - \binom{C+D}{D} \\ \end{aligned} $$

よって、冒頭に挙げた前者のケースの通り数は $$ \begin{aligned} &\phantom{{}={}} \sum_{i=0}^{B-1} \binom{A-1+i}{i} \sum_{j=1}^C \binom{B-i-1+j}{j} \binom{C-j+D}{D} \\ &= \sum_{i=0}^{B-1} \binom{A-1+i}{i} \left( \binom{B+C+D-i}{C} - \binom{C+D}{D} \right) \\ &= \sum_{i=0}^{B-1} \binom{A-1+i}{A-1} \binom{B+C+D-i}{C} - \binom{C+D}{D} \sum_{i=0}^{B-1} \binom{A-1+i}{A-1} \\ &= \sum_{i=0}^{B-1} \binom{A-1+i}{A-1} \binom{B+C+D-i}{C} - \binom{A-1+B}{A} \binom{C+D}{D} \end{aligned} $$ となります。後者のケースと合わせて、全体の答えは $$ \begin{aligned} &\phantom{{}={}} \sum_{i=0}^{B-1} \binom{A-1+i}{A-1} \binom{B+C+D-i}{C} \\ &\qquad\quad {} - \binom{A-1+B}{A} \binom{C+D}{D} + \binom{A+B}{A} \binom{C+D}{D} \\ &= \sum_{i=0}^{B-1} \binom{A-1+i}{A-1} \binom{B+C+D-i}{C} + \binom{A+B-1}{A-1} \binom{C+D}{D} \\ &= \sum_{i=0}^{B} \binom{A-1+i}{A-1} \binom{B+C+D-i}{C} \end{aligned} $$ とできますね。

また、対称性から、入力が $(A, B, C, D)$ のときの答えと $(D, C, B, A)$ のときの答えが一致することもわかります。実際、 $$ \begin{aligned} &\phantom{{}={}} \sum_{i=0}^{B} \binom{A-1+i}{A-1} \binom{B+C+D-i}{C} \\ &= \sum_{i=0}^{C} \binom{D-1+i}{D-1} \binom{C+B+A-i}{B} \\ &= \sum_{i=0}^{C} \binom{D-1+C-i}{D-1} \binom{B+A+i}{B} \\ &= \sum_{i=0}^{C} \binom{A+B+i}{B} \binom{D-1+C-i}{D-1} \\ \end{aligned} $$ となり、公式解説 と同じ式が得られました。

ということで、この畳み込みも高速化できないものかね?という気持ちになります。下記が成り立つらしいです。 $$ \begin{aligned} &\phantom{{}={}} \sum_{i=0}^{k-1} \binom{n+r+i}{n} \binom{m+k-1-i}{m} \\ &= \binom{k+m-1}{m}\binom{n+r}{r} {}_3 F_2 (1, 1-k, n-r+1; -k-m+1, r+1; 1). \end{aligned} $$ なんやかんやで答えは下記になります。 $$ \begin{aligned} \binom{A+B}{A} \binom{C+D-1}{C} {}_3 F_2(1, -C, A+B+1; A+1, -C-D+1; 1) \end{aligned} $$ ${}_3 F_2$ を高速に有理数表示で計算する方法(の存在)がわからず、おわりです...

定義通りの $$ {}_p F_q(a_1, \dots, a_p; b_1, \dots, b_q; z) = \sum_{n=0}^{\infty} \frac{(a_1)_n\cdot\cdots\cdot(a_p)_n}{(b_1)_n\cdot\cdots\cdot(b_q)_n}\frac{z^n}{n!} $$ の $n$ 項目の各 factor を持っておけば、次の項は $O(p+q)$ 時間程度で計算できます。$n\ge C$ のとき $(-C)_n = 0$ なので先頭 $C$ 項だけ見ればよいことから $O(C)$ 時間で求められはしますが、なんだかな〜という感じですね。

もしかして二項係数関連のどうにもならない積和を超幾何関数として追いやっているだけですか?

おまけのおまけ

$$ \begin{aligned} {}_3 F_2 (1, 1-k, n+1; -k-m+1, 1; 1) &= {}_2 F_1 (1-k, n+1; -k-m+1; 1) \end{aligned} $$ が成り立つらしいので、$r=0$ を考えて $$ \begin{aligned} {}_2 F_1 (1-k, n+1; -k-m+1; 1) = \binom{n+m+k}{n+m+1} \binom{k+m-1}{m}^{-1} \end{aligned} $$ であり、$(k-1, n+1, m-a) = (a, b, c)$ として $$ \begin{aligned} {}_2 F_1 (-a, b; -c; 1) = \binom{b+c}{b+c-a} \binom{c}{c-a}^{-1} \end{aligned} $$ を得ます。

あとがき

たぶん失敗方針ではありそうな気もしつつ、おもしろい等式を知ることができたのでよかったな〜という感じです。 超幾何関数とも知り合いになれました。

この手の問題で、「とりあえず愚直を書いて、差分を睨んでいたら二項係数が出てくるだろうから、エスパーして合わせればなんとかなる」みたいなやり方ばかりやっているのはやっぱりよくないと思います。でも頭を使わずに済むから楽なんですよね。頭を使いたくないなら ABC をやめればいいのに... とも思いますし、ABC で頭を使っている時点で負けという気もします。

気づいたら日曜が終わろうとしているのは納得いきません。本題の浮動小数点数シリーズの方は進捗がありません。えびちゃんだって進捗を出したいと思っています。

おわり

おわりです。

*1:$,$ と $;$ は誤植ではなく、一般には ${}_p F_q(a_1, \dots, a_p; b_1, \dots, b_q; z)$ という形をしているみたいです。

*2:実際、Wolfram|Alpha に計算させてもその値になりました。また、整数からちょっとだけずらして $\Gamma$ を計算すると、たしかにそれっぽい値に近づきそうではありました。

correct rounding への道 (5) table-maker’s dilemma

やりましょう。

前回、double-double や triple-double の話をしましたが、「triple-double は必要なのか?」とか「quadruple-double は不要なのか?」とかについて触れた方がいいかなと思ったので、先にそういう話をします。重要なお話です。

disclaimer: 重要なお話ではあるものの、計算するにあたってのアルゴリズムなどの話ではないです。

記法

多くの記法については前回定義したものを使います。特に、double で表せる浮動小数点数全体からなる集合を $F$ とします。

今回以降、実数の表記として主に指数表記つきの 16 進法や 2 進法を使っていきます。たとえば $$ \pi = {\small 3.14159265358979}{\footnotesize 323846264338327}{\scriptsize 950288419716939}{\tiny 937510582097494{\ldots}} $$ ではなく $$ \begin{aligned} \pi &= \texttt{1}.\texttt{{\small 921FB54442D18}{\footnotesize 469898CC51701}{\scriptsize B839A252049C1}{\tiny 114CF98E80417{\ldots}}}_{(16)}\times 2^{1} \\ &= 1. \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1011}_{\texttt{B}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 1101}_{\texttt{D}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \\ & \qquad \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0110}_{\texttt{6}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \underbrace{\footnotesize 0111}_{\texttt{7}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \\ & \qquad \underbrace{\footnotesize 1011}_{\texttt{B}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 0011}_{\texttt{3}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1010}_{\texttt{A}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \\ & \qquad \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 1110}_{\texttt{E}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0001}_{\texttt{1}} {\footnotesize\,} \underbrace{\footnotesize 0111}_{\texttt{7}} {\footnotesize\,} {\footnotesize \,\dots\,}_{(2)} \times 2^1 \end{aligned} $$ のように書きます。指数の底は $2$ です。2 進法で書く場合は 4 桁ごとに区切って 16 進法での値を添えるようにします。

言語によっては、浮動小数点型の 16 進法リテラルがサポートされていて、0x1.921FB54442D18p+1 のように書くこともできます。

C ではリテラルとして可能で、Python でも float.fromhex() を使うことで可能です。Rust では標準ではできず、hexfloat2 などのクレートを使う必要があります*1。また、出力に関しても、C では printf("%a", x) としたり、Python では x.hex() とすることで可能です。Rust はお察しです。

å°Žå…¥

例

$\sin(x)$ を計算したくなったことにしましょう。

ということで、次の手続き $\textsc{Fr54-Sin}$ を考えます*2。

  • 任意の $x\in F\smallsetminus\{-\infty, 0, +\infty\}$ を入力とする。
  • 下記を満たす $y\in \Q$ を出力する。
    • ある組 $(s, e, m) \in \{0, 1\}\times\Z\times([2^{53}\lldot 2^{54})\cap\N)$ が一意に存在して ${y = (-1)^s\cdot m\cdot 2^{e-53}}$ が成り立つ。
    • 上記の $e$ に対して $|y-\sin(x)|\le 2^{e-53}$ が成り立つ。

$\roundcirc{\sin(x)} = \roundcirc{\textsc{Fr54-Sin}(x)}$ として求めようというわけです。

たとえば $x = \texttt{1}.\texttt{D037CB27EE6DF}_{(16)}\times 2^{-3}\in F$ を考えます。 実際の計算結果は下記の通りです。 $$ \begin{aligned} &\phantom{{}={}} \textsc{Fr54-Sin}(x) \\ &= \texttt{1}.\texttt{CC40C3805229A8}_{(16)}\times 2^{-3} \\ &= 1. \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0011}_{\texttt{3}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1010}_{\texttt{A}} {\footnotesize\,} \underbrace{\footnotesize 1\hphantom{000}}_{\texttt{8}} {}_{(2)} \times 2^{-3} \end{aligned} $$ 最後の部分について、$\underbrace{1010}_{\texttt{A}}\,100\ldots01\ldots$ と $\underbrace{1010}_{\texttt{A}}\,011\ldots10\ldots$ のどちらを丸めた結果なのかを判別できません。前者であれば、$\roundcirc{\sin(x)} = \texttt{1}.\texttt{CC40C3805229B}_{(16)}\times 2^{-3}$ ですし、後者であれば $\roundcirc{\sin(x)} = \texttt{1}.\texttt{CC40C3805229A}_{(16)}\times 2^{-3}$ なので、$\textsc{Fr54-Sin}(x)$ を用いて求めることはできません。

note: $x\in\Q\smallsetminus\{0\}$ より $\sin(x)\notin\Q$ なので、$\sin(x) = \texttt{1}.\texttt{CC40C3805229A8}_{(16)}\times 2^{-3}$ となることはありません。

ということで、もう少し高い精度で計算してみましょう。同様に $\textsc{Fr55-Sin}(x)$ を定義して計算します。 $$ \begin{aligned} &\phantom{{}={}} \textsc{Fr55-Sin}(x) \\ &= \texttt{1}.\texttt{CC40C3805229A8}_{(16)}\times 2^{-3} \\ &= 1. \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0011}_{\texttt{3}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1010}_{\texttt{A}} {\footnotesize\,} \underbrace{\footnotesize 10\hphantom{00}}_{\texttt{8}} {}_{(2)} \times 2^{-3}. \end{aligned} $$ まだ同様の理由でだめですね。さらに高い精度でやりましょう。

〜しばらく繰り返し〜

$$ \begin{aligned} &\phantom{{}={}} \textsc{Fr105-Sin}(x) \\ &= \texttt{1}.\texttt{CC40C3805229A8000000000000}_{(16)}\times 2^{-3} \\ &= 1. \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0011}_{\texttt{3}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1010}_{\texttt{A}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} \\ & \qquad \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} {}_{(2)} \times 2^{-3}. \end{aligned} $$ ま、まだだめです...。

$$ \begin{aligned} &\phantom{{}={}} \textsc{Fr106-Sin}(x) \\ &= \texttt{1}.\texttt{CC40C3805229A7FFFFFFFFFFFF8}_{(16)}\times 2^{-3} \\ &= 1. \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0100}_{\texttt{4}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0011}_{\texttt{3}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1010}_{\texttt{A}} {\footnotesize\,} \underbrace{\footnotesize 0111}_{\texttt{7}} {\footnotesize\,} \\ & \qquad \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1\hphantom{000}}_{\texttt{8}} {\footnotesize\,} {}_{(2)} \times 2^{-3}. \end{aligned} $$ わ、ようやく区別がつきました。 これにより $$ \sin(x) \lt \texttt{1}.\texttt{CC40C3805229A8}_{(16)}\times 2^{-3} $$ が言えるので、 $$ \roundcirc{\sin(x)} = \texttt{1}.\texttt{CC40C3805229A}_{(16)}\times 2^{-3} $$ であるとわかったことになります。めでたしめでたし*3。実際のところ、 $$ \sin(x) = \texttt{1}.\texttt{{\small CC40C3805229A}{\footnotesize 7FFFFFFFFFFFF}{\scriptsize 83E76BBF0FCBE}{\tiny 19AFB5AC38007{\ldots}}} {\,}_{(16)} \times 2^{-3} $$ です。

用語について

さて、上記の例では 106-bit での計算で十分でしたが、これが「106-bit で十分ですよ」ということを予め知っておくことは可能でしょうか? 平方根など一部の例では可能ですが、三角関数を始めとする多くの関数ではこれは難しいということになっています。

William M. Kahan*4は次のように記しています[6]。

Why can’t YW be rounded within half an ulp like SQRT ? Because nobody knows how much computation it would cost to resolve what I long ago christened “The Table-Maker’s Dilemma”.

拙訳:なぜ $Y^W$ は、平方根のように 0.5 ULP 以内(の誤差で収まるよう)に丸められないのか? それは、以前私が 数表作成者のジレンマ (Table-Maker’s Dilemma) と名づけた事象を解決するためにどれだけの計算が必要かを誰も知らないからだ。

note: table-maker’s dilemma は、しばしば TMD と略されます。

この後、$\tfrac18\cosh(\pi\sqrt{163}) - (2^{53}-1)$ を 53 ビットに正確に丸めた値を計算しようとして、精度を上げつつ ${\small 7401389035307055}.{\footnotesize 49999978}$ や ${\small 7401389035307055}.{\footnotesize 5000000000015}$ を得る話が続きます。先ほどの $\textsc{Fr106-Sin}(x)$ の例と同じような感じです。

No general way exists to predict how many extra digits will have to be carried to compute a transcendental expression and round it correctly to some preassigned number of digits.

拙訳:超越式(超越関数を含む式?)を計算して予め決められた桁数に正確に丸めるにあたり、どれだけの桁数を余分に持っておく必要があるかを予測する一般的な方法は存在しない。

Table と言っているのは、たぶん教科書の巻末に載っている三角関数表のようなもののことだと思います。下記のようなイメージですね。

$x$ $\roundcirc{\sin(x)}$
$\texttt{1}.\texttt{D037CB2700000}_{(16)}\times 2^{-3}$ $\texttt{1}.\texttt{CC40C37F69D51}_{(16)}\times 2^{-3}$
$\vdots$ $\vdots$
$\texttt{1}.\texttt{D037CB27EE6DF}_{(16)}\times 2^{-3}$ $\texttt{1}.\texttt{CC40C3805229A}_{(16)}\times 2^{-3}$
$\vdots$ $\vdots$
$\texttt{1}.\texttt{D037CB2800000}_{(16)}\times 2^{-3}$ $\texttt{1}.\texttt{CC40C3806348B}_{(16)}\times 2^{-3}$

これの各行について、正確に丸めた値を計算するにあたってどれだけの精度が必要なのかを予測できず、徐々に桁数を上げながら繰り返す*5にしてもどの程度待てばいいかわからないねえという感じでしょう。

たとえば、たかが 53-bit での correctly-rounded な値を得るための途中計算に $10^{3000}$ bits の精度が必要ないとどうして言い切れるでしょうか? 実際、先の $\sin(x)$ のような例が存在することも予想していなかった人が多いのではないかと思います。 下記のような例もあります。 $$ \begin{aligned} &\phantom{{}={}} \tan(\texttt{1}.\texttt{50486B2F87014}_{(16)}\times 2^{-5}) \\ &= 1. \underbrace{\footnotesize 0101}_{\texttt{5}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0111}_{\texttt{7}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 1110}_{\texttt{E}} {\footnotesize\,} \underbrace{\footnotesize 1011}_{\texttt{B}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1111}_{\texttt{F}} {\footnotesize\,} \underbrace{\footnotesize 1001}_{\texttt{9}} {\footnotesize\,} \underbrace{\footnotesize 1100}_{\texttt{C}} {\footnotesize\,} \underbrace{\footnotesize 0111}_{\texttt{7}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} \underbrace{\footnotesize 1000}_{\texttt{8}} {\footnotesize\,} \\ & \qquad \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0000}_{\texttt{0}} {\footnotesize\,} \underbrace{\footnotesize 0010}_{\texttt{2}} {\footnotesize\,} {\footnotesize \, \ldots} {\,}_{(2)} \times 2^{-5}. \end{aligned} $$

本題? 雑談?

double-double だけだと 106-bit 弱になってしまうので、先のような例では負けてしまいます。triple-double ではどうでしょうか? 足りますか? わかりません... いかがでしたか?

「100 台程度のワークステーションを使って 4 年かけて最悪ケースの探索をしました」という論文[2]が 2000 年に出ていますが、下記のように書かれています。

The results [...] give the worst cases for functions $\sin$, $\arcsin$, $\cos$, $\arccos$, $\tan$ and $\arctan$. For those functions, we have worst cases in some bounded domain only, because trigonometric functions are more difficult to handle than the other functions.

拙訳・要約:三角関数は他の関数たちより扱いが難しく、いくつかの区間における最悪ケースしか得られておりません。

そんなあ。

ということで、次からの記事は log や exp の correct rounding な実装の話になるかもしれないし、三角関数関連の話をするかもしれないし、サーベイの旅に出たっきり帰ってこないかもしれません。

Kahan も [6] で触れていますが、Bessel 関数 $J_n(x)$ のような名前のついた非初等関数や、$y^w$ のように複数の引数がある関数についての最悪ケースの探索はほぼ望み薄でしょう。身近なところだと atan2 とかはどうなるんでしょうね。hypot は超越関数ではないのでそこまで悲惨なことにはならないかもしれません。

これはあくまで感情側の話なのですが、標準ライブラリの実装が「ま〜 1–10 ULP くらいの誤差に収まっている気がするしいいでしょ(未証明)」という感じだったり規格側が「別に精度については規定しないよ」という感じだったりするのは困ります。一方で、規格側が「(たとえば)3 ULP までの誤差は許容する」と言ってきても「その値の根拠はどこから...?」となりますし、そもそも「可搬性は...?」となります。値自体の可搬性はないけど事後条件に関しては担保するよ*6みたいな? 可搬性を第一目標にするなら「この数学関数はこういうアルゴリズムで計算する」とでもすればよいですが、よりよい精度・速度のアルゴリズムが提案されたときにどうするの?という話にもなってきます。

というような旨のことが [7] の文献で書かれていて、次のような主張*7の節があります。

3.2 $\quad$ The only specification that makes sense is correct rounding

なかなか強めの思想な気もしないではないですが、まぁ否定もできないかなという感じです。

といった感じで、三角関数に関してはちゃんとした最悪ケースがまだわかっていないと思っているのですが、correct rounding を謳うライブラリたちはどうやっているのでしょう。Ziv’s rounding test をしながら最大 768-bit くらいの精度まで試すみたいな話はどこかで見たような気はしつつ、それで十分である証明なしには correct と主張できないはずですし。

次数 $d\ge 2$ の代数的数 $\alpha$ に対しては、ある定数 $C_{\alpha}$ が存在して任意の整数 $p$, $q$ ($q\ge 1$) に対して $|\alpha-\tfrac pq| \ge \tfrac{C_{\alpha}}{q^d}$ が成り立つことが Liouville によって示されているらしいですが、超越数は...?という感じで、参っています。どうしましょう? やはり枝刈り全探索するしか?

たとえば何らかのアルゴリズムによって高々 $2^{-768}$ 程度の相対誤差しかない値を得られるのであれば 53-bit の correct rounding にこだわる必要はないのでは?という主張は真っ当な気がしつつ、まぁそれはそれかなという気持ちでやっています。

参考文献

Vincent Lefèvre が大活躍すぎ。また、最後のサーベイ論文に関しては 2025 年(初版は 2024 年)で、びっくりです。

おわり

ということでえびちゃんはこれからお勉強です。フォロヮもごきげんよう。

おわりです。

*1:標準入りしてくれ〜〜〜。

*2:$\sin(x)$ の 54-bit 仮数部への faithful-rounded な値を返すという意味合いでの命名です。

*3:普通の人にとっては $\sin(x) \approx \texttt{1}.\texttt{CC40C3805229B}_{(16)}\times 2^{-3}$ で十分めでたくない?という気もします。普通の人のことは知りませんが。

*4:IEEE 754 の策定にも関わっておられる方で、とてもえらい方ですね。

*5:Ziv’s strategy とか Ziv’s rounding test とか呼ばれるものがあるようです。

*6:人々にはそもそも浮動小数点型ってそういうものだと思われていますか? う〜〜〜〜ん。

*7:いわゆる数学の意味合いでの Claim ではなくて、お気持ち表明に近いと思います。

浮動小数点数のお手軽チェックリスト

小さな誤差を積み重ねる操作と、積み重なった誤差に影響される操作を区別して考えましょう。

同じ符号の浮動小数点数を加算や乗算を計 $k$ 回繰り返しても、$(1+2^{-53})^k-1 \approx 2^{-53}\cdot k$ 程度の相対誤差しか生まれません。 競プロにおける演算回数はせいぜい $2^{30}$ 回程度でしょうから、だいたい $2^{-23}\approx 1.19\times 10^{-7}$ 程度の相対誤差に収まります。 $2^{20}$ 回なら $2^{-33}\approx 1.16\times 10^{-10}$ 程度です。

これらの操作が問題になることは基本的にないでしょう。 問題になる操作は主に三つです。整数への丸め、比較、引き算です。

具体例を交えながら見ていきます。

前提

IEEE 754 の規格に準拠した処理系を考えます。 C における double のデフォルトの挙動について考え、overflow や underflow については起きないこととします。 すなわち、考える浮動小数点数 $x$ は有限であり、下記を満たします。

  • $|x|\gt 0$ ならば、ある整数 $2^{52}\le m\lt 2^{53}$ と $-1022\le k\le 1023$ が存在し、$|x| = m\cdot 2^{k-52}$ が成り立つ。

デフォルトの丸め方は tiesToEven と呼ばれるもので、「double で表せる値のうち、与えられた実数に最も近いものを返す」というものです。表せる 2 数のちょうど中間の場合の tiebreak は厳密に定められていますが、込み入らない誤差評価においては問題になってこないので、今回は割愛します。 実数 $x$ を丸めたものを $\roundcirc x$ と書きます。$|x|\gt 0$ のとき下記が成り立ちます。

  • 任意の実数 $x$ に対し、実数 $|\varepsilon|\le 2^{-53}$ が存在し、$\roundcirc x = x\cdot(1+\varepsilon)$ が成り立つ。
  • 任意の実数 $x$ に対し、実数 $|\varepsilon|\le 2^{-53}$ が存在し、$x = \roundcirc x\cdot(1+\varepsilon)$ が成り立つ。

浮動小数点数における四則演算を $\oplus$, $\ominus$, $\otimes$, $\oslash$ と書き、$x \oplus y = \roundcirc{x+y}$ などが成り立ちます(他の各演算についても同様)。 すなわち、四則演算においては「無限精度で計算したものを正確に丸めたものが得られる」ということです。

たとえば $\tfrac43 = 1.333{\ldots}$ で、これと隣り合う double の値は $$ \begin{aligned} 6004799503160661\times 2^{-52} &= {\small 1.333333333333333}{\footnotesize 259318465024989}{\scriptsize 563971757888793}{\tiny 9453125}, \\ 6004799503160662\times 2^{-52} &= {\small 1.333333333333333}{\footnotesize 481363069950020}{\scriptsize 872056484222412}{\tiny 109375} \end{aligned} $$ です。近いのは前者ですから、$4\oslash 3 = 6004799503160661\times 2^{-52}$ となります。丸めモードを変えない限り、勝手に後者になってしまうことはありません。 特に、厳密に $$ \begin{aligned} 4\oslash 3 &= \frac{4-2^{-52}}3 \\ &= \frac43\cdot(1-2^{-54}) \end{aligned} $$ が成り立ちます。相対誤差が $2^{-53}$ 以下になっていることがわかりますね。

入門編

さて、問題になる二つの操作について見ていきましょう。

整数への丸め

床関数 $\floor x$ を考えます。次のような問題を見てみましょう。

何らかの式で表される実数 $x^{\ast}$ があるとします。これに対して $\floor{x^{\ast}}$ を出力してください。

なお、double で表せる任意の値 $x$ に対して $\roundcirc{\floor x} = \floor x$ が成り立ちます。すなわち、床関数によって誤差が生じることはありません*1。

簡単な例として、$x^{\ast} = 0.1 \times 10$ を考えてみます。 ここで、$\roundcirc{0.1} = \tfrac1{10}\cdot(1+2^{-54})$ かつ $\roundcirc{10} = 10$ です。 $x^{\ast}$ の近似値に相当する浮動小数点数を $x$ として書きます。 $$ \begin{aligned} x &= \roundcirc{0.1} \otimes \roundcirc{10} \\ &= \left(\tfrac1{10}\cdot (1+2^{-54})\right) \otimes 10 \\ &= \roundcirc{1+2^{-54}} \\ &= 1 \end{aligned} $$ です。一方、$0.1\times 10 = 0.1+0.1+\dots+0.1$ として計算した場合を考えてみます。 $$ \begin{aligned} x &= \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_{10} \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}1+\hphantom{0}1\cdot 2^{-54})\right) \oplus \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_9 \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}2+\hphantom{0}2\cdot 2^{-54})\right) \oplus \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_8 \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}3+\hphantom{0}8\cdot 2^{-54})\right) \oplus \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_7 \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}4+\hphantom{0}4\cdot 2^{-54})\right) \oplus \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_6 \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}5+\hphantom{0}0\cdot 2^{-54})\right) \oplus \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_5 \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}6-\hphantom{0}4\cdot 2^{-54})\right) \oplus \underbrace{\roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \dots \oplus \roundcirc{0.1}}_4 \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}7-\hphantom{0}8\cdot 2^{-54})\right) \oplus \roundcirc{0.1} \oplus \roundcirc{0.1} \oplus \roundcirc{0.1} \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}8-12\cdot 2^{-54})\right) \oplus \roundcirc{0.1} \oplus \roundcirc{0.1} \\ &= \left(\tfrac1{10}\cdot (\hphantom{0}9-16\cdot 2^{-54})\right) \oplus \roundcirc{0.1} \\ &= \left(\tfrac{1}{10}\cdot(10-20\cdot 2^{-54})\right) \\ &= 1 - 2^{-53}. \end{aligned} $$ すなわち、$x^{\ast}$ との相対誤差は $-2^{-53}\approx -1.11\times 10^{-16}$ です。(無駄な誤差が出ているとはいえ)十分に近い値が得られていると言って概ね差し支えないでしょう。

しかし、$\floor{x^{\ast}}$ を計算するとなると話は別です。 整数について考えたいときは相対誤差が興味の対象であることはあまりなく、絶対誤差が気になることが多いです。すなわち、$1$ 以上の絶対誤差は許容しないということです。 $\floor{1-2^{-53}} = 0$ ですから、これは許容できません。 相対誤差がどれだけ小さく抑えられていたとしても、$x$ が $\floor{x^{\ast}}$ より少しでも小さいと破綻してしまいます。

$\floor{x^{\ast}}-x$ の上界を $\delta$ とします。$x\oplus\delta\lt \floor{x^{\ast}}+1$ が常に成り立つのであれば、$\floor{x\oplus\delta}$ を計算すればよいです*2。

同符号の計算 $k$ 回程度の場合であれば、$\delta\approx kx^{\ast}\cdot 2^{-53}$ 程度にとってみて、問題ないかを調べるのがよいでしょうか。

一応、「わざわざ掛け算で済むところを足し算にしたからおかしくなった」という主張に対する反例も挙げておきます*3。 $1\oslash 49 = \tfrac1{49}\cdot(1-23\cdot 2^{-58})$ を使います。 $$ \begin{aligned} (1\oslash 49)\otimes 49 &= 1-2^{-53}, \\ \underbrace{(1\oslash 49)\oplus\dots\oplus(1\oslash 49)}_{49} &= 1+6\cdot 2^{-53} \end{aligned} $$ です。これは、足し算をした方のみが(たまたま)うまくいっていますね。

いずれにせよ、「たまたまうまくいくのを祈る」のではなくて、誤差評価をして補正項を入れるなりして「うまくいくことを示してやっている」にするのが重要です。 実際の具体例は後半の章で紹介します。

比較演算

本質的には整数への丸めとほぼ同様だと思います。たとえば本来 $x^{\ast} = 1$ かつ $y^{\ast} = 1$ となるべきところで $x = 1+2^{-52}$ かつ $y = 1$ となってしまっただけで $x\le y$ は成り立たないですね。

いわゆる eps を足し引きして調整するのが小手先テクニックとして広がっていますが、適切な値の決め方は前項の $\delta$ と同様です。

実数 $x^{\ast}$, $y^{\ast}$ のつもりの値として浮動小数点数 $x$, $y$ を得たとします。$x^{\ast} \le y^{\ast}$ の判定のために $x\le y$ とするのは妥当かな?という問題です。なにも考えないと下記のようになり得ます。

$x\ll y$ $x\le y$ $x\gt y$ $x\gg y$
$x^{\ast} \le y^{\ast}$ true true false (?) N/A
$x^{\ast} \gt y^{\ast}$ N/A true (?) false false

N/A と書いているのは、その欄の条件を満たすような入力が存在しないという意図です。 $x$ と $y$ が十分に離れているときは期待通り判定できますが、そうでないときは誤っちゃうかも?ということですね。

そこで、ある $\delta$ が存在して下記のようになったらうれしいです。

$x\le y+\delta$ $x\gt y+\delta$
$x^{\ast} \le y^{\ast}$ true N/A
$x^{\ast} \gt y^{\ast}$ N/A false

$x$ が $y$ より少しだけ大きかったときは $x^{\ast}\le y^{\ast}$ として判定したいということです。 右上や左下が N/A にならず、下記のようになってしまう場合はうれしくないです。

$x\le y+\delta$ $x\gt y+\delta$
$x^{\ast} \le y^{\ast}$ true false (?)
$x^{\ast} \gt y^{\ast}$ N/A false

$x\le y+\delta$ の判定は $x\ominus y\le \delta$ として行えば、$\tfrac y2\le x\le 2y$ ならば $x\ominus y=x-y$ が成り立つのでよいですね。 これは Sterbenz lemma と呼ばれる有名な補題から従います。 $x\gt 2y$ や $x\lt \tfrac y2$ のときは $x\gg y$ や $x\ll y$ の扱いになっているとうれしいです。

評価の方法としては、整数への丸めの際の $\delta$ と同様に行えばよいですね。 もちろん適切な $\delta$ は状況によります。「テンプレート用におすすめの eps はいくらですか?」のような質問*4はナンセンスであることがわかると思います。

桁落ち

はい、本題の中の本題です。桁落ち (catastrophic cancellation (of significant digits)) と呼ばれるものについて話します。 数が打ち消し合うことを指して cancellation と呼んでいます*5。 主要な桁同士が打ち消し合ったときに誤差の部分が無視できなくなるのが問題となります。 もちろん、浮動小数点数側は「自分は誤差をこのくらい含んでいます」というのを自覚しているわけではないので、「この項はこのくらい誤差を含んでいるねえ」というのは人間側が解析するしかないですね。

実数 $x^{\ast}$, $y^{\ast}$ の近似値として計算した浮動小数点数を $x$, $y$ とします。$x^{\ast} = x\cdot(1+\varepsilon_x)$ かつ $y^{\ast} = y\cdot(1+\varepsilon_y)$ としましょう。$|\varepsilon_x|$, $|\varepsilon_y|$ の上界は状況によるものです。

このとき、$x^{\ast}-y^{\ast} = (x-y) + (x\varepsilon_x - y\varepsilon_y)$ となります。$x-y$ がとても小さいと、$x\varepsilon_x - y\varepsilon_y$ が無視できなくなってきます。

Exercise 1: $x = 2^{52}+2$ かつ $y = 2^{52}+1$ に対して $\sqrt x-\sqrt y$ を計算せよ。

note: $\sqrt{1+x} = 1 + \tfrac12x - \tfrac18x^2 + \tfrac1{16}x^3 + O(x^4)$ が成り立つ。

Bad solution

$$ \begin{aligned} \sqrt{2^{52}+2} &= 2^{26}\cdot\sqrt{1+2^{-25}} \\ &= 2^{26}\cdot(1 + 2^{-26} - 2^{-28} + 2^{-29} + \cdots), \\ \sqrt{2^{52}+1} &= 2^{26}\cdot\sqrt{1+2^{-26}} \\ &= 2^{26}\cdot(1 + 2^{-27} - 2^{-29} + 2^{-30} + \cdots) \\ \end{aligned} $$ なので $$ \begin{aligned} \roundcirc{\sqrt{2^{52}+2}} &= 2^{26} + 2^{-26}, \\ \roundcirc{\sqrt{2^{52}+1}} &= 2^{26} \end{aligned} $$ が成り立つ。よって $$ \begin{aligned} \roundcirc{\sqrt{2^{52}+2}} \ominus \roundcirc{\sqrt{2^{52}+1}} &= (2^{26}+2^{-26})\ominus 2^{-26} \\ &= 2^{-26} \end{aligned} $$ である。よって $\sqrt x-\sqrt y \approx 2^{-26}$ である (?)。$\eod$

Better solution

$$ \begin{aligned} &\phantom{{}={}} \sqrt{2^{52}+2} - \sqrt{2^{52}+1} \\ &= \frac{(\sqrt{2^{52}+2} - \sqrt{2^{52}+1})\cdot(\sqrt{2^{52}+2} + \sqrt{2^{52}+1})}{\sqrt{2^{52}+2} + \sqrt{2^{52}+1}} \\ &= \frac{(2^{52}+2) - (2^{52}+1)}{\sqrt{2^{52}+2} + \sqrt{2^{52}+1}} \\ &= \frac1{\sqrt{2^{52}+2} + \sqrt{2^{52}+1}} \end{aligned} $$ であり、 $$ \begin{aligned} 1\oslash (\roundcirc{\sqrt x}\oplus \roundcirc{\sqrt y}) &= 1\oslash ( (2^{26}+2^{-26})\oplus 2^{26} ) \\ &= 1\oslash 2^{27} \\ &= 2^{-27} \end{aligned} $$ である。よって $\sqrt x-\sqrt y \approx 2^{-27}$ である。また、相対誤差は高々 $4\cdot 2^{-53}$ 程度である。$\eod$

Exercise 2: $x =2^{-53}$ に対して $\tfrac1x\cdot(e^x-1)$ を計算せよ。

Bad solution

$$ \exp(2^{-53}) = {\small 1.000000000000000}{\footnotesize 111022302462515}{\scriptsize 660205338988848}{\tiny 236989105051344{\dots}} $$ であり、 $$ \roundcirc{\exp(2^{-53})} = 1 + 2^{-52} $$ である*6。実際に計算し、 $$ \begin{aligned} &\phantom{{}={}} (\roundcirc{\exp(2^{-53})}\ominus 1)\oslash 2^{-53} \\ &= 2^{-52}\oslash 2^{-53} \\ &= 2 \end{aligned} $$ を得る。よって $\tfrac1x\cdot(e^x-1) \approx 2$ である (?)。$\eod$

Better solution(むずかしい)

$e^x-1$ を計算する際の桁落ちを避けたい。

$y = \roundcirc{\exp(x)}$ とする。ここで、ある実数 $x'$ に対して $y = \exp(x')$ が成り立つ。 これにより、$\roundcirc{\exp(x)}\ominus 1 = \exp(x')-1$ が成り立つため、$\tfrac1x\cdot(e^x-1)$ の代わりに $\tfrac1{x'}\cdot(e^{x'}-1)$ を計算することにする。

数値計算により $\roundcirc{\log(y)} = 2^{-52} - 2^{-105}$ を得る。よって、 $$ \begin{aligned} (y\ominus 1)\oslash \roundcirc{\log(y)} &= 2^{-52} \oslash (2^{-52} - 2^{-105}) \\ &= 1+2^{-52} \end{aligned} $$ となる。

さて、誤差評価をがんばる。 $$ \begin{aligned} \exp(x) &= \sum_{i=0}^{\infty} \frac{x^i}{i!}, \\ \frac{\exp(x)-1}x &= \sum_{i=0}^{\infty} \frac{x^i}{(i+1)!} \end{aligned} $$ であり、 $$ \begin{aligned} &\phantom{{}={}} \frac{\exp(x+h)-1}{x+h} - \frac{\exp(x)-1}x \\ &= \sum_{i=0}^{\infty} \frac{(x+h)^i}{(i+1)!} - \sum_{i=0}^{\infty} \frac{x^i}{(i+1)!} \\ &= \sum_{i=0}^\infty \frac1{(i+1)!}\sum_{j=1}^i \binom ij h^j x^{i-j} \\ % &= \frac1{2!} \left(\binom 11 h^1 x^0 \right) \\ % & \qquad\quad {} + \frac1{3!} \left(\binom 21 h^1 x^1 + \binom 22 h^2 x^0 \right) \\ % & \qquad\quad {} + \frac1{4!} \left(\binom 31 h^1 x^2 + \binom 32 h^2 x^1 + \binom 33 h^3 x^0 \right) \\ % & \qquad\quad {} + \cdots \\ % &= \left(\frac1{2!} \binom11 h^1 + \frac1{3!} \binom 22 h^2 + \frac1{4!} \binom 33 h^3 + \cdots \right) x^0 \\ % & \qquad\quad {} + \left(\frac1{3!} \binom21 h^1 + \frac1{4!} \binom 32 h^2 + \cdots \right) x^1 \\ % & \qquad\quad {} + \cdots \\ &= \sum_{i=0}^{\infty} \sum_{j=0}^{\infty} \frac1{(i+j+2)!} \binom{i+j+1}{j+1} h^{j+1} x^i \\ &= \sum_{j=0}^{\infty} \frac1{(j+2)!} h^{j+1} + \sum_{j=0}^{\infty} \frac{j+2}{(j+3)!} h^{j+1} x + O(x^2) \\ &= \left(\frac{\exp(h)-(1+h)}h\right) + \left(\frac{\exp(h)\cdot(h-1)+1}{h^2}-\frac12\right)x + O(x^2) \\ % &= \left(\frac{\tfrac12h^2 + O(h^3)}h\right) + \left(\frac{(1+h+\tfrac12h^2+O(h^3) )\cdot(h-1)+1}{h^2}-\frac12\right)x + O(x^2) \\ % &= \left(\frac12h + O(h^2)\right) + \left(\frac{(h+h^2+\tfrac12h^3 - 1-h-\tfrac12h^2-\tfrac16h^3 + O(h^4) )+1}{h^2}-\frac12\right)x + O(x^2) \\ % &= \left(\frac12h + O(h^2)\right) + \left(\frac{(\tfrac12h^2+\tfrac13h^3 + O(h^4) )}{h^2}-\frac12\right)x + O(x^2) \\ &= \left(\tfrac12h + O(h^2)\right) + \left(\tfrac13h + O(h^2)\right)x + O(x^2) \\ &= \tfrac h2 + O(h(x+h) ) \end{aligned} $$ になると思う。また、$\tfrac1{x'}(y-1)$ と $(y\ominus 1)\oslash \roundcirc{\log(y)}$ の相対誤差は $2\cdot 2^{-53}$ 程度である。真の値は $1$ 程度であるから、絶対誤差も $2\cdot 2^{-53}$ 程度である。 $h$ は高々 $2^{-53}$ 程度なので、合わせて $2.5\cdot 2^{-53}$ 程度の絶対誤差であることがわかる。$\eod$

Sterbenz lemma は、$x$ と $y$ の比が $\tfrac12$ 以上 $2$ 以下のときは $x\ominus y = x-y$ になる(差による誤差が生じない)ということを主張しています。これはあくまで「入力の浮動小数点数からの誤差がない」というだけで、入力の浮動小数点数が誤差を含む場合は「出力もそのままその誤差を含む」ということになります。 逆に、入力の誤差がない場合は誤差なく引き算できるので、「近い値の引き算は怖いから...」などと変な怯え方をする必要はありません。

このような、入力に誤差がないことがわかっている状況での cancellation は、benign cancellation と呼ばれることがしばしばあります。 日本語での定訳があるかはわかりません。

実践編

解析のやり方とかも含めて追っていきましょう。コンテスト中でもできるように、手軽な感じの手法を心がけます。

整数への丸め

reference: AGC 047 A - Integer Product

整数部分が 4 桁以下、小数部分が 9 桁以下であるような実数 $A_i$ が与えられます。解法の詳細には触れませんが、$10^9\cdot A_i$ を計算したくなります。 しかし、$\floor{10^9\otimes \roundcirc{A_i}}$ として計算すると失敗するような例はいくらでもあります。 $$ \begin{aligned} \roundcirc{1024.000000006} &= {\small 1024.000000005999936}{\footnotesize 547596007585525}{\scriptsize 5126953125} \\ &= 4503599627396884\cdot 2^{-42}, \\ \roundcirc{1024.000000006} \otimes 10^9 &= {\small 1024000000005.9998779296875} \\ &= 8388608000049151 \cdot 2^{-13}. \end{aligned} $$

ある $\delta$ を導入して $\floor{(10^9\otimes \roundcirc{A_i})\oplus\delta}$ を考える方法を考えましょう。

以下、$x = \roundcirc{A_i}$ とします。 $$ \begin{aligned} |x - A_i| &\le x\cdot 2^{-53}, \\ |10^9\otimes x - 10^9\cdot x| &\le 10^9\cdot x\cdot 2^{-53} \end{aligned} $$ が成り立つので、三角不等式より $$ \begin{aligned} |10^9\otimes x - 10^9\cdot A_i| &= |10^9\otimes x - 10^9\cdot x| + |10^9\cdot x - 10^9\cdot A_i| \\ &\le 10^9\cdot x\cdot 2^{-53} + 10^9\cdot x\cdot 2^{-53} \\ &\lt 2\cdot 10^9\cdot 10^5\cdot 2^{-53} \\ &\lt 0.022204\\ &\lt \roundcirc{0.02221} \end{aligned} $$ が成り立ちます。よって、 $$ 10^9\cdot A_i - 0.022204 \lt 10^9\otimes x \lt 10^9\cdot A_i + 0.022204 $$ であり、 $$ \begin{aligned} 10^9\cdot A_i \lt 10^9\otimes x + \roundcirc{0.02221} &\lt 10^9\cdot A_i + 0.022204 + \roundcirc{0.02221} \\ &\lt 10^9\cdot A_i + 1 \end{aligned} $$ です。また、$10^9\cdot A_i\lt 2^{52}$ から、$(10^9\otimes x) \oplus \roundcirc{0.02221} \lt 10^9\cdot A_i + 1$ が成り立ちます。 よって、$\floor{(10^9\otimes x) \oplus \roundcirc{0.02221}} = 10^9\cdot A_i$ が従います。わざわざ細かい桁まで指定せず、$\delta = \roundcirc{0.03}$ として問題ないでしょう。

note: たとえば $2^{52} + \roundcirc{0.6} \lt 2^{52}+1$ ですが、$2^{52}\oplus\roundcirc{0.6} = 2^{52}+1$ なので、そのような部分が問題ないかも気をつける必要があります。

note: 差が $0.5$ 未満で抑えられていることから、.round() を用いる解法も正当であることがわかりますね。

提出コード:提出 #65357163


次の問題です。

reference: ABC 169 C - Multiplication 3

これは double だけで解けるでしょうか?

たとえば $(A, B) = (999689151469700, 9.01)$ などのケースで $\floor{AB}$ 自体を double で表すことができないので、「答えを double で返す」という前提では厳しそうですね。 $100B$ を $901$ 以上 $999$ 以下の奇数として固定し、 $$ A = \left(2\Floor{\frac12\Ceil{\frac{2^{53}}{100B}}}+1\right)\cdot 100 $$ とすることで、所望の $(A, B)$ を構成することができます。

このように、「そもそも答えを double で表せるのか?」と考えるのは重要です。$2^{53}+1$ 以上の整数は double で表せるとは限らないので、「答えがそういう値になる入力を構成できるか?」という視点で考えてみるのがよいですね。

先の例と同様にして xx.yy を補正しつつ 100 倍するなどして xxyy にしてから整数型として扱うのが賢明でしょう*7。

仮数部が 64-bit や 113-bit の浮動小数点型を使える言語であれば、それを使えば可能な気はします。もちろん素朴に floor(a * b) とするだけではだめですが、前述のように補正項を入れるなどで対応することはできますね。「誤差とかよくわかんないけど long double を使って強引に通した」ではなくて「long double だと仮数部のビット長が足りるので long double を使いました」と言えるようになってほしいものです*8。

ということで、ここでは、64-bit 仮数部の型を使いましょう。 求めたいのは $\floor{AB}$ で、計算可能なのは $A\otimes\roundcirc B$ です。

実数 $|\varepsilon_B|, |\varepsilon_{\otimes}|\le 2^{-64}$ が存在して $$ A\otimes\roundcirc B = \left(A\cdot (B\cdot (1+\varepsilon_B) )\right)\cdot (1+\varepsilon_{\otimes}) $$ が成り立つので、 $$ \begin{aligned} |A\otimes\roundcirc B - AB| &= AB\cdot ( (1+2^{-64})^2-1) \\ &\lt 10^{15}\cdot 9.99\cdot 1.085\times 10^{-19} \\ &\lt 1.084\times 10^{-3} \end{aligned} $$ が成り立ちます。

今回は、$AB$ が整数とは限らないため、先のものよりも上界はぎりぎりです。 $$A\otimes \roundcirc B - \floor{AB} \in (-0.001084\lldot 0.991084)$$ であり、 $$(A\otimes \roundcirc B)\oplus \roundcirc{0.002} - \floor{AB} \in (0.00092\lldot 0.99309)$$ なので、$\floor{(A\otimes \roundcirc B)\oplus \roundcirc{0.002}}$ を計算すればよいですね。

提出コード:提出 #65358049

比較演算

大まかな考え方は前項と同じでしょうから、ここで特に話すことはないような気がしています。気が変わったら追記します。

桁落ち

二つの実数を異なる式で計算し、「本来それらは差が $0$ なので問題ないはずだ」というのを前提としているような例では、桁落ちで撃墜できることが多いです。

減算をしない形*9を意識して式変形を行うのが重要でしょう。 分数であれば、分子 $n$ と分母 $d$ を整数で計算し、$\roundcirc n\oslash \roundcirc d$ として計算するのがよさそうです。

実数 $|\varepsilon_n|, |\varepsilon_d|, |\varepsilon_{\oslash}|\le 2^{-53}$ が存在して $$ \roundcirc n\oslash \roundcirc d = \frac{n\cdot(1+\varepsilon_n)}{d\cdot(1+\varepsilon_d)}\cdot(1+\varepsilon_{\oslash}) $$ と書けるので、 $$ \begin{aligned} |(\roundcirc n\oslash \roundcirc d) - (n\div d)| &= \frac nd\cdot \left| \frac{(1+\varepsilon_n)(1+\varepsilon_{\oslash})}{1+\varepsilon_d} -1 \right| \\ &\le \frac nd\cdot \left| \frac{(1+2^{-53})^2}{1-2^{-53}} -1 \right| \\ % &= \frac nd\cdot \left| 1 - (1+2^{-53})^2 \cdot \sum_{i=0}^{\infty} {(2^{-53})^i} \right| \\ % &= \frac nd\cdot \left| 1 - (1+2^{-53})^2 \cdot \left(1+2^{-53}+\sum_{i=2}^{\infty} {(2^{-53})^i}\right) \right| \\ % &= \frac nd\cdot \left| 1 - (1+2^{-53})^3 - (1+2^{-53})^2\cdot \sum_{i=2}^{\infty} {(2^{-53})^i} \right| \\ % &= \frac nd\cdot \left| 1 - (1+2^{-53})^3 - (1+2^{-53})^2\cdot \frac{(2^{-53})^2}{1-2^{-53}} \right| \\ % &= \frac nd\cdot \left| 1 - (1+2^{-53})^3 - (2^{-53})^2\cdot \frac{(1+2^{-53})^2}{1-2^{-53}} \right| \\ &= \frac nd\cdot \left( (1+2^{-53})^3 - 1 + (2^{-53})^2\cdot \frac{(1+2^{-53})^2}{1-2^{-53}} \right) \\ &\lt \frac nd\cdot (3+10^{-15})\cdot 2^{-53} \end{aligned} $$ となり、$3\cdot 2^{-53}$ 程度の相対誤差で抑えられます。

分子や分母に根号が含まれる場合は、分子と分母の適切な方の有理化を行うことで減算を避けるのがよさそうです。それ以外の無理数がある場合は、たぶん困ります。 三角関数であれば和積公式などが使える場合もあるかも。

具体的に撃墜するケースの構築や式変形の方法の例については、別の記事で過去に書いたのでそちらを参考にしてください。

rsk0315.hatenablog.com

同符号のたくさんの積和

例として期待値の DP とかを考えてみましょう。

reference: ABC 402 E - Payment Required

まず最初に、整数 $p$ に対しての $(100 - p) \oslash 100$ と $1 \ominus (p\oslash 100)$ の誤差の違いについて考えてみたりしますか? 前者は誤差の出る操作が 1 回なので $2^{-53}$ 程度の相対誤差なのに対し、後者は catastrophic cancellation の影響でもっと大きな誤差が出そうですね。実際*10、 $$ \begin{aligned} % 1\oslash 100 &= \frac{1+3\cdot 2^{-57}}{100}, \\ 99\oslash 100 &= \frac{99-2^{-50}}{100} \end{aligned} $$ なので、Sterbenz lemma を使いつつ $$ \begin{aligned} 1\ominus (99\oslash 100) &= 1 - \frac{99-2^{-50}}{100} \\ &= \frac{1+2^{-50}}{100} \\ &= 0.01 \cdot (1+8\cdot 2^{-53}) \end{aligned} $$ となり、本来の上界の $8$ 倍程度の誤差が無駄に出ています。実際にこの誤差が問題になることは少ないでしょうが、わざわざ誤差の出る方の形を好む必要もないでしょうね。

さて、DP で計算される値(ここでは期待値)の誤差はどうなるでしょう? 雑に見積もっても、遷移は $2^8\cdot 5000$ 回で抑えられます、遷移は $$ \mathrm{dp}[i] = (p_0\otimes \mathrm{dp}[i_0]) \oplus (p_1\otimes \mathrm{dp}[i_1]) $$ のような式になり、$p_0$ や $p_1$ にそれぞれ計算 $1$ 回分の相対誤差があるので、遷移あたり計算 $5$ 回分の相対誤差で抑えられます*11。

ということで、計 $2^8\cdot 5000\cdot 5 = 6.4\times 10^6$ 回分になり、$2^{-53}\cdot 6.4\times 10^6 \approx 7.1\times 10^{-10}$ 程度の相対誤差で抑えられることがわかります。許容誤差は $10^{-6}$ なので問題なさそうですね。

おまけ

二分探索

よく $x_U - x_L \ge \roundcirc{10^{-9}}$ などをループ継続条件にして失敗する人がいますね。 たとえば $(x_L, x_U) = (10^8, x_L+2^{-26})$ を考えてみます。 $x_L \lt x\lt x_U$ を満たすような $x$ は存在しませんが、$2^{-26}\gt 10^{-9}$ が成り立ちます。 このとき $(x_L\oplus x_U)\oslash 2 = x_L$ なので、更新式の条件的に $x_U \gets x_L$ で更新されない場合は無限ループとなります。

そこで、よく「浮動小数点数で二分探索をするときは、差で判定するんじゃなくて回数でループするといいんだよ」ということが言われています。 果たして回数でのループで問題ないことは明らかでしょうか? その回数はどの程度? 得られる精度はどのくらい? $[x_L\lldot x_U]$ の範囲でない値がうっかり返ってしまうことはない? など、「それっぽいから大丈夫そう」でやっている人はやっぱり多いのではないでしょうか。

$|x\oplus y| \lt \infty$ ならば $(x\oplus y)\oslash 2 = \roundcirc{\tfrac{x+y}2}$ が成り立ちます。 よって、$x\le y$ ならば $x \le (x\oplus y)\oslash 2\le y$ となりますね。

たとえば $(x_L\oplus x_U)\oslash 2\notin \{x_L, x_U\}$ をループ継続条件にしてもよさそうな気がしていますが、どうでしょう。 どのくらいの回数で収束してくれるでしょうか。

今回の記事のスコープとは微妙に外れている気がするので、次回以降の記事で気が向いたら解析してみることにします。

練習問題

浮動小数点数や誤差が関連している問題を挙げておきます。必ずしも「double で解けますよ〜」という意味ではありません。その見極めも含めて使ってもらえたらいいんじゃないかなと思います。

ABC 300 から ABC 403 で、問題文中に「誤差」が含まれている問題のリストです。

739 問中 21 問しかないらしいです。まじ? 2.84 % くらいです。

「小数」のリストです。これはあまり関係ないですね。

また、それ以外で、手元のコードで f64 が含まれていたものに基づくリストです。あまり素直じゃないものが多かったです。

上記に当てはまらないが「初心者が浮動小数点数で解こうとしてハマりそうなもの」とかがあれば募集中です。

あとがき

最近は correct rounding モノばかり書いていたので、「えっ 今日は全員誤差出していいのか‼️」「ああ…しっかり出せ」「桁落ちもいいぞ!(??)」のような気持ちになりました。「ただ今より整数訓練を開始する‼️」(???)

浮動小数点数に限らないことだとは思うのですが、土壇場で必要になったときに日頃から訓練していないと誤ってしまうことが多いと思います。 コンテスト本番でわざわざ浮動小数点数による解法を好んで使おうとする理由はあまりないとも思いますが、復習の際には敢えて「浮動小数点数で解けないもんかな?」とやってみることも重要かなとも思います。

初心者層の優先度としてはもっと他にやるべきことがあるとは思いつつ、「誤差とかよくわかんないけどなんか通った」「なんかわかんないけど落ちた」と言い続けるよりはいいんじゃないかな?とも思います。Twitter で検索してみるとそういう人はしばしばいます。

う〜〜〜ん、少し考えてみても、「このくらいの層であれば他の勉強よりも浮動小数点数の勉強を優先するのがよい」と勧めたくなる状況が思いつかないですね。 気分転換としてやるのがよいのではないでしょうか。

初心者が望んでいる浮動小数点数の解説記事ってどんなのですか? 正直あまりわからなくなってきました。もちろん初心者以外が望んでいるものでもいいんですが、あまりわからないです。わからないのでえびちゃんが好き勝手に書いちゃいます。

いま GW 何日目ですか? あまり現実逃避に記事を書いて時間を溶かさない方がいいですよ。correct rounding から逃げてはいけません。

おわり

おわりです。

*1:もちろんライブラリの実装にはよるのですが、容易に実現できるため、まともな実装であることを仮定するのは十分現実的でしょう。

*2:$\floor{x+\delta}$ ではなく $\floor{x\oplus\delta}$ を考える必要があることに注意。

*3:$x=1$ とすればいいのにわざわざ四則演算をしているからおかしくなった ← それはそう。

*4:こういう質問をする人が昔しばしばいたような気もするし、架空の人だったような気もします。老人の記憶はアテになりません。

*5:打ち消しの操作を主語とする文では、たとえば “In this situation, the cancellation is catastrophic.” とか言われたりします。他にも “the terms are cancelled catastrophically.” と書かれていたとしても桁落ちのことを指すでしょう。catastrophic の代わりに severe と言われることもあります。

*6:correct rounding な実装でないとここで $1$ を返すかもしれません。correct rounding でない実装を前提にしても仕方がないですし、correct rounding な実装を前提にしていても桁落ちでこわれますよ〜というのを話したいので、大勢には影響がないと思います。

*7:浮動小数点数を避けるのが賢明? まぁそういう立場もあるでしょうね。

*8:答えを表すのに必要なビット長があれば十分という意味ではなくて、計算途中の精度なども当然加味した上での主張です。

*9:もちろん加算だけの形であっても異符号になりうるなら無意味です。

*10:こういうのって読者への課題として残した方がいいですか?

*11:$\oplus$ の部分をちゃんと考えると $3$ 回分で抑えられる気がしますが、ここでは不要だと思うので大雑把にいきます。

correct rounding への道 (4) error-free transformation

倍精度浮動小数点型であるところの double ですが、現代においてはもはや double が「ふつう」になり、単精度と呼ばれている float は実質的に「半精度」のような感覚になっている気がします。

なにかしらを計算したいときに、計算結果は double だとしても計算途中は double よりも高い精度で行う必要がある場合はしばしばあります*1。 そうした場合に 64-bit や 113-bit の仮数部を持つ long double や __float128 などの型を扱える環境ならそれを使うのも手ですが、そうでない環境もしばしばありますし、将来 __float128 が「ふつう」になったときに「__float256 なしにはなにも打つ手がない...」となってしまいます。 内部実装として多倍長整数を使うような任意精度の型を使う方法もありますが、これも標準で用意されていない言語もしばしばあります*2し、速度面も気になってきます。

ともかく、今回からの数回は「ふつう」の浮動小数点数だけを使って、より高い精度で計算する方法について触れようと思います。

å°Žå…¥

一旦、円周率 $\pi$ について考えてみましょう。 $$ \pi = {\small 3.141592653589793}{\footnotesize 238462643383279}{\scriptsize 502884197169399}{\tiny 375105820974944592307{\dots}}. $$ これを普通に double で表そうとすると以下のようになります。 $$ \begin{aligned} \roundcirc{\pi} &= {\small 3.141592653589793}{\footnotesize 115997963468544}{\scriptsize 185161590576171}{\tiny 875} \\ &= 7074237752028440\times 2^{-51}. \end{aligned} $$ $\hat\pi_1 = \roundcirc{\pi}$ として、下記が成り立ちます。 $$ \pi - \hat\pi_1 = {\small 1.224646799147353}{\footnotesize 177226065932275}{\scriptsize 001058209749445}{\tiny 923078164062862{\dots}}\times 10^{-16} $$ よって、$\hat\pi_2 = \roundcirc{\pi - \hat\pi_1}$ とすると下記のようになります。 $$ \begin{aligned} \hat\pi_2 &= ({\small 1.224646799147353}{\footnotesize 207173764029458}{\scriptsize 396604625692124}{\tiny 677580063796256} \\ &\qquad\quad + {\tiny 12680683843791484832763671875}\times 10^{-60})\times 10^{-16} \\ &= 4967757600021511\times 2^{-105}. \end{aligned} $$ 同様にして $$ \begin{aligned} \hat\pi_3 &= \roundcirc{\pi - (\hat\pi_1 + \hat\pi_2)} \\ &= -({\small 2.994769809718339}{\footnotesize 665887016354211}{\scriptsize 984016705500952}{\tiny 339037967798563} \\ & \qquad \quad + {\tiny 706161978508415962547495152434873233460166375152766704559326171875}\times 10^{-60}) \times 10^{-33} \\ &= 8753721960665020 \times 2^{-161} \end{aligned} $$ とすると、 $$ \pi - (\hat\pi_1 + \hat\pi_2 + \hat\pi_3) \approx 1.112\times 10^{-49} $$ となり、$\hat\pi_1$ と比べて 3 倍程度の桁数で一致していることがわかります(3 倍程度の桁数を使っているのでそれはそう)。

ただし、もちろん $\hat\pi_1 \oplus \hat\pi_2 \oplus \hat\pi_3$ のように計算してしまうと $\hat\pi_1$ と同程度の精度に落ちてしまうため、内部表現としては $(\hat\pi_1, \hat\pi_2, \hat\pi_3)$ のように別個で持っておくものとします。 この $(\hat\pi_1, \hat\pi_2)$ や $(\hat\pi_1, \hat\pi_2, \hat\pi_3)$ のような表現を、それぞれ double-double, triple-double と呼んでいます。

上記の例では、「何らかの手段で別途与えた定数を 3 つの double に分解して表す」ということだけをしましたが、もちろんこれを計算に使う方法も必要になってきます。 すなわち、たとえば double と double-double の和や積を triple-double として得る(内部的には double の演算のみを用いる)方法などについても話していきます。

本題

今回は、double と double の和と積を double-double として表す方法について述べ、それ以外については次回以降にしたいと思います*3。

これを明らかと感じるかはわからないのですが、浮動小数点数 $a$, $b$ に対して $(a\oplus b)-(a+b)$ や $(a\otimes b)-(a\times b)$ は(underflow などが絡む議論は別途するとして、基本的に)同じフォーマットの浮動小数点数として表すことができます。 すなわち、double 同士の和・積の誤差を double で正確に表せるということです。よって、double 同士の和・積を誤差なく double-double として表せるということになります。

さて、「値としては表せる」という事実だけでは実際に計算できることにならないので、手続きについて説明していきます。もちろん、表せるという事実についてもちゃんと証明します。

記号に関して

任意の実数 $x \gt 0$ に対して、$2^k\le x\lt 2^{k+1}$ なる整数 $k$ が一意に存在するので、その $k$ を用いて $\hfloor{x} = 2^k$ と定義します。 また、便宜上 $\hfloor0 = 0$ としておきます。

任意の実数 $x\ge 0$ と整数 $k$ に対して明らかに $\hfloor{x\cdot 2^k} = \hfloor x\cdot 2^k$ が成り立ちます。

Property 1: 任意の実数 $a$, $b$ に対して $\hfloor{|a|}\ge \hfloor{|b|}$ ならば $\hfloor{|a+b|}\le 2\hfloor{|a|}$ が成り立つ。

Proof

Case 1: $\hfloor{|a|} = \hfloor{|b|} = 0$

このとき、$a = b = 0$ であり、$\hfloor{|a+b|} = 2\hfloor{|a|} = 0$ である。

Case 2: $\hfloor{|a|} = \hfloor{|b|} \gt 0$

整数 $k$ と実数 $a', b' \in (-2\lldot -1]\sqcup[1\lldot 2)$ が一意に存在して $$ \begin{aligned} 2^k &= \hfloor{|a|} = \hfloor{|b|}, \\ a &= a'\cdot 2^k, \\ b &= b'\cdot 2^k \end{aligned} $$ が成り立つ。ここで $|a'+b'|\lt 4$ であり、$\hfloor{|a'+b'|}\le 2$ となる。 $$ \begin{aligned} \hfloor{|a+b|} &= \hfloor{|(a'+b')\cdot 2^k|} \\ &= \hfloor{|a'+b'|}\cdot 2^k \\ &\le 2\cdot 2^k \\ &= 2\hfloor{|a|}. \end{aligned} $$

Case 3: $\hfloor{|a|} \gt \hfloor{|b|}$

このとき、$|a| \gt |b|$ が成り立つ。 $$ \begin{aligned} \hfloor{|a+b|} &\le \hfloor{|a| + |b|} \\ &\le \hfloor{|a| + |a|} \\ &= \hfloor{2|a|} \\ &= 2\hfloor{|a|}. \quad\qed \end{aligned} $$

Property 2: 任意の実数 $a, b\ge 0$ に対して、$a\le b$ ならば $\hfloor a\le \hfloor b$ が成り立つ。

Proof

このとき、$a\le b$ より $\hfloor a\lt 2\hfloor b$ である。すなわち、$\hfloor{a}\le \hfloor b$ が成り立つ。$\qed$

正負のゼロについて、0.0 を $0_+$、-0.0 を $0_-$ と表記します。以下では単に $0$ と書いた場合は $0_+$ を意味するものとします。 特に $0_-$ について考えなくていいかなと思っている主な理由は下記の通りです。

  • $\roundcirc0 = 0_+$ であるため
  • 最終的に実装したい関数において、入力が $0_-$ だった場合は別途処理すればよいため
  • double-double, triple-double の計算途中で $0_-$ が出てこないのが期待されるため
  • 計算途中で $0_-$ が出てくる場合でも、$\pm\infty$ ã‚„ $0_{\pm}$ 以外との演算では符号は影響しないため

さて、double として考えている値たちを改めて定式化しておきます。

$-1022\le k\le 1023$ に対して、下記を定義します。それぞれ符号ごとの正規化数の集合です。 $$ \begin{aligned} F_k^- &= \{-m\cdot 2^{k-52} \mid m\in [2^{52}\lldot 2^{53})\cap\Z\}, \\ F_k^+ &= \{+m\cdot 2^{k-52} \mid m\in [2^{52}\lldot 2^{53})\cap\Z\}. \end{aligned} $$ また、非正規化数の集合を下記で定義します*4。 $$ \begin{aligned} F_{-1023}^- &= \{-m\cdot 2^{-1022-52} \mid m\in[1\lldot 2^{52})\cap\Z\}, \\ F_{-1023}^+ &= \{+m\cdot 2^{-1022-52} \mid m\in[1\lldot 2^{52})\cap\Z\}. \end{aligned} $$ また、考えたい数全体からなる集合 $F$ を下記で定義します。 $$ F = \left(\bigsqcup_{k=-1023}^{1023} {(F_k^-\sqcup F_k^+)}\right) \sqcup \{-\infty, 0, +\infty\}. $$ たとえば下記が成り立ちます。 $$ \begin{aligned} 1 &\in F_0^+, \\ -2^{-1074} &\in F_{-1023}^-, \\ 2^{1024} &\notin F, \\ 1+2^{-53} &\notin F, \\ 0.1 &\notin F. \end{aligned} $$ さらに、下記の性質も成り立ちます。

  • $(F\smallsetminus \{-\infty, +\infty\})\subset \Q$
  • 任意の実数 $x$ に対して $x\in F \iff -x \in F$
  • 任意の実数 $x$ に対して $x\in F \implies x\equiv 0\pmod{2^{-1074}}$
  • 任意の $x, y \in (F_{-1023}^-\sqcup F_{-1023}^+)$ に対して $x\pm y\in F$

Lemma 3: 任意の $k\in[-1022\lldot 1023]\cap\Z$ および $m\in[1\lldot 2^{53})\cap\N$ に対して、$$\{-m\cdot 2^{k-52}, +m\cdot 2^{k-52}\}\subset F$$が成り立つ。

Proof

任意の $k\in[-1022\lldot 1023]$ および $m\in[1\lldot 2^{53})\cap\N$ に対して $m\cdot 2^{k-52} \in F$ を示せば十分。下記で $P(k)$ を定義し、$k$ に関する数学的帰納法で示す。 $$ P(k) \iff \Forall{m\in[1\lldot 2^{53})\cap\N}{m\cdot 2^{k-52}\in F}. $$

To-be-proved 1: $P(-1022)$

任意の $m\in[1\lldot 2^{-53})\cap\N$ に対し、$m\cdot 2^{k-52} \in F_{-1023}^+\sqcup F_{-1022}^+$ が成り立つ。

To-be-proved 2: $k\lt 1023 \wedge P(k) \implies P(k+1)$

$m\in[2^{52}\lldot 2^{53})\cap\N$ のとき $m\cdot 2^{(k+1)-52}\in F_{k+1}^+$ が成り立つ。

$m\in[1\lldot 2^{52})\cap\N$ のとき、$2m\in[2\lldot 2^{53})\cap\N$ かつ $m\cdot 2^{(k+1)-52} = (2m)\cdot 2^{k-52}$ が成り立ち、$P(k)$ より従う。$\qed$

tiesToEven による丸めについても定義しましょう。$\circ\colon \R\to F$ を次のように定義します。 ただし、閾値として $\Theta_{\infty} = (2-2^{-53})\cdot 2^{1023}$ および $\Theta_0 = 2^{-53}\cdot 2^{-1022}$ としておきます。 $$ \roundcirc{x} = \begin{cases} +\infty, & \text{if}\: x \ge \Theta_{\infty}; \\ \roundcirc{x\cdot\hfloor{x}^{-1}\cdot 2^{52}}\cdot\hfloor{x}\cdot 2^{-52}, & \text{if}\: x\in[2^{53}\lldot\Theta_{\infty}); \\ \floor{x}, & \text{if}\: x\in[2^{52}\lldot 2^{53}) \wedge (x\bmod 1)\lt \tfrac12; \\ \floor{\frac{2x+1}4}\cdot 2, & \text{if}\: x\in[2^{52}\lldot 2^{53}) \wedge (x\bmod 1) = \tfrac12; \\ \ceil{x}, & \text{if}\: x\in[2^{52}\lldot 2^{53}) \wedge (x\bmod 1)\gt \tfrac12; \\ \roundcirc{x\cdot\hfloor{x}^{-1}\cdot 2^{52}}\cdot\hfloor{x}\cdot 2^{-52}, & \text{if}\: x\in[2^{-1022}\lldot 2^{52}); \\ \roundcirc{x+2^{-1022}}-2^{-1022}, & \text{if}\: x\in(\Theta_0\lldot 2^{-1022}); \\ 0, &\text{if}\: x\in[0\lldot \Theta_0]; \\ \roundcirc{-x}, & \text{if}\: x\lt 0. \end{cases} $$ 特に、$x\in F\iff \roundcirc x=x$ です。また $x\bmod 1=\tfrac12$ のとき $\floor{\frac{2x+1}4}\cdot 2 \in \{\floor x, \ceil x\}$ です。

また、$(x, y)\in \R\times F$ かつ $y\le x$ ならば $y\le \roundcirc x$($\le$ を $\ge$ で置き換えた命題も同様)です。

任意の実数 $x$ に対して、$|\roundcirc x|\lt \infty$ ならば下記が成り立ちます。 $$ |\roundcirc x-x| \le \begin{cases} \hfloor{|x|}\cdot 2^{-53}, & \text{if}\: |x|\ge 2^{-1022}; \\ 2^{-1022-53}, & \text{if}\: |x|\lt 2^{-1022}. \end{cases} $$

さらに、任意の実数 $x$ と整数 $k$ に対して $x\equiv 0\pmod{2^k}$ ならば $\roundcirc x\equiv 0\pmod{2^k}$ が成り立ちます。

補題たち

頻出なので、改めて触れておきます。

Lemma 4 (Sterbenz): $x, y\in F\smallsetminus{\{-\infty, +\infty\}}$ について、$\frac x2\le y\le 2x$ ならば $x\ominus y=x-y$ が成り立つ。

Proof

$x=y$ のときは明らか(特に $x=y=0$ も含む)。一般性を失わず $x\gt y\gt 0$ とする*5。また、$x-y\le \frac x2$ に注意する。

Case 1: $y\in F_{-1023}^+$

$x\le 2y$ より、$x\in F_{-1023}^+\sqcup F_{-1022}^+$ である。

Case 1-1: $x\in F_{-1023}^+$

ある整数 $m_x$, $m_y$ ($0\lt m_y\lt m_x\lt 2^{52}$) に対して $x = m_x\cdot 2^{-1074}$ および $y = m_y\cdot 2^{-1074}$ が成り立つ。 $$ x - y = (m_x-m_y)\cdot 2^{-1074} $$ であり、$m_x-m_y\in[1\lldot 2^{52})\cap\N$ であるから、$x-y\in F_{-1023}^+$ である。

Case 1-2: $x\in F_{-1022}^+$

ある整数 $2^{52}\le m_x\lt 2^{53}$, $1\le m_y\lt 2^{52}$ に対して $x = m_x\cdot 2^{-1074}$ および $y = m_y\cdot 2^{-1074}$ が成り立つ。 $$ x - y = (m_x-m_y)\cdot 2^{-1074} $$ である。ここで、$m_x-m_y\in[1\lldot 2^{53})\cap\N$ であるから、$x-y\in F_{-1023}^+\sqcup F_{-1022}^+$ である。

Case 2: ある整数 $-1022\le k\le 1023$ に対して $y\in F_k^+$

Case 1 同様に $x\in F_k^+\sqcup F_{k+1}^+$ である。

Case 2-1: $x\in F_k^+$

ある整数 $2^{52}\le m_y\lt m_x\lt 2^{53}$ に対して $x = m_x\cdot 2^{k-52}$ および $y = m_y\cdot 2^{k-52}$ が成り立つ。 $$ x - y = (m_x-m_y)\cdot 2^{k-52} $$ であり、かつ $m_x-m_y\in[1\lldot 2^{52})\cap\N$ であるから、Lemma 3 より $x-y\in F$ が従う。

Case 2-2: $x\in F_{k+1}^+$

ある整数 $2^{52}\le m_x\lt 2^{53}$ および $2^{52}\le m_y\lt 2^{53}$ に対して $x = m_x\cdot 2^{k+1-52}$ および $y = m_y\cdot 2^{k-52}$ が成り立つ。 $$ \begin{aligned} x-y &= m_x\cdot 2^{k+1-52} - m_y\cdot 2^{k-52} \\ &= (2m_x-m_y)\cdot 2^{k-52} \end{aligned} $$ と書ける。$x-y\le\tfrac x2$ より、$2m_x-m_y\le m_x\lt 2^{53}$ が従う。Case 2-1 同様にして、$x-y\in F$ が従う。$\qed$

Lemma 5: 整数 $m\in[1\lldot 2^{106})$ および $k\in[-1022\lldot 1023-53]$ に対して $x = m\cdot 2^{k-52}$ と表せるとき、ある $(x_h, x_l)\in F^2$ が存在して $x=x_h+x_l$ が成り立つ。

Proof

$m_h = \floor{m/{2^{53}}}$ および $m_l = (m\bmod 2^{53})$ とすると $m = m_h\cdot 2^{53} + m_l$ が成り立つ。 $$ \begin{aligned} x_h &= (m_h\cdot 2^{53}) \cdot 2^{k-52} \\ &= m_h\cdot 2^{(k+53)-52}, \\ x_l &= m_l\cdot 2^{k-52} \end{aligned} $$ とすると、$(x_h, x_l)\in F^2$ かつ $x = x_h + x_l$ が成り立つ。$\qed$

$x, y\in F\smallsetminus{\{-\infty, +\infty\}}$ に対して、$(x\oplus y)-(x+y)$ すなわち $x\oplus y$ の誤差について考えます。

Lemma 6: $x, y\in F$ が $x\gt 0$ かつ $\hfloor x\ge \hfloor{|y|}$ かつ $x+y\lt \Theta_{\infty}$ を満たすとする。このとき、$(x\oplus y)-x \in F$ かつ $(x\oplus y)-(x+y)\in F$ が成り立つ。

Proof

Case 1: $\hfloor{|y|} \le \hfloor x\cdot 2^{-55}$

このとき $|y|\lt \hfloor x\cdot 2^{-54}$ であり、$x\oplus y=x$ であるから、$(x\oplus y)-x = 0$ かつ $(x\oplus y)-(x+y)=-y$ である。 明らかに $0, -y\in F$ である。

Case 2: $\hfloor{|y|} = \hfloor x\cdot 2^{-54}$

このとき $\hfloor x\cdot 2^{-54}\le |y|\lt \hfloor x\cdot 2^{-53}$ である。また、$x\in F$ より $\hfloor{x}\le 2^{1023}$ であるから、$\hfloor{|y|}\le 2^{1023-54}$ である。また、$x\notin F_{-1023}^+$ である。

Case 2-1: $x = \hfloor x$ かつ $-\hfloor x\cdot 2^{-54}\lt y\lt -\hfloor x\cdot 2^{-53}$

このとき $x\oplus y = x-\hfloor x\cdot 2^{-53}$ であるから、 $$ \begin{aligned} (x\oplus y)-x &= -\hfloor x\cdot 2^{-53} \\ &= -2\hfloor{|y|}, \\ (x\oplus y)-(x+y) &= -y-2\hfloor{|y|} \\ &= |y| - 2\hfloor{|y|} \end{aligned} $$ である。$\hfloor{|y|}\le 2^{969}$ より $2\hfloor{|y|}\in F$ であり、$|y|-2\hfloor{|y|}\in F$ も Sterbenz lemma から従う。

Case 2-2: $x = \hfloor x$ かつ $y = -\hfloor x\cdot 2^{-54}$

$x\oplus y = x$ であるから、Case 1 同様にして従う。

Case 2-3: $x = \hfloor x$ かつ $y\gt 0$

$x\oplus y = x$ であるから、Case 1 同様にして従う。

Case 2-4: $x \gt \hfloor x$

$x\oplus y = x$ であるから、Case 1 同様にして従う。

Case 3: $\hfloor{|y|} = \hfloor x\cdot 2^{-53}$

このとき $\hfloor x\cdot 2^{-53}\le |y|\lt \hfloor x\cdot 2^{-52}$ である。

$x=\hfloor x$ のとき、$y\lt 0$ ならば $x\oplus y\in\{x-\hfloor x\cdot 2^{-52}, x-\hfloor x\cdot 2^{-53}\}$ であり、$y\gt 0$ ならば $x\oplus y = x$ である。

$x\gt \hfloor x$ のとき、$y\lt 0$ ならば $x\oplus y\in\{x-\hfloor x\cdot 2^{-52}, x\}$ であり、$y\gt 0$ ならば $x\oplus y\in\{x, x+\hfloor x\cdot 2^{-52}\}$ である。

Sterbenz lemma に気をつけつつ、総括して、$y\lt 0$ ならば $$ \begin{aligned} (x\oplus y)-(x+y) &\in \{-y-\hfloor x\cdot 2^{-52}, -y-\hfloor x\cdot 2^{-53}, -y\} \\ &= \{-y-2\hfloor{|y|}, -y-\hfloor{|y|}, -y\} \\ &= \{|y|-2\hfloor{|y|}, |y|-\hfloor{|y|}, |y|\} \subset F \end{aligned} $$ であり、$y\gt 0$ ならば $$ \begin{aligned} (x\oplus y)-(x+y) &\in \{-y, -y+\hfloor x\cdot 2^{-52}\} \\ &= \{-y, -y+2\hfloor{|y|}\} \\ &= \{-y, -y+2\hfloor{y}\} \subset F \end{aligned} $$ である。

Case 4: $\hfloor x\cdot 2^{-52} \le \hfloor{|y|} \le \hfloor x$

$|y|\gt 0$ に注意する。

Case 4-1: $y\lt 0$

Case 4-1-1: $y\in F_{-1023}^-$ かつ $x\in F_{-1023}^+$

ある整数 $m_x, m_y\in[1\lldot 2^{52})$ に対して $x = m_x\cdot 2^{-1022-52}$ かつ $y = -m_y\cdot 2^{-1022-52}$ が成り立つ。 $$ x + y = (m_x-m_y)\cdot 2^{-1022-52} $$ であり、$|m_x-m_y| \in [0\lldot 2^{53})\cap\N$ より $x+y = x\oplus y$ が従う。 よって、$(x\oplus y)-x = y$ かつ $(x\oplus y)-(x+y) = 0$ が成り立つ。

Case 4-1-2: $y\in F_{-1023}^-$ かつ、ある整数 $k\ge -1022$ に対して $x\in F_k^+$

$$ \begin{aligned} \hfloor x &\le \hfloor{|y|}\cdot 2^{52} \\ &\lt 2^{52}\cdot 2^{-1022-52}\cdot 2^{52} \\ &= 2^{-1022+52} \end{aligned} $$ であるから、$x\lt 2^{-1022+52}$ となる。また、$x+y\equiv 0\pmod{2^{-1022-52}}$ が成り立つ。

$x+y\lt 2^{-1022}$ のとき $x+y\in F_{-1023}^+$ より $x\oplus y=x+y$ が従うため、以降は $x+y\ge 2^{-1022}$ とする。 このとき、 $$ \begin{aligned} |(x\oplus y)-(x+y)| &\le (x+y)\cdot 2^{-53} \\ &\lt x\cdot 2^{-53} \\ &\le 2^{-1022+52}\cdot 2^{-53} \\ &= 2^{-1022-1} \end{aligned} $$ より $(x\oplus y)-(x+y)\in F_{-1023}^+$ が成り立つ。 また、$y\in F_{-1023}^-$ より $(x\oplus y)-x = ( (x\oplus y)-(x+y) ) + y \in F$ が従う。

Case 4-1-3: ある整数 $k\ge -1022$ に対して $y\in F_k^-$

このとき、ある整数 $k'\ge k$ に対して $x\in F_{k'}^+$ である。Case 4-1-2 同様 $x+y\ge 2^{-1022}$ の場合について考える。

また、$\frac x2\le |y|\le 2x$ のときは Sterbenz lemma より $x\oplus y=x+y$ が従うから、$-y \lt \frac x2$ とする。 このとき $\tfrac x2\lt x+y$ である。よって $\tfrac x2\le x\oplus y$ であるから、Sterbenz lemma より $(x\oplus y)-x\in F$ である。

さて、 $$ \begin{aligned} \hfloor x &\le \hfloor{|y|}\cdot 2^{52} \\ &= 2^{52}\cdot 2^{k-52}\cdot 2^{52} \\ &= 2^{k+52} \end{aligned} $$ より $x\lt 2^{k+53}$ である。 $x+y\equiv 0 \pmod{2^{k-52}}$ から、ある整数 $0\le m\lt 2^{105}$ が存在して $x+y = m\cdot 2^{k-52}$ が成り立つ。

$x\oplus y\equiv 0\pmod{2^{k-52}}$ かつ $|(x\oplus y)-(x+y)| \le (x+y)\cdot 2^{-53}$ であるから、ある整数 $0\le m'\lt 2^{52}$ が存在して $(x\oplus y)-(x+y) = m'\cdot 2^{k-52}$ が成り立つため、$(x\oplus y)-(x+y)\in F$ が従う。

Case 4-2: $y\gt 0$

Case 4-2-1: $y\in F_{-1023}^+$ かつ $x\in F_{-1023}^+$

Case 4-1-1 同様にして従う。

Case 4-2-2: ある整数 $k\ge -1022$ に対して $x\in F_k^+$

このとき、$x+y\ge 2^{-1022}$ が成り立つ。

$|(x\oplus y)-(x+y)|\le (x+y)\cdot 2^{-53}$ より Sterbenz lemma が従い、$(x\oplus y)-(x+y)\in F$ となる。

$x+y\le 2x$ のとき $x\oplus y\le 2x$ であるから、Sterbenz lemma より $(x\oplus y)-x\in F$ が従う。 以降はそうでない場合を考える。すなわち $y\gt x$ とする。$\hfloor x=\hfloor y$ かつ $\hfloor{x+y} = 2\hfloor x$ が成り立つ。

よって、ある $m_x, m_y\in [2^{52}\lldot 2^{53})\cap\N$ に対して $x = m_x\cdot 2^{k-52}$ かつ $y = m_y\cdot 2^{k-52}$ が成り立つ。 $$ x\oplus y \in \{(m_x+m_y-1)\cdot 2^{k-52}, (m_x+m_y)\cdot 2^{k-52}, (m_x+m_y+1)\cdot 2^{k-52}\} $$ であり、 $$ (x\oplus y)-x \in \{(m_y-1)\cdot 2^{k-52}, m_y\cdot 2^{k-52}, (m_y+1)\cdot 2^{k-52}\} \subset F_k^+ $$ が成り立つ。$\qed$

Error-free transformation

さて、実際にやっていきます。$a+b$ や $a\times b$ などに対して、二つの浮動小数点数 $h$, $l$ であって(誤差なく)$h+l$ と表せるものを求めます。

今後の応用を見据えると $a\oplus b$ や $a\otimes b$ が非正規化数になる場合は除外してもいい気がしますが、非正規化数ちゃんを仲間外れにして忌避感を強めるのも嫌なので、できる限りケアしていきます。

和

最も基本的な操作ですが、Lemma 6 相当の前提がないと証明は面倒です。

  • 入力: $(a, b)\in F\times F$
  • 出力: $(s_h, s_l)\in F^2$
  • 事前条件:
    • $|a+b| \lt \Theta_{\infty}$, and
    • $\hfloor{|a|}\ge \hfloor{|b|}$
  • 事後条件:
    • $s_h = a\oplus b$, and
    • $s_h + s_l = a + b$
  • 手続き:
    • $s_h \gets a\oplus b$ で初期化する。
    • $z \gets s_h\ominus a$ で初期化する。
    • $s_l \gets b\ominus z$ で初期化する。
    • $(s_h, s_l)$ を出力する。

Claim 7: 上記の手続きの実行後、事後条件が成り立つ。

Proof

$s_h = a\oplus b$ は明らかなので、$s_h+s_l = a+b$ を示す。

Case 1: $a\lt 0$

$a\gt 0$ で成り立つならば $a\lt 0$ でも成り立つことを示す。 $$ \begin{aligned} s_h^{\pm} &= (\pm a)\oplus (\pm b), \\ z^{\pm} &= s_h^{\pm}\ominus (\pm a), \\ s_l^{\pm} &= (\pm b)\ominus z^{\pm} \end{aligned} $$ とする(複号同順)。このとき、 $$ \begin{aligned} s_h^- &= (-a)\oplus(-b) \\ &= -(a\oplus b) \\ &= -s_h^+, \\ z^- &= s_h^- \ominus (-a) \\ &= -(s_h^+ \ominus a) \\ &= -z^+, \\ s_l^- &= (-b)\ominus z^- \\ &= -(b\ominus z^+) \\ &= -s_l^+ \end{aligned} $$ であるから、$s_h^- + s_l^- = -(s_h^+ + s_l^+)$ が成り立つ。 よって、$s_h^+ + s_l^+ = a+b$ ならば $s_h^- + s_l^- = -(a + b) = (-a) + (-b)$ が成り立つ。

Case 2: $a = 0$

$$ \begin{aligned} s_h &= 0\oplus b = b, \\ z &= b\ominus 0 = b, \\ s_l &= b\ominus b = 0 \end{aligned} $$ より、$s_h + s_l = b = a + b$ が成り立つ。

Case 3: $a\gt 0$

Lemma 6 より、$z = s_h\ominus a = s_h - a$ が従う。また、 $$ \begin{aligned} s_l &= b\ominus z \\ &= b - (s_h - a) \\ &= (a+b)-s_h \\ &= (a+b)-(a\oplus b) \end{aligned} $$ である。$\qed$

$\hfloor{|a|}\ge \hfloor{|b|}$ が事前に保証できない場合は $|a|\ge |b|$ で分岐すればよいです。 わざわざ $\hfloor{\bullet}$ の部分を実装したりする必要はありません。

$a$, $b$ の大小によらない亜種なども考案されていますが、ここでは割愛します。

丸め

次以降で使うサブルーチンです。$\textsc{Round}(a, k)$ として使います。 「$a$ の仮数部を $53-k$ bits に丸めた値」とその際の誤差に分離します。

  • 入力: $(a, k)\in F \times \N$
  • 出力: $(a_h, a_l)\in F^2$
  • 事前条件:
    • $|a| \le 2^{1023-k}$, and
    • $k\in[1\lldot 52]$
  • 事後条件:
    • $a_h + a_l = a$,
    • $a_h \equiv 0 \pmod{\hfloor{|a|}\cdot 2^{-(52-k)}}$, and
    • $|a_l| \le \hfloor{|a|}\cdot 2^{-(53-k)}$.
  • 手続き:
    • $c \gets 2^k+1$ で初期化する。
    • $a_c \gets a\otimes c$ で初期化する。
    • $a_h \gets (a\ominus a_c)\oplus a_c$ で初期化する。
    • $a_l \gets a \ominus a_h$ で初期化する。
    • $(a_h, a_l)$ を出力する。

Claim 8: 上記の手続きの実行後、事後条件が成り立つ。

Proof

$a = 0$ のときは明らか。$a\gt 0$ で成り立てば $a\lt 0$ で成り立つことも明らかなので、以降 $a\gt 0$ とする。

Case 1: $a\ge 2^{-1022}$

$a\cdot (2^k+1) \ge 2^{-1022}$ に注意し、$\hfloor{(2^k+1)\cdot a} = 2^{52}$ とする。 そうでない場合は $a$ を $\hfloor{(2^k+1)\cdot a}^{-1}\cdot 2^{52}$ 倍することで帰着できる。 特に、$|a\cdot(2^k+1)| \le 2^{1023-1}\cdot (2^1+1)\lt \Theta_{\infty}$ に注意する。

まず $2^{51}+\tfrac12 \le 2^k\cdot a$ を示す。$2^{51}\le \tfrac12(2^k+1)\cdot a$ であるから、$\tfrac12(2^k+1)\cdot a\le 2^k\cdot a-\tfrac12$ を示せば十分。 $$ \begin{aligned} &\phantom{{}\iff{}} \tfrac12(2^k+1)\cdot a\le 2^k\cdot a-\tfrac12 \\ % &\iff \tfrac12\cdot 2^k\cdot a + \tfrac12\cdot a \le 2^k\cdot a-\tfrac12 \\ &\iff \tfrac12\cdot a \le \tfrac12\cdot 2^k\cdot a-\tfrac12 \\ &\iff a\le 2^k\cdot a-1 \\ &\iff 1\le (2^k-1)\cdot a \\ &\iff 1\le \tfrac{2^k-1}{2^k+1}\cdot (2^k+1)\cdot a \end{aligned} $$ $k\ge 1$ のとき $\tfrac{2^k-1}{2^k+1}\ge \tfrac13$ であり、$(2^k+1)\cdot a\ge 2^{52}$ より従う。

さて、 $$ \begin{aligned} (2^k+1)\cdot a &= 2^k\cdot a + a \\ &= \floor{2^k\cdot a + a} + ( (2^k\cdot a + a)\bmod 1) \end{aligned} $$ である。

$\hfloor{2^k\cdot a} \ge 2^{51}$ かつ $2^k\cdot a\in F$ であるから、$( (2^k\cdot a)\bmod 1)\in\{0, \tfrac12\}$ である。

また、$\hfloor a\ge 2^{51-k}$ であるから、$a\equiv 0\pmod{2^{-k-1}}$ が成り立つ。特に、$a\equiv 0\pmod{2^{-53}}$ である。

Case 1-1: $( (2^k\cdot a)\bmod 1) = 0$

$$ \begin{aligned} (2^k+1)\cdot a &= 2^k\cdot a + \floor{a} + (a\bmod 1). \end{aligned} $$

$r = (a\bmod 1)$ とすると、下記が成り立つ。

  • $r\le \tfrac12 \wedge (2^k+1)\otimes a = 2^k\cdot a + \floor{a}$, or
  • $r\ge \tfrac12 \wedge (2^k+1)\otimes a = 2^k\cdot a + \floor{a} + 1$.

$r\le \tfrac12$ のとき、 $$ \begin{aligned} a - ( (2^k+1)\otimes a) &= (\floor{a} + r) - (2^k\cdot a + \floor{a}) \\ &= -(2^k\cdot a - r) \end{aligned} $$ である。また、$r\ge \tfrac12$ のとき、 $$ \begin{aligned} a - ( (2^k+1)\otimes a) &= (\floor{a} + r) - (2^k\cdot a + \floor{a} + 1) \\ &= -(2^k\cdot a - (r-1) ) \end{aligned} $$ である。

Case 1-1-1: $2^k\cdot a\gt 2^{52}$

remark: $( (2^k\cdot a)\bmod 1) = 0$ より $2^k\cdot a-1\ge 2^{52}$ が成り立つ。

$r\le \tfrac12$ のとき、ある $r'\in\{0, 1\}$ に対して $a\ominus ( (2^k+1)\otimes a) = -(2^k\cdot a-r')$ かつ $|r-r'|\le \tfrac12$ が成り立つ。 よって、 $$ \begin{aligned} &\phantom{{}={}} (a\ominus ( (2^k+1)\otimes a) ) + ( (2^k+1)\otimes a) \\ &= -(2^k\cdot a-r') + (2^k\cdot a + \floor a) \\ &= \floor a + r' \end{aligned} $$ となる。$\hfloor a=2^{52-k}$ より $a\le 2^{52}-\frac12$ であるから、$|\floor a + r'|\le 2^{52}$ であり、$\floor a+r'\in F$ である。 よって $$ (a\ominus ( (2^k+1)\otimes a) ) \oplus ( (2^k+1)\otimes a) = \floor a+r' $$ となる。$\floor a+r'\equiv 0\pmod 1$ かつ $1 = \hfloor{a}\cdot 2^{-(52-k)}$ である。

よって $a_h \equiv 0 \pmod{\hfloor a\cdot 2^{-(52-k)}}$ である。また、$a_l = a-(\floor a+r') = r-r'$ であるから、$|a_l|\le \hfloor a\cdot2^{-(53-k)}$ である。

また、$r\ge \tfrac12$ のとき、ある $r'\in\{0, 1\}$ に対して $a\ominus ( (2^k+1)\otimes a) = -(2^k\cdot a-r')$ かつ $|(r-1)-r'|\le \tfrac12$ が成り立ち、$r\le \tfrac12$ の場合と同様にして従う。特に、 $$ \begin{aligned} a_l &= a - a_h \\ &= (\floor a+r) - (\floor a+r'+1) \\ &= (r-1)-r' \end{aligned} $$ である。

Case 1-1-2: $2^k\cdot a\le 2^{52}$

ある $r'\in\{0, \tfrac12, 1\}$ に対して $a\ominus ( (2^k+1)\otimes a) = -(2^k\cdot a-r')$ が成り立つ。 $r\le \tfrac12$ のとき $|r-r'|\le \tfrac14$、$r\ge \tfrac12$ のとき $|(r-1)-r'|\le \tfrac14$ が成り立つ。

よって、Case 1-1-1 同様にして $$ (a\ominus ( (2^k+1)\otimes a) ) \oplus ( (2^k+1)\otimes a) = \floor a+r' $$ となる。$\floor a+r'\equiv 0\pmod{\tfrac12}$ かつ $\tfrac12 = \hfloor{a}\cdot 2^{-(52-k)}$ である。 すなわち、$a_h\equiv 0\pmod{\hfloor a\cdot 2^{-(52-k)}}$ かつ $|a_l|\le \hfloor a\cdot 2^{-(53-k)}$ が成り立つ。

Case 1-2: $( (2^k\cdot a)\bmod 1) = \tfrac12$

このとき、$\hfloor{2^k\cdot a} = 2^{51}$ である。 $$ \begin{aligned} (2^k+1)\cdot a &= 2^k\cdot a - \tfrac12 + \floor{\tfrac12+a} + ( (\tfrac12+a)\bmod 1). \end{aligned} $$ $r = ( (\tfrac12+a)\bmod 1)$ とすると、下記が成り立つ。

  • $r\le \tfrac12 \wedge (2^k+1)\otimes a = 2^k\cdot a - \tfrac12 + \floor{\tfrac12+a}$, or
  • $r\ge \tfrac12 \wedge (2^k+1)\otimes a = 2^k\cdot a + \tfrac12 + \floor{\tfrac12+a}$.

note: $\tfrac12+a = \floor{\tfrac12+a} + ( (\tfrac12+a)\bmod 1)$ である。

$r\le \tfrac12$ のとき、 $$ \begin{aligned} &\phantom{{}={}} a - ( (2^k+1)\otimes a) \\ &= (-\tfrac12 + \floor{\tfrac12+a} + r) - (2^k\cdot a - \tfrac12 + \floor{\tfrac12+a}) \\ &= -(2^k\cdot a-r) \end{aligned} $$ であり、 $r\ge \tfrac12$ のとき、 $$ \begin{aligned} &\phantom{{}={}} a - ( (2^k+1)\otimes a) \\ &= (-\tfrac12 + \floor{\tfrac12+a} + r) - (2^k\cdot a + \tfrac12 + \floor{\tfrac12+a}) \\ &= -(2^k\cdot a-(r-1) ) \end{aligned} $$ である。$\hfloor a=2^{51-k}$ より、$a\le 2^{51}-\tfrac14$ であることに注意して Case 1-1-2 と同様にして従う。

Case 2: $a\lt 2^{-1022}$

Case 2-1: $a\times (2^k+1)\le 2^{-1022}$

このとき、$a_c = a\times c$ かつ $(a_h, a_l) = (a, 0)$ が成り立つ。 $|a_l| \le \hfloor a\cdot 2^{-(53-k)}$ は明らかであるから、$a \equiv 0 \pmod{\hfloor a\cdot 2^{k-53}}$ を示す。

$\hfloor a=2^{i-1022-52}$ とすると、ある整数 $2^i\le m_a\lt 2^{i+1}$ が存在して $a = m_a\cdot 2^{-1022-52}$ が成り立ち、$m_a\cdot 2^{-1022-52} \equiv 0 \pmod{2^{-1022-52}}$ となる。 $a\times 2^k \lt 2^{-1022}$ より $(i-1022-52)+k\lt -1022$ が成り立つため、 $$ \begin{aligned} &\phantom{{}\implies{}} m_a\cdot 2^{-1022-52} \equiv 0 \pmod{2^{-1022-52}} \\ &\implies m_a\cdot 2^{-1022-52} \equiv 0 \pmod{2^{i-1022-52+k-1-52}} \\ &\iff m_a\cdot 2^{-1022-52} \equiv 0 \pmod{\hfloor a\cdot 2^{k-53}} \end{aligned} $$ が従う。

Case 2-2: $a\times (2^k+1)\gt 2^{-1022}$

$\hfloor{(2^k+1)\cdot a}^{-1}\cdot 2^{52}$ 倍することで、Case 1 に帰着できる。特に、$r'\in\{0, 1\}$ に対して $$ \hfloor{(2^k+1)\cdot a}\cdot 2^{-52}\cdot(\floor{\hfloor{(2^k+1)\cdot a}^{-1}\cdot 2^{52}\cdot a}+r')\in F $$ などが成り立つことに注意せよ。$\qed$

積

さて、こちらも基本的な操作です。FMA が使える前提であれば次のようにできます。

  • 入力: $(a, b)\in F\times F$
  • 出力: $(p_h, p_l)\in F^2$
  • 事前条件:
    • $|a\times b|\lt \Theta_{\infty}$, and
    • $a\times b \equiv 0 \pmod{2^{-1074}}$
  • 事後条件:
    • $p_h = a\otimes b$, and
    • $p_h + p_l = a \times b$
  • 手続き:
    • $p_h \gets a\otimes b$ で初期化する。
    • $p_l \gets \roundcirc{a\times b + (-p_h)}$ で初期化する。
    • $(p_h, p_l)$ を出力する。

Claim 9: 上記の手続きの実行後、事後条件が成り立つ。

Proof

$p_h = a\otimes b$ は明らかであり、$\roundcirc{a\times b + (-p_h)} = a\times b - p_h$ を証明すれば十分。 $ab = 0$ の場合は明らか。$ab\gt 0$ の場合で成り立てば $ab\lt 0$ の場合で成り立つのも明らかなので、$a, b\gt 0$ とする。

Case 1: $ab\ge 2^{-1022}$

$\hfloor a=\hfloor b=2^{52}$ とする。そうでない場合は、それぞれ $\hfloor a^{-1}\cdot 2^{52}$, $\hfloor b^{-1}\cdot 2^{52}$ 倍することで帰着できる。

実数 $|\varepsilon|\le 2^{-53}$ が存在して $a\otimes b = ab+\hfloor{ab}\cdot\varepsilon$ が成り立つ。 $2^{104}\le ab\lt 2^{106}$ であるから、$\hfloor{ab}\le 2^{105}$ である。 $$ \begin{aligned} |(a\otimes b) - (a\times b)| &\le \hfloor{ab}\cdot\varepsilon \\ &\le 2^{105}\cdot 2^{-53} \\ &= 2^{52} \end{aligned} $$ であり、Property 3 から $(a\otimes b)-(a\times b)\in F$ が従う。

Case 2: $ab\lt 2^{-1022}$

このとき、$a\otimes b = a\times b$ が成り立ち、$(p_h, p_l) = (ab, 0)$ となる。$\qed$

事前条件の $a\times b\equiv 0\pmod{2^{-1074}}$ がない場合の反例は、たとえば $(a, b) = (1.5, 2^{-1074})$ です。 $$ \begin{aligned} a\times b &= 1.5\cdot 2^{-1074}, \\ a\otimes b &= 2\cdot 2^{-1074}, \\ (a\otimes b)-(a\times b) &= 0.5\cdot 2^{-1074}\notin F \end{aligned} $$ です。事前条件として $|a\times b|\gt \Theta_0$ を課したとしてもうまくいかないですね。

特に、$|a\times b|\ge 2^{-1022}$ でもだめですね。$(a, b) = ( (2-2^{-52})\cdot 2^{-971}, 2-2^{-52})$ などを考えると、 $$ \begin{aligned} a\times b &= (2-2^{-51}+2^{-105})\cdot 2^{-970}, \\ a\otimes b &= (2-2^{-51})\cdot 2^{-970} \end{aligned} $$ であり、$a\times b \ge 2^{-1022}$ ですが $(a\otimes b) - (a\times b) = 2^{-1075}\notin F$ です。

FMA が使えない場合でも可能です。サブルーチン Round を用いて次のようにできます。

  • 入力: $(a, b)\in F\times F$
  • 出力: $(p_h, p_l)\in F^2$
  • 事前条件:
    • $|a\times b| \lt \Theta_{\infty}$,
    • $a\times b\equiv 0\pmod{2^{-1074}}$, and
    • $\max{\{|a|, |b|}\}\le 2^{1023-27}$
  • 事後条件:
    • $p_h = a\otimes b$, and
    • $p_h + p_l = a \times b$
  • 手続き:
    • $(u_h, u_l) \gets \textsc{Round}(a, 27)$ で初期化する。
    • $(v_h, v_l) \gets \textsc{Round}(b, 27)$ で初期化する。
    • $p_h \gets a\otimes b$ で初期化する。
    • $p_l \gets ( ( ( (u_h\otimes v_h) \ominus p_h)\oplus (u_h\otimes v_l) ) \oplus (u_l\otimes v_h) ) \oplus (u_l \otimes v_l)$ で初期化する。
    • $(p_h, p_l)$ を出力する。

Claim 10: 上記の手続きの実行後、事後条件が成り立つ。

Proof

Claim 9 同様 $a, b\gt 0$ として、$p_h+p_l = a\times b$ を示す。

Case 1: $ab\ge 2^{-1022}$

Claim 9 同様、$\hfloor a=\hfloor b=2^{52}$ とする。Round の事前条件が成り立っていることに注意する。

Round の事後条件より $u_h \equiv v_h \equiv 0 \pmod{2^{27}}$ かつ $|u_l|, |v_l|\le 2^{26}$ が成り立つ。 $$ \begin{aligned} a\times b &= (u_h+u_l) \times (v_h+v_l) \\ &= \underbrace{u_h\times v_h}_{[2^{104}\lldot 2^{106})} + \underbrace{u_h\times v_l}_{[-2^{79}\lldot 2^{79}]} + \underbrace{u_l\times v_h}_{[-2^{79}\lldot 2^{79}]} + \underbrace{u_l\times v_l}_{[-2^{52}\lldot 2^{52}]} \end{aligned} $$ と書ける。ある実数 $|\varepsilon|\le 2^{-53}$ が存在して $|(a\otimes b)-(a\times b)| \le \hfloor{ab}\cdot \varepsilon$ が成り立つから、 $$ \begin{aligned} a\otimes b &= \underbrace{u_h\times v_h\vphantom{\hfloor x}}_{[2^{104}\lldot 2^{106})} + \underbrace{u_h\times v_l\vphantom{\hfloor x}}_{[-2^{79}\lldot 2^{79}]} + \underbrace{u_l\times v_h\vphantom{\hfloor x}}_{[-2^{79}\lldot 2^{79}]} + \underbrace{u_l\times v_l\vphantom{\hfloor x}}_{[-2^{52}\lldot 2^{52}]} + \underbrace{\hfloor{ab}\cdot \varepsilon}_{[-2^{52}\lldot 2^{52}]} \end{aligned} $$ が成り立つ。

$m_u = u_h/2^{27}$ および $m_v = v_h/2^{27}$ とすると $u_h\times v_h = (m_u\times m_v)\times 2^{54}$ となる。 $m_u, m_v\in[2^{25}\lldot 2^{26})\cap\N$ であるから、$u_h \otimes v_h = u_h\times v_h$ が成り立つ。

$u_h\times v_l = (m_u \times v_l)\times 2^{27}$ であり、$|m_u\times v_l|\le 2^{51}$ であるから、$u_h\otimes v_l = u_h\times v_l$ が成り立つ。 同様にして、$u_l\otimes v_h = u_l\times v_h$ である。 また、$|u_l\times v_l| \le 2^{52}$ であるから、$u_l\otimes v_l = u_l\times v_l$ である。

よって、Sterbenz lemma より $$ \begin{aligned} &\phantom{{}={}} (u_h\otimes v_h) \ominus p_h \\ &= (u_hv_h) - (u_hv_h + u_hv_l + u_lv_h + u_lv_l + \hfloor{ab}\cdot\varepsilon) \\ &= -(u_hv_l + u_lv_h + u_lv_l + \hfloor{ab}\cdot\varepsilon) \end{aligned} $$ が成り立つ。

ここで、$a\otimes b = p_h \equiv 0 \pmod{2^{52}}$ かつ $u_hv_h\equiv 0\pmod{2^{54}}$ であるから、$u_hv_h-p_h\equiv 0\pmod{2^{52}}$ が成り立つ。 また、$u_hv_l\equiv 0\pmod{2^{27}}$ であるから、 $$ \begin{aligned} (u_hv_h-p_h)+u_hv_l &= -(u_lv_h + u_lv_l + \hfloor{ab}\cdot\varepsilon) \\ &\equiv 0 \pmod{2^{27}} \end{aligned} $$ かつ $$ \begin{aligned} |(u_hv_h-p_h)+u_hv_l| &\le |u_lv_h| + |u_lv_l| + \hfloor{ab}\cdot|\varepsilon| \\ &\le 2^{79} + 2^{52} + 2^{52} \\ &\lt 2^{80} \end{aligned} $$ であり、ある整数 $0\le m\lt 2^{53}$ に対して $|(u_hv_h-p_h)+u_hv_l| = m\cdot 2^{27}$ と書ける。 よって、 $$ \begin{aligned} ( (u_h\otimes v_h)\ominus p_h)\oplus (u_h\otimes v_l) &= u_hv_h - p_h + u_hv_l \\ &= -(u_lv_h + u_lv_l + \hfloor{ab}\cdot \varepsilon) \end{aligned} $$ が従う。

note: $|u_hv_l|$ が上界に近いとは限らないので、Sterbenz lemma は使えないことに注意せよ。

同様にして、$-(u_lv_h + u_lv_l + \hfloor{ab}\cdot\varepsilon)\equiv 0\pmod{2^{27}}$ かつ $u_lv_h\equiv 0\pmod{2^{27}}$ であるから、 $$ \begin{aligned} ( (u_hv_h-p_h)+u_hv_l)+u_lv_h &= -(u_lv_l + \hfloor{ab}\cdot\varepsilon \\ &\equiv 0 \pmod{2^{27}} \end{aligned} $$ かつ $$ \begin{aligned} |( (u_hv_h-p_h)+u_hv_l)+u_lv_h| &= |u_lv_l| + \hfloor{ab}\cdot|\varepsilon| \\ &\le 2^{52} + 2^{52} \\ &= 2^{53} \end{aligned} $$ であり、$u_lv_l+\hfloor{ab}\cdot\varepsilon\in F$ が従う。 すなわち $$ \begin{aligned} &\phantom{{}={}} ( ( (u_h\otimes v_h) \ominus p_h)\oplus (u_h\otimes v_l) )\oplus (u_l\otimes v_h) \\ &= -(u_lv_h + u_lv_l + \hfloor{ab}\cdot\varepsilon) \oplus u_lv_h \\ &= -(u_lv_l + \hfloor{ab}\cdot\varepsilon) \end{aligned} $$ が従う。

最後に、$\hfloor{ab}\cdot\varepsilon\le 2^{52}$ かつ $\hfloor{ab}\cdot\varepsilon\equiv 1$ より $\hfloor{ab}\cdot\varepsilon\in F$ であるから、 $$ \begin{aligned} &\phantom{{}={}} ( ( ( (u_h\otimes v_h) \ominus p_h)\oplus (u_h\otimes v_l) )\oplus (u_l\otimes v_h) )\oplus (u_l\otimes v_l) \\ &= -(u_lv_l + \hfloor{ab}\cdot\varepsilon) \oplus u_lv_l \\ &= -\hfloor{ab}\cdot\varepsilon \end{aligned} $$ となる。

したがって、$p_h = a\times b + \hfloor{ab}\cdot \varepsilon$ かつ $p_l = -\hfloor{ab}\cdot\varepsilon$ であるから、$p_h+p_l = a\times b$ が成り立つ。

Case 2: $ab\lt 2^{-1022}$

$a\otimes b = a\times b$ に注意する。

$r\ge 0$ に対して $(r_h, r_l) = \textsc{Round}(r, 27)$ とすると $r_h\equiv 0\pmod{\hfloor r\cdot 2^{-25}}$ かつ $|r_l| \le \hfloor r\cdot 2^{-26}$ が成り立つ。 よって、 $$ \begin{aligned} |r_l| &\le \hfloor r\cdot 2^{-26} \\ &\le 2^{-26}\cdot r, \\ r_h &= r - r_l \\ &\le r + r\cdot 2^{-26} \\ &= (1+2^{-26})\cdot r \end{aligned} $$ が成り立つ。よって、 $$ \begin{aligned} u_hv_h &= ab - (u_hv_l + u_lv_h + u_lv_l) \\ &\le ab + |u_hv_l| + |u_lv_h| + |u_lv_l| \\ &\le ab + (1+2^{-26})\cdot a\cdot 2^{-26}\cdot b + 2^{-26}\cdot a\cdot (1+2^{-26})\cdot b + 2^{-52}\cdot ab \\ &= (1 + 2\cdot (1+2^{-26})\cdot 2^{-26} + 2^{-52})\cdot ab \\ &\lt 2ab \\ &\lt 2^{-1021} \end{aligned} $$ であり、$u_hv_h\in F_{-1023}^+\sqcup F_{-1022}^+$ が従う。

同様にして、

  • $u_hv_l$,
  • $u_lv_h$,
  • $u_lv_l$,
  • $u_hv_l + u_lv_h + u_lv_l$, and
  • $u_lv_h + u_lv_l$

はすべて $F$ の元であることが示せるから、$(p_h, p_l) = (ab, 0)$ が従う。$\qed$

$a\le 2^{1023-27}$ が事前に保証できない場合は、$a\gt 2^{1023-27}$ かどうか判定して true なら $a\otimes 2^{-53}$ などを渡し、結果を $2^{53}$ 倍すればよいです($b$ についても同様)。

参考文献

あとがき

今回は、double-double や triple-double を実装する際の基本操作のみを導入しました。

非正規化数についても触れましたが、「よくわからないので避けたいもの」という印象は拭えても「場合分けが面倒になるもの」という印象が強まってしまっているかもしれません? わからないので避けたいというのよりはマシな気はしますね。

次回いきなり気が変わって double-double ではないなにかの話を始めるかもしれません? そのときの気分でテーマが決まるので仕方ないですね。

毎度のことですが、Sterbenz lemma が大活躍すぎます。「$\roundcirc x=x$ を示したくなったらまず Sterbenz lemma を使えないか疑え」まである気がします。 もちろん、mod の性質など、それ以外のものを使う局面もしばしばありますが、Sterbenz lemma がお手軽どころの頻出パターンという感じですね。

なんか疲れてしまいました。証明の方針が全然思いつかなかったり、場合分けをがんばる方法で書き終えたと思ったらまとめられることに気づいたり、なにも考えられなくなったり、シンプルに嘘を書いていたり、ひ〜という感じです。 お気持ちパートも流れるように書けなくなってきました。不調?

さっさと元気になって続編を勉強したいですね。correct rounding に興味のあるフォロヮがどの程度いるかは知らないですが、えびちゃんは知りたがっているので。

おわり

おわりです。

*1:もちろん、考えなしにそういうことをすると二重丸めなどのうれしくないことが発生しえますが、それはまた今後の回で扱う予定です。

*2:別に標準で用意されていることにこだわる必要もないにはないですね。

*3:当初は今回で終わらせる予定だったのですが、思っていたよりボリュームが大変なことになって収集がつかなくなったためです。

*4:$k=-1023$ のような見た目をしているの微妙かな? 後の回で変えるかもしれないです。

*5:$x\gt 0\gt y$ のときは $\frac x2\le y\le 2x$ は偽である。