検索条件
全1件
(1/1ページ)
double x, y; int i; y = frexp(x, &i);のように呼び出すと、xを入力として 0.5 ≤ |y| < 1, x = y × 2iを満たすようなyとiを計算してくれる関数で、(IEEE754の指数部とは1ずれるが)指数部を取り出してくれる関数としてよく用いられます。ddに対しても、
kv::dd x, y; int i; y = frexp(x, &i);のように同様に使える関数を用意していました。これの実装は、
friend dd frexp(const dd& x, int* m) { double z1, z2; z1 = std::frexp(x.a1, m); z2 = std::ldexp(x.a2, -(*m)); return dd(z1, z2); }のようなものでした。しかしこれだと、xが(1, -2-54)のように2のベキ乗よりわずかに小さく、2のベキ乗と小さな負の数の和で表現されている場合、指数部を正しく取り出すことが出来ません。これを、
friend dd frexp(const dd& x, int* m) { double z1, z2; z1 = std::frexp(x.a1, m); z2 = std::ldexp(x.a2, -(*m)); if ((z1 == 0.5 && z2 < 0) || (z1 == -0.5 && z2 > 0)) { z1 *= 2; z2 *= 2; (*m)--; } return dd(z1, z2); }のように修正する必要がありました。完全に作者の見落としであり、これを見つけてくれた学生には感謝しかないです。このバグが発生する確率は非常に低いので、かなりしつこく追い込まないと見つからない種類のバグだと思います。これにより、x = 1-2-54に対して、
x: 0.999999999999999944488848768742172978818416595458984375 y: 0.4999999999999999722444243843710864894092082977294921875 i: 1のように誤った(0.5 ≤ |y| < 1を満たしていない)値を返していたのに対して、
x: 0.999999999999999944488848768742172978818416595458984375 y: 0.999999999999999944488848768742172978818416595458984375 i: 0のように正しく計算されるようになりました。
[0,0]のように誤った(真値を含まない)値が計算されました。frexpは、返却値のうち仮数部yが問題であり、指数部iは信頼できるので、指数部iのみ使って、yは改めて区間演算するように精度保証付きlogの計算方法を改めることで対処しました。これで、
[0,9.881312916824930883531375857364427447301196052286495288511713650013510145404 17503730599672723271984759593129390891435461853313420711879592797549592021563756 25260142638062280905569163433569796420737743727211399746144610001277481830712996 87746249467945463392302800634307707961482524771311823420533171133735363740791206 21249863890543182984910658610913088802254960259419999083863978818160833126649049 51429573802945356031871047722310026960705298694403875805362142149834066644536895 06671441664863872184765786916736120212023012339619506156684554636658495809965049 46155275185449574931216955640746893939906729403594535543517025132110239826300978 22029020757254763345019116747794671979873296198823284114052741805584855350891304 5817507736501283943653106689453125e-324]のように正しい値を含む区間が返されるようになりました。