如何检查double是否最多有n个小数位?

目前我有这个方法:

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)是失败的checkDecmialPlaces(649632196443.4279, 4)可能是因为我在基数为2的数字上进行了10次数学运算。

那么如何才能正确完成这项检查呢?

我想到获得double值的字符串表示,然后用regexp检查 – 但这感觉很奇怪。

编辑:谢谢你的所有答案。 在某些情况下,我真的得到了一个双倍,在这些情况下,我实现了以下内容:

 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值来自Apache POI API,它读取excel文件。 我做了一些测试,发现尽管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); 

这也适用于无法以二进制格式精确表示的值。

测试失败,因为您已达到二进制浮点表示的精度,大约是16位数,具有IEEE754双精度 。 乘以649632196443.4279乘以10000会截断二进制表示,导致后续舍入和除法时出错,从而使函数的结果完全失效。

有关详细信息,请参阅http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

更好的方法是检查n+1个小数位是否低于某个阈值。 如果d - round(d)小于epsilon (参见limit ),则d的十进制表示没有显着的小数位。 类似地,如果(d - round(d)) * 10^n小于epsilon ,则d可以具有至多n重要位置。

使用Jon Skeet的DoubleConverter来检查d不够精确以保存您要查找的小数位数的情况。

如果您的目标是在小数点右侧表示一个具有正确n个有效数字的数字,则BigDecimal是要使用的类。

不可变的,任意精度的带符号十进制数。 BigDecimal由任意精度整数非标度值和32位整数标度组成。 如果为零或正数,则比例是小数点右侧的位数。 如果是负数,则将数字的未缩放值乘以10来表示比例的否定。 因此,BigDecimal表示的数字的值是(unscaledValue×10-scale)。

可以通过setScale(int)设置scale

与所有浮点运算一样,您不应检查相等性,而应检查错误(epsilon)是否足够小。

如果你更换:

 return (d==check); 

有类似的东西

 return (Math.abs(d-check) <= 0.0000001); 

它应该工作。 显然,与您要检查的小数位数相比,应该选择足够小的epsilon。

double类型是二进制浮点数。 在处理它们时总是存在明显的不准确性,就像它们是十进制浮点数一样。 我不知道你将能够编写你的function,以便它按照你想要的方式工作。

您可能必须返回到数字的原始来源(可能是字符串输入)并保留小数表示(如果它对您很重要)。

如果你可以切换到BigDecimal,那么正如Ken G所解释的那样,那就是你应该使用的东西。

如果没有,那么你必须处理其他答案中提到的一系列问题。 对我来说,你正在处理二进制数(double)并询问有关该数字的十进制表示的问题; 即,你问的是一个字符串。 我认为你的直觉是正确的。

我不确定这一般是否真的可行。 例如, 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); }