次の例で0(または無限大)で除算することは可能ですか?
public double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
通常の場合、もちろんそうではありません。しかし、a
とb
が非常に近い場合、(a-b)
結果は0
計算の精度のため?
この質問はJavaに関するものですが、ほとんどのプログラミング言語に当てはまると思います。
Javaでは、_a - b
_の場合、_0
_が_a != b
_と等しくなることはありません。これは、Javaが非正規化数をサポートするIEEE 754浮動小数点演算を義務付けているためです。 spec から:
特に、Javaプログラミング言語では、IEEE 754非正規化浮動小数点数と段階的アンダーフローのサポートが必要です。これにより、特定の数値アルゴリズムの望ましい特性を簡単に証明できます。計算結果が非正規化数の場合、ゼロにフラッシュします。
[〜#〜] fpu [〜#〜] が 非正規化数 で機能する場合、等しくない数を減算してもゼロになることはありません(乗算とは異なります) この質問 。
他の言語の場合、それは異なります。たとえば、CまたはC++では、IEEE 754サポートはオプションです。
とはいえ、 可能 式2 / (a - b)
がオーバーフローする場合、たとえば_a = 5e-308
_および_b = 4e-308
_の場合。
回避策として、以下はどうですか?
public double calculation(double a, double b) {
double c = a - b;
if (c == 0)
{
return 0;
}
else
{
return 2 / c;
}
}
そうすれば、どの言語のIEEEサポートにも依存しません。
0による浮動小数点除算では例外がスローされないため、a - b
の値に関係なく、ゼロによる除算は行われません。無限を返します。
これで、a == b
がtrueを返す唯一の方法は、a
とb
にまったく同じビットが含まれている場合です。最下位ビットだけが異なる場合、それらの差は0になりません。
編集:
バトシェバが正しくコメントしたように、いくつかの例外があります。
「数字ではない」はそれ自体と偽ですが、ビットパターンは同じです。
-0.0はtrueと+0.0を比較するために定義されており、ビットパターンは異なります。
したがって、a
とb
の両方がDouble.NaN
の場合、else節に到達しますが、NaN - NaN
もNaN
を返すため、ゼロで除算します。
ここでゼロによる除算が発生する場合はありません。
SMTソルバーZ は、正確なIEEE浮動小数点演算をサポートします。 Z3にa != b && (a - b) == 0
となるような番号a
とb
を見つけるように依頼しましょう:
_(set-info :status unknown)
(set-logic QF_FP)
(declare-fun b () (FloatingPoint 8 24))
(declare-fun a () (FloatingPoint 8 24))
(declare-fun rm () RoundingMode)
(assert
(and (not (fp.eq a b)) (fp.eq (fp.sub rm a b) +zero) true))
(check-sat)
_
結果はUNSAT
です。そのような番号はありません。
上記のSMTLIB文字列により、Z3は任意の丸めモード(rm
)を選択することもできます。これは、可能なすべての丸めモード(そのうち5つ)に対して結果が保持されることを意味します。結果には、遊び中の変数がNaN
または無限大である可能性も含まれます。
_a == b
_は_fp.eq
_品質として実装されているため、_+0f
_と_-0f
_は等しくなります。ゼロとの比較も_fp.eq
_を使用して実装されます。質問はゼロによる除算を回避することを目的としているため、これは適切な比較です。
等値性テストがビット単位の等値性を使用して実装された場合、_+0f
_および_-0f
_は_a - b
_をゼロにする方法でした。この回答の誤った以前のバージョンには、好奇心のためのそのケースに関するモードの詳細が含まれています。
Z3 Online はFPA理論をまだサポートしていません。この結果は、最新の不安定ブランチを使用して取得されました。次のように.NETバインディングを使用して再現できます。
_var fpSort = context.MkFPSort32();
var aExpr = (FPExpr)context.MkConst("a", fpSort);
var bExpr = (FPExpr)context.MkConst("b", fpSort);
var rmExpr = (FPRMExpr)context.MkConst("rm", context.MkFPRoundingModeSort());
var fpZero = context.MkFP(0f, fpSort);
var subExpr = context.MkFPSub(rmExpr, aExpr, bExpr);
var constraintExpr = context.MkAnd(
context.MkNot(context.MkFPEq(aExpr, bExpr)),
context.MkFPEq(subExpr, fpZero),
context.MkTrue()
);
var smtlibString = context.BenchmarkToSMTString(null, "QF_FP", null, null, new BoolExpr[0], constraintExpr);
var solver = context.MkSimpleSolver();
solver.Assert(constraintExpr);
var status = solver.Check();
Console.WriteLine(status);
_
Z3を使用してIEEE floatの質問に答えることは、ケース(NaN
、_-0f
_、_+-inf
_など)を見落とすのが難しく、任意の質問をすることができるため、素晴らしいです。仕様を解釈して引用する必要はありません。 「この特定のint log2(float)
アルゴリズムは正しいですか?」など、フロートと整数の混在した質問をすることもできます。
提供された関数は実際に無限を返すことができます:
public class Test {
public static double calculation(double a, double b)
{
if (a == b)
{
return 0;
}
else
{
return 2 / (a - b);
}
}
/**
* @param args
*/
public static void main(String[] args) {
double d1 = Double.MIN_VALUE;
double d2 = 2.0 * Double.MIN_VALUE;
System.out.println("Result: " + calculation(d1, d2));
}
}
出力はResult: -Infinity
。
除算の結果が大きすぎてdoubleに格納される場合、分母がゼロ以外であっても無限大が返されます。
IEEE-754に準拠する浮動小数点実装では、各浮動小数点型は2つの形式の数値を保持できます。 1つ(「正規化された」)はほとんどの浮動小数点値に使用されますが、2番目に小さい数値は最小値よりも小さいため、同じ形式ではそれらの差を表現できません。他の(「非正規化」)形式は、最初の形式では表現できない非常に小さな数値にのみ使用されます。
非正規化浮動小数点形式を効率的に処理するための回路は高価であり、すべてのプロセッサに含まれているわけではありません。一部のプロセッサでは、本当に小さな数の操作をmuch他の値の操作よりも遅くするか、または正規化された形式では小さすぎる数をゼロと見なすかを選択できます。
Java仕様は、コードの実行が遅くなるマシンでも、実装が非正規化形式をサポートすることを意味します。一方、一部の実装ではコードを許可するオプションが提供される可能性がありますほとんどの場合、問題となるには小さすぎる値のわずかにずさんな処理と引き換えに高速で実行します(値が問題となるには小さすぎる場合、重要な計算の10倍の時間がかかるのは煩わしい場合があります) 、したがって、多くの実際的な状況では、ゼロにフラッシュする方が、遅いが正確な算術よりも便利です。
IEEE 754より前の昔は、a!= bがa-b!= 0を暗示していなかった可能性があり、その逆も同様でした。それが、そもそもIEEE 754を作成した理由の1つでした。
IEEE 754ではalmostが保証されています。 CまたはC++コンパイラは、必要以上に高い精度で操作を実行できます。したがって、aとbが変数ではなく式である場合、(a + b)!= cは(a + b)-c!= 0を意味しません。より高い精度。
多くのFPUは、非正規化数を返さずに0に置き換えるモードに切り替えることができます。そのモードでは、aとbが最小の正規化数より小さく、0より大きい場合、a != bもa == bを保証しません。
「浮動小数点数を比較しない」は、貨物カルトプログラミングです。 「イプシロンが必要」というマントラを持っている人の中には、ほとんどの場合、イプシロンを適切に選択する方法がわかりません。
私はあなたがmightがこれを起こさせることができる場合を考えることができます。これは、ベース10の類似のサンプルです。実際、これはもちろんベース2でも起こります。
浮動小数点数は多かれ少なかれ科学的記法で保存されます-つまり、35.2を見る代わりに、保存される数は3.52e2のようになります。
便宜上、基数10で動作し、精度が3桁の浮動小数点ユニットがあるとします。 10.0から9.99を引くとどうなりますか?
1.00e2-9.99e1
各値に同じ指数を与えるためにシフト
1.00e2-0.999e2
3桁に丸める
1.00e2-1.00e2
ええとああ!
これが最終的に発生するかどうかは、FPUの設計に依存します。 doubleの指数の範囲は非常に広いため、ハードウェアはある時点で内部的に丸める必要がありますが、上記の場合、内部で1桁だけ余分に問題を防ぐことができます。
中心的な問題は、数値として書き込めないdoubleを扱う場合など、10進数が多すぎる場合、コンピューター表現のdouble(別名float、または数学言語の実数)が間違っていることです( piまたは1/3の結果)。
したがって、aとbの二重の値ではa == bを行うことはできません。 OS対FPU対数対言語対0の後の3のカウントに応じて、trueまたはfalseになります。
とにかく、コンピューターで「二重値計算」を行う場合は、精度に対処する必要があるため、_a==b
_を行う代わりにabsolute_value(a-b)<epsilon
を行う必要があり、イプシロンはあなたが何であるかに関連しますその時点でのアルゴリズムのモデリング。すべての二重比較にイプシロン値を使用することはできません。
簡単に言えば、a == bと入力すると、コンピューター上で変換できない数式表現があります(浮動小数点数の場合)。
PS:うーん、ここで私が答えるのは、多かれ少なかれ他の人の応答やコメントです。
浮動小数点数や倍精度を比較して同等にすべきではありません。なぜなら、floatまたはdoubleに割り当てる数値が正確であることを実際に保証できないからです。
浮動小数点数を同等に比較するには、値が同じ値に「十分に近い」かどうかを確認する必要があります。
if ((first >= second - error) || (first <= second + error)
@malarresの応答と@Taemyrのコメントに基づいて、私の小さな貢献を以下に示します。
public double calculation(double a, double b)
{
double c = 2 / (a - b);
// Should not have a big cost.
if (isnan(c) || isinf(c))
{
return 0; // A 'whatever' value.
}
else
{
return c;
}
}
私のポイントは言うことです:除算の結果がnanかinfかを知る最も簡単な方法は、実際に除算を実行することです。
ゼロによる除算は未定義です。正の数からの制限は無限大になり、負の数からの制限は負の無限大になりやすいためです。
言語タグがないため、これがC++かJavaかどうかはわかりません。
double calculation(double a, double b)
{
if (a == b)
{
return nan(""); // C++
return Double.NaN; // Java
}
else
{
return 2 / (a - b);
}
}