web-dev-qa-db-ja.com

Javaで浮動小数点または倍精度を使用して浮動小数点精度エラーを回避するにはどうすればよいですか?

Javaでのfloatやdoubleの合計が長いと、非常に迷惑な問題があります。基本的に考えは、私が実行する場合です:

for ( float value = 0.0f; value < 1.0f; value += 0.1f )
    System.out.println( value );

私が得るものは:

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.70000005
0.8000001
0.9000001

浮動精度誤差の蓄積があることを理解していますが、これをどのように取り除くのですか?ダブルを使用してエラーの半分を試しましたが、結果は同じです。

何か案は?

30
tunnuz

floatまたはdoubleとしての0.1の正確な表現はありません。この表現エラーのため、結果は期待したものとわずかに異なります。

使用できるいくつかのアプローチ:

  • doubleタイプを使用する場合、必要な桁数だけを表示します。等しいかどうかをチェックするときは、どちらの場合も小さな許容誤差を考慮してください。
  • または、正確に表現しようとしている数値を格納できるタイプを使用します。たとえば、BigDecimalは0.1を正確に表現できます。

BigDecimalのコード例:

BigDecimal step = new BigDecimal("0.1");
for (BigDecimal value = BigDecimal.ZERO;
     value.compareTo(BigDecimal.ONE) < 0;
     value = value.add(step)) {
    System.out.println(value);
}

オンラインで見る: ideone

33
Mark Byers

イテレータでfloat/doubleを使用しないでください。これにより、丸め誤差が最大化されます。次を使用する場合

for (int i = 0; i < 10; i++)
    System.out.println(i / 10.0);

印刷する

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

BigDecimalが人気のある選択肢であることは知っていますが、doubleの方がはるかに高速であるためではなく、通常は理解するのにはるかに短い/クリーンであるためです。

コードの複雑さの尺度としてシンボルの数を数える場合

  • double => 11シンボルを使用
  • bigDecimalを使用(@Mark Byersの例から)=> 21シンボル

ところで:reallydoubleを使用しない正当な理由がない限り、floatを使用しないでください。

7
Peter Lawrey

not単なる累積エラーです(Javaとはまったく関係ありません)。 1.0fは、一度実際のコードに変換されると、値0.1を持ちません-すでに丸めエラーが発生しています。

から 浮動小数点ガイド:

この問題を回避するにはどうすればよいですか?

それはあなたがどんな種類の計算をしているかに依存します。

  • 結果を正確に加算する必要がある場合、特にお金を扱う場合は、特別な10進数データ型を使用します。
  • 余分な小数点以下をすべて表示したくない場合は、表示するときに結果を小数点以下の桁数に丸めてフォーマットします。
  • 使用可能なdecimalデータ型がない場合、代わりに整数を使用することもできます。完全にセントでお金の計算を行います。しかし、これはより多くの作業であり、いくつかの欠点があります。

詳細については、リンク先のサイトをご覧ください。

4

完全を期すために、これをお勧めします。

Shewchuck、「ロバストな適応浮動小数点幾何学的述語」、浮動小数点で正確な算術を実行する方法の例がもっと必要な場合、または著者の本来の意図である少なくとも制御された精度 http:// www。 cs.berkeley.edu/~jrs/papers/robustr.pdf

3
aka.nice

別の解決策は、==と2つの値が十分に近いかどうかを確認します。 (これはあなたが本文で尋ねたものではないことを知っていますが、質問のタイトルに答えています。)

2
aib

私は同じ問題に直面し、BigDecimalを使用して同じ問題を解決しました。以下は私を助けたスニペットです。

double[] array = {45.34d, 45000.24d, 15000.12d, 4534.89d, 3444.12d, 12000.00d, 4900.00d, 1800.01d};
double total = 0.00d;
BigDecimal bTotal = new BigDecimal(0.0+"");
for(int i = 0;i < array.length; i++) {
    total += (double)array[i];
    bTotal = bTotal.add(new BigDecimal(array[i] +""));
}
System.out.println(total);
System.out.println(bTotal);

それがあなたを助けることを願っています。

2
Balu SKT
package loopinamdar;

import Java.text.DecimalFormat;

public class loopinam {
    static DecimalFormat valueFormat = new DecimalFormat("0.0");

    public static void main(String[] args) {
        for (float value = 0.0f; value < 1.0f; value += 0.1f)
            System.out.println("" + valueFormat.format(value));
    }
}
2
Solus

Floatではなく、decimalデータ型を使用する必要があります。

https://docs.Oracle.com/javase/7/docs/api/Java/math/BigDecimal.html

2

最初にdoubleにします。 floatを使用しないでください。使用すると、Java.lang.Mathユーティリティ。

これで、必要な精度が事前にわかっていて、15以下である場合、doubles動作します。以下を確認してください:

// the magic method:
public final static double makePrecise(double value, int precision) {
    double pow = Math.pow(10, precision);
    long powValue = Math.round(pow * value);
    return powValue / pow;
}

これで、操作を行うたびに、doubleの結果を動作させる必要があります。

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 1) + " => " + value );

出力:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
1.0 => 0.9999999999999999

15を超える精度が必要な場合は、運が悪くなります。

for ( double value = 0.0d; value < 1.0d; value += 0.1d )
            System.out.println( makePrecise(value, 16) + " => " + value );

出力:

0.0 => 0.0
0.1 => 0.1
0.2 => 0.2
0.3000000000000001 => 0.30000000000000004
0.4 => 0.4
0.5 => 0.5
0.6 => 0.6
0.7 => 0.7
0.8 => 0.7999999999999999
0.9 => 0.8999999999999999
0.9999999999999998 => 0.9999999999999999

NOTE1:パフォーマンスのために、Math.pow配列内の操作。わかりやすくするためにここでは行いません。

NOTE2:そのため、価格にdoublesを使用することはありませんが、longsここで、最後のN(つまり、N <= 15、通常8)桁は10進数です。その後、私が上に書いたことを忘れることができます:)

1
rdalmeida

floatの使用を継続し、0.1fを繰り返し追加してエラーの蓄積を避けたい場合は、次のようにしてください:

for (int count = 0; count < 10; count++) {
    float value = 0.1f * count;
    System.out.println(value);
}

ただし、他の人がすでに説明したように、floatは無限に正確なデータ型ではないことに注意してください。

0
Jesper

計算に必要な精度と、選択したデータ型が可能な精度を認識し、それに応じて答えを提示する必要があります。

たとえば、有効数字3桁の数値を扱う場合、float(有効数字7桁の精度を提供)の使用が適切です。ただし、開始値の有効数字が2つの有効数字しかない場合、7つの有効数字の精度に対する最終回答を引用することはできません。

5.01 + 4.02 = 9.03 (to 3 significant figures)

この例では、複数の加算を実行していますが、加算ごとに最終的な精度に影響があります。

0
Nick Pierpoint