doubleの小数点以下の桁数が最大nであるかどうかを確認するにはどうすればよいですか?
-
06-07-2019 - |
質問
現在、私はこのメソッドを持っています:
static boolean checkDecimalPlaces(double d, int decimalPlaces){
if (d==0) return true;
double multiplier = Math.pow(10, decimalPlaces);
double check = d * multiplier;
check = Math.round(check);
check = check/multiplier;
return (d==check);
}
ただし、このメソッドは checkDecmialPlaces(649632196443.4279、4)
で失敗します。おそらく、2を基数とする10を基数計算するためです。
では、このチェックをどのように正しく行うことができますか?
double値の文字列表現を取得し、正規表現で確認することを考えましたが、それは奇妙に感じました。
編集: すべての答えをありがとう。本当に倍になる場合がありますが、その場合は以下を実装しました:
private static boolean checkDecimalPlaces(double d, int decimalPlaces) {
if (d == 0) return true;
final double epsilon = Math.pow(10.0, ((decimalPlaces + 1) * -1));
double multiplier = Math.pow(10, decimalPlaces);
double check = d * multiplier;
long checkLong = (long) Math.abs(check);
check = checkLong / multiplier;
double e = Math.abs(d - check);
return e < epsilon;
}
round
を切り捨てに変更しました。 round
で行われた計算が不正確さを大幅に増加させているようです。少なくとも失敗したテストケースでは。
「実際の」文字列入力に到達できるかどうかを指摘したように、 BigDecimal
を使用して確認する必要があるため、次のようにしました。
BigDecimal decimal = new BigDecimal(value);
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces);
return checkDecimal.scale() == 0;
取得する double
の値は、Excelファイルを読み取るApache POI APIから取得されます。いくつかのテストを行ったところ、数値セルのAPIは double
値を返しますが、 DecimalFormatで
: double
をすぐにフォーマットすると正確な表現が得られることがわかりました。
DecimalFormat decimalFormat = new DecimalFormat();
decimalFormat.setMaximumIntegerDigits(Integer.MAX_VALUE);
// don't use grouping for numeric-type cells
decimalFormat.setGroupingUsed(false);
decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
value = decimalFormat.format(numericValue);
これは、バイナリ形式で正確に表現できない値に対しても機能します。
解決
IEEE754倍精度。 649632196443.4279に10000を掛けると、バイナリ表現が切り捨てられ、その後に丸めて除算するときにエラーが発生し、関数の結果が完全に無効になります。
詳細については、 http://en.wikipedia.org/wiki/Floating_point#Accuracy_problemsをご覧ください。
より良い方法は、 n + 1
の小数点以下の桁数が特定のしきい値を下回っているかどうかを確認することです。 d-round(d)
が epsilon
( limit を参照)、の10進表現d
には小数点以下の桁がありません。同様に、(d-round(d))* 10 ^ n
が epsilon
より小さい場合、dは最大で n
の重要な位置を持ちます。
Jon Skeet の DoubleConverter
を使用して、 d
が小数点以下の桁数を保持するのに十分でない場合を確認します。探しています。
他のヒント
目標が小数の右側に正確に n 有効数字を持つ数字を表すことである場合、 BigDecimal は使用するクラスです。
不変、任意精度の符号付き 10進数。 BigDecimalは 任意精度整数の スケールなしの値と32ビット整数 規模。ゼロまたは正の場合、スケール は右側の桁数です 小数点の。負の場合、 数値のスケールなしの値は 10のべき乗 スケールの否定。の値 によって表される数 したがって、BigDecimalは(unscaledValue &#215; 10スケール)。
scale
は、 setScale(int)
すべての浮動小数点演算と同様に、等価性をチェックするのではなく、エラー(イプシロン)が十分に小さいことを確認してください。
交換する場合:
return (d==check);
次のようなもの
return (Math.abs(d-check) <= 0.0000001);
動作するはずです。明らかに、チェックする小数の数と比較して十分に小さいイプシロンを選択する必要があります。
double
タイプは、2進浮動小数点数です。それらを10進浮動小数点数であるかのように扱うことには、常に明らかな不正確さがあります。あなたが望むように機能するようにあなたの関数を書くことができるようになることを私は知りません。
重要な場合は、元の数値のソース(おそらく文字列入力)に戻り、10進数表現を保持する必要があります。
BigDecimalに切り替えることができる場合は、Ken Gが説明しているように、それを使用する必要があります。
そうでない場合は、他の回答で述べたように、多くの問題に対処する必要があります。私には、2進数(double)を扱っており、その数値の10進数表現について質問しています。つまり、文字列について尋ねています。あなたの直感は正しいと思います。
これが一般的に本当に実行可能かどうかはわかりません。たとえば、 1.0e-13
の小数点以下の桁数はいくつですか?算術中の丸め誤差が原因で、実際には 0
が偽装されている場合はどうなりますか?オンの場合、一方で、最初の n 小数点以下にゼロ以外の数字があるかどうかを尋ねています:
static boolean checkDecimalPlaces(double d, unsigned int decimalPlaces){
// take advantage of truncation, may need to use BigInt here
// depending on your range
double d_abs = Math.abs(d);
unsigned long d_i = d_abs;
unsigned long e = (d_abs - d_i) * Math.pow(10, decimalPlaces);
return e > 0;
}
これはもっと良いと思う 文字列に変換し、指数の値を調べます
public int calcBase10Exponet (Number increment)
{
//toSting of 0.0=0.0
//toSting of 1.0=1.0
//toSting of 10.0=10.0
//toSting of 100.0=100.0
//toSting of 1000.0=1000.0
//toSting of 10000.0=10000.0
//toSting of 100000.0=100000.0
//toSting of 1000000.0=1000000.0
//toSting of 1.0E7=1.0E7
//toSting of 1.0E8=1.0E8
//toSting of 1.0E9=1.0E9
//toSting of 1.0E10=1.0E10
//toSting of 1.0E11=1.0E11
//toSting of 0.1=0.1
//toSting of 0.01=0.01
//toSting of 0.0010=0.0010 <== need to trim off this extra zero
//toSting of 1.0E-4=1.0E-4
//toSting of 1.0E-5=1.0E-5
//toSting of 1.0E-6=1.0E-6
//toSting of 1.0E-7=1.0E-7
//toSting of 1.0E-8=1.0E-8
//toSting of 1.0E-9=1.0E-9
//toSting of 1.0E-10=1.0E-10
//toSting of 1.0E-11=1.0E-11
double dbl = increment.doubleValue ();
String str = Double.toString (dbl);
// System.out.println ("NumberBoxDefaultPatternCalculator: toSting of " + dbl + "=" + str);
if (str.contains ("E"))
{
return Integer.parseInt (str.substring (str.indexOf ("E") + 1));
}
if (str.endsWith (".0"))
{
return str.length () - 3;
}
while (str.endsWith ("0"))
{
str = str.substring (0, str.length () - 1);
}
return - (str.length () - str.indexOf (".") - 1);
}