4

私が取り組んでいるアプリケーションの計算を行っていると、奇妙な結果が得られました。

この特定の計算の要件では、計算が次のようになる必要があります。

AとBはわかっている

A * B = C

この特定の計算について

A = 0.0410
B = 123456789010

ここに私が見ている結果があります:

電卓:

0.0410 * 123456789010 = 5061728349.41

ジャワ:

B は double です:

0.0410f * 123456789010d = 5.061728489223363E9 = 5061728489.223363

B は long です:

0.0410f * 123456789010l = 5.0617288E9 

精度の低下は、10 と 1 のスポットの違いほど重要ではありません (とにかく 9 桁の精度しか必要ありません)。double を使用して計算を行うと、「間違った」結果が得られるのはなぜですか?

ちなみに、 を使って計算してみたBigDecimalところ、 double を使ったのと同じ結果が得られました。

4

5 に答える 5

6

発生するさまざまな型変換は、JLS #5.6.2で指定されています。あなたの場合(抜粋):

  • いずれかのオペランドが double 型の場合、もう一方は double に変換されます。
  • それ以外の場合、いずれかのオペランドが float 型の場合、もう一方は float に変換されます。

では0.0410f * 123456789010d = 506172848.92233630.0410fは最初に double に変換されますが、これは必ずしも と等しいとは限りません0.0410d。実際に試してみると、そうではないことがわかります。

    double d1 = 0.041d;
    double d2 = 0.041f;
    System.out.println(new BigDecimal(d1));
    System.out.println(new BigDecimal(d2));

出力:

0.041000000000000001720845688168992637656629085540771484375
0.041000001132488250732421875

次の例では:

0.0410f * 123456789010L = 506172832

long は float に変換されます。これは、次の例で確認できます。

    float f1 = 0.0410f;
    float f2 = 123456789010L;
    System.out.println(new BigDecimal(f1)); // 0.041000001132488250732421875
    System.out.println(new BigDecimal(f2)); // 123456790528
    System.out.println(new BigDecimal(0.0410f * 123456789010L)); // 5061728768
    System.out.println(new BigDecimal(f1 * f2)); // 5061728768

float / double 演算の一般的な精度については、この質問を確認してください。

最後に、BigDecimal を使用すると、正しい答えが得られます。

    BigDecimal a = new BigDecimal("0.041");
    BigDecimal b = new BigDecimal("123456789010");
    System.out.println(a.multiply(b)); // outputs 5061728349.410
于 2012-06-06T12:22:20.667 に答える
2

TLDR 回答: float は「正しい」回答を正確に表すことができません。代わりに double を使用してください。また、明示的なキャストがないと、乗算も不正確に行われます。

http://www.ideone.comを使用して得た回答

  A      B     C      
float  long  float  5061728768.000000
double long  double 5061728489.223363

問題は、float の精度が double よりもはるかに小さいことです。そのため、大きな数 (たとえば、10^10 の値) を乗算すると、乗算でこの精度が失われます。乗算のために A を double に明示的にキャストすると、次のようになります。

double C = ((double)A)*B; //=5061728489.223363

次に、追加の精度を取得します。double の回答を float にキャストすると、次のようになります。

float C = (float)((double)((double)A)*B); //=5061728256.000000 

答えがまた違うことがわかります。乗算の結果の型が使用されるため、この例doubleでは が、キャスト バックによりfloat精度が低下します。double C=A*Bdouble ( )の明示的なケースがなければ、float型が使用されます。どちらのキャストでも、乗算は double として実行され、乗算に精度が失われます。

于 2012-06-06T12:24:26.307 に答える
1

最初の計算はdouble(64ビット)を使用し、2番目の計算はfloat(32ビット)を使用します。表示されているのは「丸め誤差」です。

どちらの場合も浮動小数点計算ですが、2番目の場合は「double」引数が含まれないため、32ビット演算のみを使用します。

Java言語仕様の引用:

二項演算子のオペランドの少なくとも1つが浮動小数点型の場合、もう一方が整数であっても、演算は浮動小数点演算です。

数値演算子のオペランドの少なくとも1つがdouble型の場合、64ビット浮動小数点演算を使用して演算が実行され、数値演算子の結果はdouble型の値になります。もう一方のオペランドがdoubleでない場合は、最初に拡張され(§5.1.5)、数値昇格(§5.6)によってdoubleと入力します。

それ以外の場合、演算は32ビット浮動小数点演算を使用して実行され、数値演算子の結果はfloat型の値になります。(他のオペランドがfloatでない場合は、最初に数値昇格によってfloatと入力するように拡張されます。)

于 2012-06-06T12:20:04.367 に答える
1

32ビットIEEE浮動小数点数の精度は7桁です。64ビットは16を許可します。それがあなたが得るすべてです。どちらも十分でない場合は、BigDecimalを使用する必要があります。

これは、Javaだけでなく、IEE標準を実装するすべての言語に当てはまります。

于 2012-06-06T12:27:32.723 に答える
1

あなたの質問に対する答えは、おそらくJava 言語仕様の浮動小数点演算セクションと、この古い投稿にあります。発生している暗黙の変換により、丸め誤差が発生している可能性があります。

あなたの状況に当てはまる引用は

3 番目の操作:

2 項演算子のオペランドの少なくとも 1 つが浮動小数点型である場合、その演算は浮動小数点演算であり、もう一方が整数であっても同様です。

2 番目の操作:

数値演算子のオペランドの少なくとも 1 つが double 型の場合、演算は 64 ビット浮動小数点演算を使用して実行され、数値演算子の結果は double 型の値になります。もう一方のオペランドが double でない場合は、最初に数値昇格 (§5.6) によって double 型に拡張 (§5.1.5) されます。

最初の操作

それ以外の場合、演算は 32 ビット浮動小数点演算を使用して実行され、数値演算子の結果は float 型の値になります。(他のオペランドが float でない場合は、最初に数値昇格によって float 型に拡張されます。)

したがって、心配する必要はありませんが、希望する精度を決定し、必要に応じて適切な鋳造を使用してください。

于 2012-06-06T12:24:25.793 に答える