2つの等しくない浮動小数点数を引くことによって0を取得することは可能ですか?


131

次の例では、0(または無限大)による除算を取得できますか?

public double calculation(double a, double b)
{
     if (a == b)
     {
         return 0;
     }
     else
     {
         return 2 / (a - b);
     }
}

もちろん、通常はそうなりません。しかし、どのような場合ab非常に接近している、できる(a-b)ことになり0、計算の精度が原因?

この質問はJavaに関するものですが、ほとんどのプログラミング言語に当てはまると思います。


49
私はダブルスのすべての組み合わせを試さなければならないでしょう、それは時間がかかります:)
Thirler

3
@Thirlerは、JUnit Testingを使用するときのように思えます。
Matt Clark

7
@bluebrain、私の推測では、あなたのリテラル数2.000などには、浮動小数点数で表される多くの小数が含まれています。したがって、最後のものは、比較で実際に使用された数で表されません。
Thirler、2015

4
@Thirlerたぶん。「フロートまたはダブルに割り当てた数値が正確であることを実際に保証することはできません」
ガネス

4
その場合に0を返すと、デバッグが難しくなる可能性があるため、例外をスローしたりNaNを返したりするのではなく、本当に0を返したいことを確認してください。
m0skit0

回答:


132

Javaでは、a - b0ifが等しくなることはありませんa != b。これは、Javaが非正規化数をサポートするIEEE 754浮動小数点演算を義務付けているためです。仕様から:

特に、Javaプログラミング言語では、IEEE 754非正規化浮動小数点数と段階的なアンダーフローのサポートが必要です。これにより、特定の数値アルゴリズムの望ましい特性を簡単に証明できます。計算結果が非正規化数である場合、浮動小数点演算は「ゼロにフラッシュ」されません。

FPU非正規化数で機能する場合、等しくない数を減算すると(乗算とは異なり)ゼロが生成されることはありません。この質問も参照してください。

他の言語については、状況によって異なります。たとえば、CまたはC ++では、IEEE 754サポートはオプションです。

とはいえ、式2 / (a - b)がオーバーフローする可能性もa = 5e-308ありb = 4e-308ます。


4
ただし、OPは2 /(ab)について知りたいと考えています。これは有限であることを保証できますか?
Taemyr、2015

答えをありがとう、非正規化数の説明のためにウィキペディアへのリンクを追加しました。
Thirler、2015

3
@Taemyr私の編集を参照してください。分割は実際にはオーバーフローする可能性があります。
nwellnhof 2015

@Taemyr (a,b) = (3,1)=> 2/(a-b) = 2/(3-1) = 2/2 = 1これがIEEE浮動小数点に当てはまるかどうかはわかりません
Cole Johnson

1
@DrewDormann IEEE 754もC99のオプションです。標準の付録Fを参照してください。
nwellnhof 2015

50

回避策として、以下についてはどうですか?

public double calculation(double a, double b) {
     double c = a - b;
     if (c == 0)
     {
         return 0;
     }
     else
     {
         return 2 / c;
     }
}

そうすれば、どの言語でのIEEEサポートにも依存しません。


6
問題を回避し、テストを一度に簡素化します。私が好きです。
ジョシュア

11
-1の場合a=b、戻るべきではありません00IEEE 754で除算すると、例外ではなく無限になります。あなたは問題を回避しているので、戻ること0は起こるのを待つバグです。考えてください1/x + 1。の場合x=0、結果は1になりますが、正しい値は無限大ではありません。
Cole Johnson、

5
@ColeJohnson正解も無限ではありません(制限がどちら側から来るかを指定しない限り、右側= + inf、左側= -inf、未指定=未定義またはNaN)。
Nick T

12
@ChrisHayes:これは質問はXYの問題であってもよいことを認識質問に対する有効な回答である:meta.stackexchange.com/questions/66377/what-is-the-xy-problem
slebetman

17
@ColeJohnson戻ること0は本当に問題ではありません。これはOPが問題で行うことです。ブロックのその部分に、例外または状況に適したものを置くことができます。戻るの0が嫌いなら、それは問題の批判であるべきです。確かに、OPが行ったように行うことは、回答への反対票を保証するものではありません。この質問は、指定された関数が完了した後の計算とは何の関係もありません。ご存じのとおり、プログラムの要件により、を返す必要があり0ます。
jpmc26 2015

25

a - b0による浮動小数点除算では例外がスローされないため、の値に関係なく、ゼロによる除算は行われません。無限を返します。

