Javaでdoubleを使用して精度を保持する


149
public class doublePrecision {
    public static void main(String[] args) {

        double total = 0;
        total += 5.6;
        total += 5.8;
        System.out.println(total);
    }
}

上記のコードは出力します:

11.399999999999

これをどのようにして印刷する(またはそれを11.4として使用できるようにする)ことができますか?


回答:


151

他の人が述べたように、BigDecimal11.4の正確な表現が必要な場合は、おそらくクラスを使用する必要があります。

さて、なぜこれが起こっているのかについて少し説明します:

floatそしてdoubleJavaでプリミティブ型である点フローティング数は分数と指数のバイナリ表現として記憶される数値を、。

より具体的には、double型などの倍精度浮動小数点値は64ビット値です。ここで、

  • 1ビットは符号(正または負)を示します。
  • 指数用の11ビット。
  • 有効数字52ビット(小数部は2進数)。

これらの部分が組み合わされてdouble、値の表現が生成されます。

(出典:ウィキペディア:倍精度

Javaでの浮動小数点値の処理方法の詳細については、セクション4.2.3: Java言語仕様の浮動小数点型、形式、および値を参照してください。

bytecharintlongタイプは、固定小数点数の正確なrepresentionsある数字、。固定小数点数とは異なり、浮動小数点数は(「ほとんどの場合」と想定しても安全)、数値の正確な表現を返すことができない場合があります。これがの11.399999999999結果としてあなたが終わる理由です5.6 + 5.8

1.5や150.1005などの正確な値が必要な場合は、固定小数点型のいずれかを使用すると、数値を正確に表すことができます。

すでに何度か言及されているように、JavaにはBigDecimal非常に大きな数と非常に小さな数を処理するクラスがあります。

BigDecimalクラスのJava APIリファレンスから:

不変の任意精度の符号付き10進数。BigDecimalは、任意の精度の整数のスケールなしの値と32ビット整数のスケールで構成されます。ゼロまたは正の場合、位取りは小数点の右側の桁数です。負の場合、数値のスケーリングされていない値に、スケールの否定の累乗に10を掛けます。したがって、BigDecimalで表される数値の値は(unscaledValue×10 ^ -scale)になります。

浮動小数点数とその精度の問題に関して、スタックオーバーフローに関する多くの質問がありました。興味があるかもしれない関連する質問のリストはここにあります:

浮動小数点数の細部までこだわる必要がある場合は「すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと」をご覧ください。


3
実際、「小数点」の前の1は非正規化された値を除くすべての値に適用され、追加の精度のビットを与えるため、通常53の有効ビットがあります。たとえば、3は(1.)1000 ... x 2 ^ 1として保存され、0.5は(1.)0000 ... x 2 ^ -1として保存されます。値が非正規化されると(すべての指数ビットがゼロ)、次のことができます。通常、有効桁数は少なくなります。たとえば、1 x 2 ^ -1030は(0.)00000001 x 2 ^ -1022として格納されるため、7桁の有効数字が縮尺に合わせて犠牲になっています。
サラフィリップス

1
doubleは小数点以下15桁の精度であるため、この場合BigDecimalよりもはるかに低速doubleですが、丸めが必要なだけであることに注意してください。
Peter Lawrey

2
@PeterLawrey すべてが小数点の前にある場合、精度は15 です。10進数と2進数の分数の比較不可能なため、小数点の後に何かが発生する可能性があります。
ローンの侯爵

@EJP正解です。有効桁数は約15桁です。これは、16とすることができるが、それは15または多分14であると仮定する方が安全だ
ピーターLawrey

@PeterLawrey EJPの補正は、私の質問のためであった:stackoverflow.com/questions/36344758/...は、あなたはその正確15とどのような状況で、それは16または14ことができない理由を拡張してくださいだろうか?
Shivam Sinha

103

たとえば、のように倍精度数を入力すると33.33333333333333、実際に得られる値は、最も近い表現可能な倍精度値であり、正確には次のようになります。

33.3333333333333285963817615993320941925048828125

それを100で割ると、次のようになります。

0.333333333333333285963817615993320941925048828125

これも倍精度数として表現できないため、正確に次の最も近い表現可能な値に丸められます。

0.3333333333333332593184650249895639717578887939453125

あなたは、この値をプリントアウトするとき、それは丸められます再び与えて、17桁に:

0.33333333333333326

114
将来これを読んでいて、答えが質問と何の関係もないのかと戸惑う人にとっては、私(と他の人)が答えた質問をマージすることに決めたモデレーターが、このかなり異なる質問で答えました。
スティーブンキャノン

正確なdouble値をどのようにして知るのですか?
Michael Yaworski、2014年


23

値を分数として処理する場合は、分子と分母のフィールドを保持するFractionクラスを作成できます。

加算、減算、乗算、除算のメソッドとtoDoubleメソッドを記述します。このようにして、計算中にフロートを回避できます。

編集:迅速な実装、

public class Fraction {

private int numerator;
private int denominator;

public Fraction(int n, int d){
    numerator = n;
    denominator = d;
}

public double toDouble(){
    return ((double)numerator)/((double)denominator);
}


public static Fraction add(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop + bTop, a.denominator * b.denominator);
    }
    else{
        return new Fraction(a.numerator + b.numerator, a.denominator);
    }
}

