検索条件
全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と正常になりました。