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

时间:2008-11-05 12:01:06

标签: java floating-point decimal

目前我有这种方法:

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值,但是当我使用double立即格式化DecimalFormat时,我可以获得准确的表示:

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);

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

8 个答案:

答案 0 :(得分:6)

测试失败,因为您已达到二进制浮点表示的精度,大约是16位数IEEE754 double precision。乘以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 SkeetDoubleConverter来检查d不足以保存您要查找的小数位数的情况。

答案 1 :(得分:5)

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

  

不可变,任意精度签名   十进制数。 BigDecimal包含   一个任意精度整数   未缩放的值和32位整数   规模。如果为零或正,则为比例   是右边的位数   小数点。如果是否定的话   未缩放的数字值是   乘以十来得到的力量   否定规模。的价值   由...表示的数字   因此BigDecimal(unscaledValue   ×10比例)。

scale可以通过setScale(int)

设置

答案 2 :(得分:3)

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

如果替换:

return (d==check);

类似

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

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

答案 6 :(得分: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);
 }

答案 7 :(得分:0)

也许会有所帮助。这是我的方法

private int checkPrecisionOfDouble(Double atribute) {
    String s = String.valueOf(atribute);
    String[] split = s.split("\\.");
    return split[1].length();
}

private boolean checkPrecisionOfDouble(Double atribute, int decimalPlaces) {
    String s = String.valueOf(atribute);
    String[] split = s.split("\\.");

    return split[1].length() == decimalPlaces;
}