高精度計算や精度保証付き数値計算でよく使われる、twosumとtwoproductアルゴリズム。最近はError Free Transformation (EFT) (無誤差変換) という呼び方が定着しつつありますが、誤差が発生するケースも多いことをご存知でしょうか。
def twosum(a, b) :
x = a + b
tmp = x - a
y = (a - (x - tmp)) + (b - tmp)
return x, y
def split(a) :
tmp = a * (2**27 + 1)
x = tmp - (tmp - a)
y = a - x
return x, y
def twoproduct(a, b) :
x = a * b
a1, a2 = split(a)
b1, b2 = split(b)
y = a2 * b2 - (((x - a1 * b1) - a2 * b1) - a1 * b2)
return x, y
python風に書くとこんな感じ。
x,y = twosum(a, b) は、xがa+bを浮動小数点数で普通に計算した値、yはその誤差分になり、x+y=a+bが厳密に成立するとされています。
x,y = twoproduct(a, b) は、xがa*bを浮動小数点数で普通に計算した値、yはその誤差分になり、x+y=a*bが厳密に成立するとされています。
しかし、オーバーフローやアンダーフローに注意が必要です。
xがオーバーフローしたら誤差も何も無いのでまあよしとしましょう。
アンダーフローは、twosumの場合、IEEE754標準のように非正規化数を用いた漸近アンダーフローを備えた浮動小数点数では原理的に発生しないので問題ありません。
しかし、twoproductの場合は、yが非正規化数の領域に入る場合にはyの有効桁数が失われてしまい、無誤差ではなくなってしまいます。すなわち、|x| < 2^(-969) (=-1074+106-1) の場合は誤差が発生します。
また、twoproductで使われているsplitにも注意が必要で、(2^27+1)を乗じている部分でオーバーフローする可能性があります。ただしこれは実装上の工夫でどうにでも回避可能。
ここまではよく知られている問題点ですが、今までにあまり注意を払われて来なかったと思われる問題点を見つけたのでメモしておきます。
まず、twoproductで
a = 6.929001713869936e+236
b = 2.5944475251952003e+71
のケース。x=a*bはオーバーフローしませんが、a1*b1がオーバーフローしてしまい正しくyが計算されません。
2つ目は、twosumで、
a = 3.5630624444874539e+307
b = -1.7976931348623157e+308
のケース。x=a+bはオーバーフローしませんが、中間変数tmpがオーバーフローしてしまい正しくyが計算されません。
twosum, twoproductを用いた方向付き丸めのエミュレート法を考えていて、これらを見つけました。
精度保証付き数値計算では一つの例外も許されないので、こういう現象には本当に神経を使います。他にまだ気付いていない問題点が存在しないと言い切れるのでしょうか。