11

目前我有这个方法:

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 为基数的数学运算。

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

我想得到一个双精度值的字符串表示,然后用正则表达式检查它——但这感觉很奇怪。

编辑: 感谢所有的答案。在某些情况下,我真的得到了双倍的回报,对于这些情况,我实施了以下措施:

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格式对其进行格式化时,我可以获得准确的表示: doubleDecimalFormat

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

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

4

8 回答 8

6

如果您的目标是用小数点右侧正好n 个有效数字来表示一个数字,那么BigDecimal就是要使用的类。

不可变的、任意精度的有符号十进制数。BigDecimal 由一个任意精度的整数未缩放值和一个 32 位整数刻度组成。如果为零或正数,则刻度是小数点右侧的位数。如果为负数,则将数字的未缩放值乘以 10 的负数次方。因此 BigDecimal 表示的数字的值是 (unscaledValue × 10-scale)。

scale可以通过setScale(int)设置

于 2008-11-05T12:15:21.737 回答
6

测试失败,因为您已达到二进制浮点表示的精度,大约 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 SkeetDoubleConverter检查那些d不够准确以保持您正在寻找的小数位的情况。

于 2008-11-05T13:07:09.713 回答
3

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

如果您更换:

return (d==check);

有类似的东西

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

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

于 2008-11-05T12:04:26.860 回答
1

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

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

于 2008-11-05T12:06:05.670 回答
1

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

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

于 2008-11-05T21:12:27.737 回答
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;
   }
于 2008-11-05T12:31:49.377 回答
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);
 }
于 2011-01-07T13:07:36.607 回答
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;
}
于 2019-08-01T12:29:07.627 回答