Javaでdoubleを使用して精度を保持する
-
11-07-2019 - |
質問
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で印刷(または使用)するにはどうすればよいですか?
解決
他の人が述べたように、おそらく BigDecimal
クラス、11.4の正確な表現が必要な場合
今、なぜこれが起こっているのかについての簡単な説明:
Javaのfloat
およびdouble
プリミティブ型は、浮動小数点番号です。ここで、数値は、分数と指数のバイナリ表現として保存されます。
より具体的には、byte
型などの倍精度浮動小数点値は64ビット値です。ここで、
- 1ビットは符号(正または負)を示します。
- 11ビットの指数。
- 有効数字用の52ビット(バイナリとしての小数部)。
これらの部分を組み合わせて、値のchar
表現を生成します。
(出典:ウィキペディア:倍精度)
Javaでの浮動小数点値の処理方法の詳細については、セクション4.2.3:Java言語仕様の浮動小数点型、形式、および値。
int
、long
、11.399999999999
、5.6 + 5.8
タイプは fixed-ポイント、これは数字の正確な表現です。固定小数点数とは異なり、浮動小数点数は(<!> quot;ほとんどの場合<!> quot;と想定しても安全です)数値の正確な表現を返せないことがあります。これが、<=>の結果として<=>になってしまう理由です。
1.5や150.1005などの正確な値が必要な場合は、数値を正確に表すことができる固定小数点型のいずれかを使用する必要があります。
すでに何度か言及されているように、Javaには <=> クラスは、非常に大きな数と非常に小さな数を処理します。
<=>クラスのJava APIリファレンスから:
不変、 任意精度の符号付き10進数 数字。 BigDecimalは、 スケールなしの任意精度整数 値と32ビット整数スケール。もし ゼロまたは正、スケールは の右側の桁数 小数点。負の場合、 数値のスケールなしの値は 10のべき乗 スケールの否定。の値 によって表される数 したがって、BigDecimalは(unscaledValue <!>#215; 10 ^ -scale)。
浮動小数点数とその精度の問題に関して、スタックオーバーフローに関する多くの質問がありました。関連する質問のリストを次に示します。
- 21.4のような値に初期化されたdouble変数が21.399999618530273として表示されるのはなぜですか?
- 本当に大きな数字をC ++で印刷する方法
- 浮動小数点はどのように保存されますか?重要なのはいつですか?
- 会計アプリケーションのドル金額に浮動小数点または小数を使用しますか?
浮動小数点数の細部について詳しく知りたい場合は、すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと。
他のヒント
たとえば、33.33333333333333
などの倍数を入力すると、実際に得られる値は、最も近い表現可能な倍精度値であり、正確には次のとおりです。
33.3333333333333285963817615993320941925048828125
100で割ると、次のようになります。
0.333333333333333285963817615993320941925048828125
これも倍精度の数値として表現できないため、やはり最も近い表現可能な値に丸められます。正確には次のとおりです。
0.3333333333333332593184650249895639717578887939453125
この値を出力すると、次のようにまだ小数点以下17桁に丸められます:
0.33333333333333326
値を分数として処理するだけの場合は、分子と分母のフィールドを保持する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);
}
}
}
精度が制限された10進数演算を使用し、1/3を処理したい場合も同じ問題が発生することに注意してください。0.333333333* 3は1.00000000ではなく0.999999999です。
残念ながら、5.6、5.8、および11.4は、5桁を含むため、2進数の丸め数値ではありません。したがって、0.3333が正確に1/3ではないように、それらの浮動小数点表現は正確ではありません。
使用する数値がすべて非繰り返し小数であり、正確な結果が必要な場合は、BigDecimalを使用します。または、他の人が言ったように、値が0.01または0.001などの倍数であるという意味でお金のようなものである場合、すべてを10の固定累乗で乗算し、intまたはlongを使用します(加算と減算は簡単:乗算に注意してください)。
ただし、計算用のバイナリに満足しているが、少しわかりやすい形式で印刷したい場合は、java.util.Formatter
またはString.format
を試してください。書式文字列では、doubleの完全精度よりも低い精度を指定します。有効数字10桁まで、たとえば11.399999999999は11.4であるため、バイナリ結果が小数点以下数桁のみを必要とする値に非常に近い場合、結果はほぼ正確で人間が読めるようになります。
指定する精度は、数値を使用して計算した量に少し依存します-一般に、実行するほどエラーが累積しますが、一部のアルゴリズムは他のアルゴリズムよりもはるかに速く累積します(< !> quot; unstable <!> quot;丸め誤差に関して<!> quot; stable <!> quot;とは対照的に)。いくつかの値を追加するだけであれば、小数点以下1桁の精度を落とすだけで問題は解決すると思います。実験。
本当に正確な数学が必要な場合は、javaのjava.math.BigDecimalクラスの使用を検討してください。 Oracle / Sunの BigDecimalの事例に関する良い記事があります。誰かが言及したように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
3行の例にできたことを確認してください。 :)
正確な精度が必要な場合は、BigDecimalを使用します。それ以外の場合は、10倍したintを使用できます。^精度は任意です。
他の人が指摘したように、10進数は10の累乗に基づいており、バイナリは2の累乗に基づいているため、すべての10進値がバイナリとして表現できるわけではありません。
精度が重要な場合はBigDecimalを使用しますが、わかりやすい出力が必要な場合:
System.out.printf("%.2f\n", total);
次のものを提供します:
11.40
double型の精度制限に直面しています。
Java.Mathには、任意精度の演算機能がいくつかあります。
できません。7.3にはバイナリ形式の有限表現がないためです。最も近いものは2054767329987789/2 ** 48 = 7.3 + 1/1407374883553280です。
解決策は、問題の内容によって異なります。
- これらのすべてのノイズディジットを見るのが嫌な場合は、文字列の書式を修正します。有効数字を15桁(フロートの場合は7桁)以上表示しないでください。
- 数字の不正確さが<!> quot; if <!> quotのようなものを壊している場合ステートメント、それからif(abs(x-7.3)<!> lt; TOLERANCE)if(x == 7.3)の代わりに記述する必要があります。
- お金で作業している場合、おそらく本当に必要なのは10進固定小数点です。セントの整数または通貨の最小単位を格納します。
- (非常に珍しい)53ビット(15-16有効桁)を超える精度が必要な場合は、BigDecimalなどの高精度浮動小数点型を使用します。
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
}
java.math.BigDecimalを使用
doubleは内部的には2進小数であるため、小数を正確な小数に表すことができない場合があります。
すべてを100で乗算し、セント単位で保存します。
コンピューターは数値をバイナリで保存し、実際には33.333333333や100.0などの数値を正確に表すことはできません。これは、doubleの使用に関する厄介なことの1つです。ユーザーに表示する前に答えを丸める必要があります。幸いなことに、ほとんどのアプリケーションでは、それほど多くの小数点以下の桁数は必要ありません。
浮動小数点数は、任意の浮動小数点数に対して次に高い浮動小数点数があるという点で実数と異なります。整数と同じ。 1〜2の整数はありません。
1/3を浮動小数点数として表す方法はありません。その下にフロートがあり、その上にフロートがあり、それらの間には一定の距離があります。そして、1/3はそのスペースにあります。
Apfloat for Javaは、任意精度の浮動小数点数で動作すると主張していますが、私はそれを使用したことがありません。おそらく一見の価値があります。 http://www.apfloat.org/apfloat_java/
同様の質問が以前ここで尋ねられました Java浮動小数点高精度ライブラリ
doubleは、Javaソースの10進数の近似値です。 double(バイナリコード化された値)とソース(decimalコード化された)の不一致の結果が表示されています。
Javaは最も近いバイナリ近似を生成します。 java.text.DecimalFormatを使用して、見栄えの良い10進数値を表示できます。
BigDecimalを使用します。丸めルールを指定することもできます(ROUND_HALF_EVENなど、両方が同じ距離の場合、偶数隣に丸めることで統計誤差を最小限に抑えます。つまり、1.5と2.5の両方が2に丸められます)。
BigDecimalをチェックして、そのような浮動小数点演算を扱う問題を処理します。
新しい呼び出しは次のようになります。
term[number].coefficient.add(co);
setScale()を使用して、使用する小数点以下の桁数の精度を設定します。
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
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;
}
BigDecimalを使用して無駄にしないでください。 99.99999%の場合、必要ありません。 java double タイプは近似値ですが、ほとんどすべての場合、十分に正確です。 14桁目でエラーがあることに注意してください。 これは本当に無視できます!
見事な出力を得るには:
System.out.printf("%.2f\n", total);