public static Fraction divide(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.denominator, a.denominator * b.numerator);
}

public static Fraction multiply(Fraction a, Fraction b){
    return new Fraction(a.numerator * b.numerator, a.denominator * b.denominator);
}

public static Fraction subtract(Fraction a, Fraction b){
    if(a.denominator != b.denominator){
        double aTop = b.denominator * a.numerator;
        double bTop = a.denominator * b.numerator;
        return new Fraction(aTop-bTop, a.denominator*b.denominator);
    }
    else{
        return new Fraction(a.numerator - b.numerator, a.denominator);
    }
}

}

1
きっとnumerator、s であるdenominator必要がありintますか?浮動小数点の精度が必要なのはなぜですか?
Samir Talwar、2010年

それは本当に必要ではないと思いますが、コードが読みやすくなるようにtoDouble関数でのキャストを回避します。
Viral Shah

5
ViralShah:数学演算を処理するときに、浮動小数点エラーが発生する可能性もあります。この演習の目的はそれを正確に回避することであることを考えると、変更するのが賢明なようです。
Samir Talwar

上記のSamir Talwarが述べた理由により、doubleの代わりにintを使用するように編集されました。
Viral Shah

3
この分数の実装は、最も単純な形式に縮小されないため、問題があります。2/3 * 1/2は、答えを1/3にしたい場合に2/6を与えます。理想的には、コンストラクタで分子と除数のgcdを見つけて、両方で除算する必要があります。
Salix alba 2014

15

精度が制限された10進算術を使用していて、1/3を処理したい場合、同じ問題があることに注意してください。0.333333333* 3は、1.00000000ではなく0.999999999です。

残念ながら、5.6、5.8、11.4は5分の1を含むため、2進数で丸めた数値ではありません。したがって、0.3333が正確に1/3ではないように、それらの浮動小数点表現は正確ではありません。

使用するすべての数値が非繰り返しの小数であり、正確な結果が必要な場合は、BigDecimalを使用します。または、他の人が言ったように、値がすべて0.01、0.001、または何かの倍数であるという意味で値がお金の場合、10の固定べき乗ですべてを乗算し、intまたはlongを使用します(加算と減算はささいなこと:乗算に注意してください)。

ただし、計算用のバイナリには満足しているが、少しわかりやすい形式で出力したい場合は、java.util.Formatterまたはを試してくださいString.format。フォーマット文字列では、倍精度の完全精度よりも低い精度を指定します。たとえば、有効桁数が10桁​​の場合、11.399999999999は11.4なので、2進数の結果が小数点以下の数桁しか必要としない値に非常に近い場合、結果はほぼ正確になり、人間が読めるようになります。

指定する精度は、数値を使用して行った計算の量に少し依存します。一般的に、実行するほどエラーが蓄積されますが、一部のアルゴリズムは他のアルゴリズムよりもはるかに速く蓄積します(「不安定」と呼ばれます)。丸め誤差に関する「安定」とは対照的です)。あなたがしているすべてがいくつかの値を追加することであるなら、私は小数点以下1桁の精度を落とすことで物事が整理されると思います。実験。


3
いいえ、金額と一緒にdoubleを使用しないでください!お金で精度が必要な場合は、代わりにBigDecimalを使用してください。そうでなければ、あなたの答えは良いです。精度が必要なものはすべてBigDecimalを使用します。精度がそれほど重要でない場合は、floatまたはdoubleを使用できます。
MetroidFan2002 2008年

1
質問はもはやお金が関与していると述べたり、示唆したりするものではありません。私は特にお金のためにBigDecimalまたは整数を使用すると言います。どうしたの?
スティーブジェソップ

1
そして、「お金のためにダブルを使用しないでください」と等しいのは、「BigDecimal または 3分の2を使用しないでください」です。しかし、時には問題が除算を伴う場合があり、その場合、すべての分母のすべての素因数で割り切れないすべての基底は、ほぼ同じくらい悪いです。
スティーブジェソップ

1
.9999 = 1は、精度が有効数字4桁未満の場合
Brian Leahy

9

精度の計算が本当に必要な場合は、javaのjava.math.BigDecimalクラスの使用を検討してください。BigDecimalのケースに関するOracle / Sunの優れた記事を次に示します。誰かが言及したように1/3を表すことはできませんが、、結果をどの程度正確にしたいかを正確に決定する力を持つます。setScale()はあなたの友達です.. :)

