検索条件
全1件
(1/1ページ)
def dd_add_down(x1, x2, y1, y2) :
z1, z2 = twosum(x1, y1)
if z1 == -float("inf") :
return z1, 0
if z1 == float("inf") :
return sys.float_info.max, sys.float_info.max * 2.**(-54)
down()
z2 = z2 + x2 + y2
near()
z1, z2 = twosum(z1, z2)
return z1, z2
無誤差のtwosumはそのまま計算し、誤差の入る可能性のある「z2 + x2 + y2」の部分は下向き丸めで計算することで、下向き丸めの結果を得ています。これに加えて、赤字で示したオーバーフローの処理を行っています。ddライブラリではIEEE754と同様の無限大の取り扱いを出来るように設計しています。これは、端点に無限大を許す区間演算の実装には不可欠です。無限大は(inf,0)または(-inf,0)のような内部表現にしています。また、twosumは
def twosum(a, b) :
x = a + b
if (abs(a) > abs(b)):
tmp = x - a
y = b - tmp
else:
tmp = x - b
y = a - tmp
return x, y
のような実装になっていますが、a,bが無限大でなくa+bがオーバーフローした場合、(inf,-inf)を返すことが分かります。add_downのオーバーフロー処理は、
def dd_add_down(x1, x2, y1, y2) :
if abs(x1) == float("inf") or abs(y1) == float("inf") :
return x1 + y1, 0
z1, z2 = twosum(x1, y1)
if z1 == -float("inf") :
return z1, 0
if z1 == float("inf") :
z1 = sys.float_info.max
z2 = sys.float_info.max * 2.**(-54)
down()
z2 = z2 + x2 + y2
near()
z1, z2 = twosum(z1, z2)
if z1 == -float("inf") :
return z1, 0
if z1 == float("inf") :
z1 = sys.float_info.max
z2 = sys.float_info.max * 2.**(-54)
return z1, z2
すなわち、まず引数に無限大が含まれている場合を先に処理し(3.の対策)、次にtwosum後に正方向にオーバーフローして正の最大数に置き換えた場合はreturnせずにそのまま計算を継続するようにし(2.の対策)、最後に2回目のtwosumに対しても同じようにオーバーフロー処理を行なう(2.の対策)ようにしました。
#include <kv/interval.hpp>
#include <kv/dd.hpp>
#include <kv/rdd.hpp>
int main()
{
std::cout.precision(32);
kv::interval<kv::dd> x, y, z;
x.lower().a1 = pow(2., 1023) - pow(2., 970);
x.lower().a2 = -(pow(2., 969) - pow(2., 916));
x.upper() = x.lower();
std::cout << x << "\n";
y.lower().a1 = pow(2., 1023);
y.lower().a2 = -pow(2., 969);
y.upper() = y.lower();
std::cout << y << "\n";
z = x + y;
std::cout << z << "\n";
}
これを改修前のkvで計算すると[8.9884656743115780417662938029053e+307,8.9884656743115780417662938029054e+307] [8.9884656743115790396864485702651e+307,8.9884656743115790396864485702652e+307] [1.797693134862315807937289714053e+308,inf]となりますが、正しい値は1.797693134862315708145274237317049\cdots \times 10^{307}なので下端が真値より大きくなってしまっています。改修後は、
[8.9884656743115780417662938029053e+307,8.9884656743115780417662938029054e+307] [8.9884656743115790396864485702651e+307,8.9884656743115790396864485702652e+307] [1.797693134862315708145274237317e+308,inf]と正しく計算されていることが分かります。
#include <kv/interval.hpp>
#include <kv/dd.hpp>
#include <kv/rdd.hpp>
int main()
{
std::cout.precision(32);
kv::interval<kv::dd> x, y, z;
x.lower().a1 = pow(2., 1023);
x.lower().a2 = pow(2., 970);
x.upper() = x.lower();
std::cout << x << "\n";
y.lower().a1 = pow(2., 1023) - pow(2., 971);
y.lower().a2 = pow(2., 969) - pow(2., 916);
y.upper() = y.lower();
std::cout << y << "\n";
z = x + y;
std::cout << z << "\n";
}
これを実行すると、改修前は[8.988465674311580536566680721305e+307,8.9884656743115805365666807213051e+307] [8.9884656743115780417662938029052e+307,8.9884656743115780417662938029053e+307] [inf,inf]のように[inf,inf]という不正な区間が発生していましたが、改修後は
[8.988465674311580536566680721305e+307,8.9884656743115805365666807213051e+307] [8.9884656743115780417662938029052e+307,8.9884656743115780417662938029053e+307] [1.797693134862315807937289714053e+308,inf]のように[ddの最大数、inf]という正しい結果を返すようになりました。
#include <kv/interval.hpp>
#include <kv/dd.hpp>
#include <kv/rdd.hpp>
int main()
{
std::cout.precision(32);
kv::dd x, y, z;
x = std::numeric_limits<kv::dd>::infinity();
std::cout << x << "\n";
y = 0;
std::cout << y << "\n";
z = kv::rop<kv::dd>::add_down(x, y);
std::cout << z << "\n";
}
これを実行すると、inf 0 1.797693134862315807937289714053e+308のようにinf+0の下向き丸めが正の最大数になってしまいますが、改修でちゃんと
inf 0 infと正常になりました。