現在、a == btrueを返す唯一の方法は、まったく同じビットab含む場合です。最下位ビットだけが異なる場合、それらの差は0にはなりません。

編集:

バトシェバが正しくコメントしたように、いくつかの例外があります:

  1. 「数ではない」はそれ自体とは偽ですが、同じビットパターンになります。

  2. -0.0は、+ 0.0とtrueを比較するように定義されており、それらのビットパターンは異なります。

両方の場合だabされているDouble.NaN、あなたは、else節に到達しますが、以降NaN - NaNも戻ってNaN、あなたはゼロ除算されることはありません。


11
エラン; 厳密には当てはまりません。「数ではない」はそれ自体とは偽ですが、同じビットパターンになります。また、-0.0は+0.0とtrueを比較するように定義されており、それらのビットパターンは異なります。
バトシェバ2015

1
@Bathsheba私はこれらの特別なケースを考慮していませんでした。コメントをありがとう。
エラン2015

2
@ Eran、0による除算は浮動小数点で無限大を返す非常に良い点。質問に追加しました。
Thirler、2015

2
@Prashantですが、この場合、除算は行われません。a== bがtrueを返すためです。
エラン2015

3
実際に、ゼロによる除算のFP例外が発生する可能性があります。これは、IEEE-754標準で定義されているオプションですが、「例外」でほとんどの人が意味するものではないかもしれません;)
Voo

17

ここでゼロによる除算が発生するケースはありません。

SMTソルバー Z3は、正確なIEEE浮動小数点演算をサポートします。Z3にa、次のbような数値などを見つけるように依頼してみましょうa != b && (a - b) == 0

(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);

(のような例を見落とすことは困難であるため、IEEEフロートの質問に答えるためにZ3を使用するといいですNaN-0f+-inf)あなたは、任意の質問をすることができます。仕様を解釈して引用する必要はありません。「この特定のint log2(float)アルゴリズムは正しいですか?」のように、floatとintegerの混合の質問をすることもできます。


SMT Solver Z3へのリンクとオンラインインタープリターへのリンクを追加できますか?この答えは完全に合法であるように見えますが、誰かがこれらの結果が間違っていると考えることができます。
AL

12

提供された関数は確かに無限大を返すことができます:

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に格納されるほど大きい場合、分母がゼロ以外であっても無限大が返されます。


6

IEEE-754に準拠する浮動小数点実装では、各浮動小数点型は2つの形式で数値を保持できます。ほとんどの浮動小数点値には1(「正規化」)が使用されますが、それが表すことができる2番目に小さい数は、最小値よりもほんの少しだけ大きいため、それらの違いは同じ形式では表現できません。もう1つの(「非正規化」)形式は、最初の形式では表現できない非常に小さな数値に対してのみ使用されます。

非正規化浮動小数点形式を効率的に処理する回路は高価であり、すべてのプロセッサに含まれているわけではありません。一部のプロセッサは、非常に小さい数値での演算を他の値での演算よりもはるかに遅くするか、正規化された形式では小さすぎる数値をゼロと見なすかのどちらかを選択できます。