さて、現時点では手に余りにも時間がかかりすぎているので、ここにあなたの質問に関連するコード例があります:

import java.math.BigDecimal;
/**
 * Created by a wonderful programmer known as:
 * Vincent Stoessel
 * xaymaca@gmail.com
 * on Mar 17, 2010 at  11:05:16 PM
 */
public class BigUp {

    public static void main(String[] args) {
        BigDecimal first, second, result ;
        first = new BigDecimal("33.33333333333333")  ;
        second = new BigDecimal("100") ;
        result = first.divide(second);
        System.out.println("result is " + result);
       //will print : result is 0.3333333333333333


    }
}

そして、私の新しいお気に入りの言語であるGroovyをプラグインするために、同じもののより良い例を次に示します。

import java.math.BigDecimal

def  first =   new BigDecimal("33.33333333333333")
def second = new BigDecimal("100")


println "result is " + first/second   // will print: result is 0.33333333333333

5

それを3行の例にできたと思います。:)

正確な精度が必要な場合は、BigDecimalを使用します。それ以外の場合は、任意の精度でintに10を掛けた値^を使用できます。


5

他の人が指摘したように、10進数は10の累乗に基づいており、2進数は2の累乗に基づいているため、すべての10進数値を2進数として表すことができるわけではありません。

精度が重要な場合はBigDecimalを使用しますが、わかりやすい出力が必要な場合は、次のようにします。

System.out.printf("%.2f\n", total);

あなたに与えるでしょう:

11.40


5

7.3にはバイナリでの有限表現がないため、できません。あなたが得ることができる最も近いものは、2054767329987789/2 ** 48 = 7.3 + 1/1407374883553280です。

詳細については、http://docs.python.org/tutorial/floatingpoint.htmlご覧ください。(PythonのWebサイトにありますが、JavaとC ++には同じ「問題」があります。)

解決策は、問題の内容によって異なります。

  • これらのノイズの数字をすべて表示したくない場合は、文字列のフォーマットを修正してください。15桁を超える有効桁数(または浮動小数点数の場合は7桁)を表示しないでください。
  • 数値の不正確さが「if」ステートメントのようなものを壊している場合は、if(x == 7.3)ではなくif(abs(x-7.3)<TOLERANCE)と書く必要があります。
  • お金で作業している場合、おそらく本当に本当に必要なのは10進固定小数点です。セントの整数または通貨の最小単位が何であれ保存します。
  • (ほとんどありません)53桁を超える有効ビット(有効桁数15〜16桁)が必要な場合は、BigDecimalなどの高精度浮動小数点型を使用します。

7.3は、バイナリ有限表現を持っていないかもしれませんが、私はC ++で同じことを試してみたとき、私は必ずGET -7.3を行う
wrongusername

2
wrongusername:いいえ、ありません。そのように表示されるだけです。実際の答えを確認するには、「%。17g」形式(より良いのは「%.51g」)を使用してください。
dan04

4
private void getRound() {
    // this is very simple and interesting 
    double a = 5, b = 3, c;
    c = a / b;
    System.out.println(" round  val is " + c);

    //  round  val is  :  1.6666666666666667
    // if you want to only two precision point with double we 
            //  can use formate option in String 
           // which takes 2 parameters one is formte specifier which 
           // shows dicimal places another double value 
    String s = String.format("%.2f", c);
    double val = Double.parseDouble(s);
    System.out.println(" val is :" + val);
    // now out put will be : val is :1.67
}

3

java.math.BigDecimalを使用します

倍精度浮動小数点数は内部では2進分数であるため、小数部分を正確な10進数に表現できない場合があります。


1
BigDecimalを盲目的に推奨する場合は-1。実際に小数演算が必要ない場合(つまり、お金で計算している場合)、BigDecimalは役に立ちません。すべての浮動小数点エラーを解決するわけではありません。それでも、1/3 * 3 = 0.9999999999999999999999999999およびsqrt(2)** 2 = 1.999999999999999999999999999を処理する必要があります。さらに、BigDecimalは速度が非常に遅くなります。さらに悪いことに、Javaには演算子のオーバーロードがないため、すべてのコードを書き直す必要があります。
dan04 2010年

2
@ dan04-お金で計算を行う場合、なぜ固有のエラーを知っている浮動小数点表現を使用するのでしょうか。セントの端数が本当に必要な場合は、aa longを使用して数千セントを計算してください。さらに、OPは不合理な数について言及していませんでした。彼が心配したのは追加だけでした。投稿を注意深く読み、問題を理解してから回答してください。恥ずかしさを和らげることができます。
ニュートピア

3
@Newtopian:恥ずかしいことは何もありません。OPは行われませんNOお金の言及、また彼の問題は、任意の固有のdecimalnessを持っていることを任意の表示を。
dan04 2010年

@ dan04 -いいえOP ...あなたがやった盲目的に最も可能性の高い完全に受け入れ答えが用意細部の貧弱な量与えられたものにコンテキスト意見のうち提供されていなかった
Newtopian

2

すべてを100倍して、セント単位で保存します。


2
@Draemon-最後の編集の前の投稿を見てください-"shoppingTotal"と "calcGST"と "calcPST"のものはすべて私にはお金のように見えます。
Paul Tomblin、

2

コンピュータは数値をバイナリで格納し、実際には33.333333333や100.0などの数値を正確に表すことができません。これは、doubleを使用する場合の注意点の1つです。ユーザーに表示する前に、回答を丸める必要があります。幸い、ほとんどのアプリケーションでは、とにかくそれほど多くの小数点以下の桁数は必要ありません。


私は可能な限り最高の精度を持ちたいオッズ計算を行っています。しかし、私は制限があることを理解しています
Aly

2

浮動小数点数は、任意の特定の浮動小数点数に対して次に高い浮動小数点数があるという点で実数とは異なります。整数と同じ。1から2までの整数はありません。

1/3を浮動小数点数として表現する方法はありません。その下にフロートがあり、その上にフロートがあり、それらの間には一定の距離があります。そして1/3はその空間にあります。

Apfloat for Javaは任意精度の浮動小数点数で機能すると主張していますが、私はこれを使用したことがありません。おそらく一見の価値があります。 http://www.apfloat.org/apfloat_java/

Java浮動小数点高精度ライブラリの前に同様の質問がここで行われました


1

doubleは、Javaソースの10進数の近似値です。double(2進数でコード化された値)とソース(10進数でコード化された)の間の不一致の結果が表示されています。

Javaは最も近いバイナリ近似を生成します。java.text.DecimalFormatを使用して、見栄えのよい10進数値を表示できます。


1

BigDecimalを使用します。丸めルールを指定することもできます(ROUND_HALF_EVENのように、両方が同じ距離である場合、つまり、1.5と2.5の両方が2に丸められる場合に、偶数の隣に丸めることによって統計エラーを最小化します)。


1

短い回答:常にBigDecimalを使用し、コンストラクタを二重ではなく文字列引数で使用していることを確認してください。

例に戻ると、次のコードは、必要に応じて11.4を出力します。

public class doublePrecision {
    public static void main(String[] args) {
      BigDecimal total = new BigDecimal("0");
      total = total.add(new BigDecimal("5.6"));
      total = total.add(new BigDecimal("5.8"));
      System.out.println(total);
    }
}

0

BigDecimalを確認してください。このような浮動小数点演算を処理する問題を処理します。

新しい呼び出しは次のようになります。

term[number].coefficient.add(co);

使用する小数点以下の桁数を設定するには、setScale()を使用します。


0

Mathクラスのround()メソッドを使用しないのはなぜですか?

// The number of 0s determines how many digits you want after the floating point
// (here one digit)
total = (double)Math.round(total * 10) / 10;
System.out.println(total); // prints 11.4

0

double値を使用する以外に選択肢がない場合は、以下のコードを使用できます。

public static double sumDouble(double value1, double value2) {
    double sum = 0.0;
    String value1Str = Double.toString(value1);
    int decimalIndex = value1Str.indexOf(".");
    int value1Precision = 0;
    if (decimalIndex != -1) {
        value1Precision = (value1Str.length() - 1) - decimalIndex;
    }

    String value2Str = Double.toString(value2);
    decimalIndex = value2Str.indexOf(".");
    int value2Precision = 0;
    if (decimalIndex != -1) {
        value2Precision = (value2Str.length() - 1) - decimalIndex;
    }

    int maxPrecision = value1Precision > value2Precision ? value1Precision : value2Precision;
    sum = value1 + value2;
    String s = String.format("%." + maxPrecision + "f", sum);
    sum = Double.parseDouble(s);
    return sum;
}

-1

BigDecimalを使用して、努力を無駄にしないでください。99.99999%のケースでは必要ありません。java double型は、おおよその値ですが、ほとんどすべての場合、十分に正確です。14桁目でエラーがあることに注意してください。これはごくわずかです!

適切な出力を取得するには、次を使用します。

System.out.printf("%.2f\n", total);

2
数値の精度ではなく、出力が気になると思います。BigDecimalは役に立ちません。3で割ります。さらに事態を悪化させる可能性もあります...
Maciek D.

お金のために浮動小数点を決して使用してはいけません。私は、そのように指示されているにもかかわらず、この規則に違反した請負業者に強制される大規模な手直しを見てきました。
ローン侯爵
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.