Java仕様は、実装が非正規化形式をサポートする必要があることを示唆しています。一方、一部の実装では、ほとんどの目的で問題には小さすぎる値のわずかにずさんな処理と引き換えに、コードをより高速に実行できるオプションを提供する可能性があります(値が小さすぎて問題にならない場合、それらを使用した計算は、重要な計算の10倍の時間がかかるので煩わしい場合があります。そのため、多くの実際の状況では、ゼロへのフラッシュは、遅いが正確な計算よりも便利です。


6

IEEE 754以前の昔は、a!= bがab!= 0を意味しておらず、その逆も可能でした。それがIEEE 754を最初に作成した理由の1つでした。

IEEE 754では、ほぼ保証されています。CまたはC ++コンパイラは、必要以上に高い精度で操作を実行できます。したがって、aとbが変数ではなく式である場合、(a + b)!= cは(a + b)-c!= 0を意味しません。より高い精度。

多くのFPUは、非正規化数を返さずに0に置き換えるモードに切り替えることができます。そのモードでは、aとbが小さな正規化数であり、差が最小正規化数よりも小さいが0より大きい場合、a != bもa == bを保証するものではありません。

「決して浮動小数点数を比較しない」はカーゴカルトプログラミングです。「イプシロンが必要です」というマントラを持っている人々の中で、ほとんどはそのイプシロンを適切に選択する方法を知りません。


2

あなたこれを起こさせることができるかもしれないケースを考えることができます。これがベース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桁だけ追加することで問題を回避できます。


1
減算のために整列されたオペランドを保持するレジスタは、この状況に対処するために、「ガードビット」と呼ばれる追加の2ビットを保持する必要があります。減算によって最上位ビットから借用が発生するシナリオでは、小さいオペランドの大きさが大きいオペランドの半分を超える必要がある(つまり、1ビットの精度しか追加できないことを意味する)か、結果が少なくとも小さい方のオペランドの半分の大きさ(これは、ビットが1つだけ必要になることと、正しい丸めを保証するのに十分な情報があることを意味します)。
スーパーキャット2015

1
「これが発生するかどうかは、最終的にはFPU設計に依存します」いいえ、Javaの定義では不可能であるため、発生することはありません。FPUの設計は、FPUとは何の関係もありません。
Pascal Cuoq 2015

@PascalCuoq:私が間違っていて、strictfp有効になっていない場合は修正してください。計算が小さすぎdoubleて拡張精度の浮動小数点値に収まる可能性があります。
スーパーキャット2015

@supercatが存在しないことはstrictfp、「中間結果」の値にのみ影響します。私はdocs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.4から引用していますaおよびbdouble変数であり、中間結果ではないため、それらの値は倍精度値であり、2 ^ -1074の倍数になります。これらの2つの倍精度値の減算は、結果として2 ^ -1074の倍数になるため、より広い指数範囲は、a == bの場合の差が0であるという特性を変更します。
Pascal Cuoq 2015

@supercatこれは理にかなっています-これを行うために必要なビットは1つだけです。
Keldor314 2015

1

floatまたはdoubleが等しいかどうかを比較することはできません。なぜなら、floatまたはdoubleに割り当てる数値が正確であることを実際に保証することはできないからです。

floatが等しいかどうかを正しく比較するには、値が同じ値に「十分近い」かどうかを確認する必要があります。

if ((first >= second - error) || (first <= second + error)

6
「これまでにない」は少し強いですが、一般的にこれは良いアドバイスです。
Mark Pattison、2015

1
あなたが本当である間、abs(first - second) < error(または<= error)はより簡単でより簡潔です。
glglgl 2015

3
ほとんどの場合(すべてではありません)はtrueですが、実際には質問に答えていません。
ミレニアムバグ2015

4
浮動小数点数が等しいかどうかをテストすると非常に便利です。慎重に選択されていないイプシロンと比較することには正気はなく、同等性をテストしているときのイプシロンと比較することの正気はさらに低くなります。
tmyklebu 2015

1
浮動小数点キーで配列をソートする場合、浮動小数点数をイプシロンと比較するトリックを使用しようとすると、コードが機能しないことを保証できます。なぜなら、a == bおよびb == cがa == cを意味するという保証はもうないからです。ハッシュテーブルについては、まったく同じ問題です。平等が推移的でない場合、アルゴリズムは壊れます。
gnasher729 2015

1

ゼロによる除算は定義されていません。正の数からの制限は無限大になる傾向があるため、負の数からの制限は負の無限大になる傾向があります。

言語タグがないため、これがC ++であるかJavaであるかは不明です。

double calculation(double a, double b)
{
     if (a == b)
     {
         return nan(""); // C++

         return Double.NaN; // Java
     }
     else
     {
         return 2 / (a - b);
     }
}

1

核となる問題は、「小数が多すぎる」場合、たとえば数値として書き込めないdoubleを処理する場合など、double(別名float、または数学言語では実数)のコンピューター表現が間違っていることです。 piまたは1/3の結果)。

したがって、a == bは、aとbのdouble値では実行できません。OS対FPU対数対言語対0の後の3のカウントに応じて、trueまたはfalseになります。

とにかく、コンピューターで "二重値計算"を行う場合は、精度を処理する必要があるため、を行う代わりに、を行うa==b必要があります。absolute_value(a-b)<epsilonイプシロンは、その時点でアルゴリズムでモデル化しているものに相対的です。すべての二重比較でイプシロン値を使用することはできません。

簡単に言うと、a == bと入力すると、コンピューターでは変換できない数学的な式になります(浮動小数点数の場合)。

PS:ハム、私がここで答えるすべては、多かれ少なかれ、他の応答やコメントにあります。


1

@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であるかどうかを知る最も簡単な方法は、実際に除算を実行することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.