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

时间:2021-03-27 11:17:14

Currently i have this method:

目前我有这个方法:

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

But this method fails for checkDecmialPlaces(649632196443.4279, 4) probably because I do base 10 math on a base 2 number.

但是这个方法对于checkDecmialPlaces(649632196443.4279,4)来说是失败的,可能是因为我在基数为2的数字上进行了10次数学运算。

So how can this check be done correctly?

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

I thought of getting a string representation of the double value and then check that with a regexp - but that felt weird.

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

EDIT: Thanks for all the answers. There are cases where I really get a double and for those cases I implemented the following:

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

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

I changed the round to a truncation. Seems that the computation done in round increases the inaccuracy too much. At least in the failing testcase.
As some of you pointed out if I could get to the 'real' string input I should use BigDecimal to check and so I have done:

我把这一轮变成了截断。似乎圆形计算过多会增加不准确性。至少在失败的测试用例中。正如你们中的一些人指出我是否可以使用“真正的”字符串输入我应该使用BigDecimal来检查,所以我做了:

BigDecimal decimal = new BigDecimal(value);
BigDecimal checkDecimal = decimal.movePointRight(decimalPlaces);
return checkDecimal.scale() == 0;

The double value I get comes from the Apache POI API that reads excel files. I did a few tests and found out that although the API returns double values for numeric cells I can get a accurate representation when I immediately format that double with the DecimalFormat:

我获得的双重值来自Apache POI API,它读取excel文件。我做了一些测试,发现尽管API返回数值单元格的双倍值,但是当我使用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);

This also works for values that can't be represented exactly in binary format.

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

7 个解决方案

#1


6  

The test fails, because you have reached the accuracy of the binary floating point representation, which is approximately 16 digits with IEEE754 double precision. Multiplying by 649632196443.4279 by 10000 will truncate the binary representation, leading to errors when rounding and dividing afterwards, thereby invalidating the result of your function completely.

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

For more details see http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

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

A better way would be to check whether the n+1 decimal places are below a certain threshold. If d - round(d) is less than epsilon (see limit), the decimal representation of d has no significant decimal places. Similarly if (d - round(d)) * 10^n is less than epsilon, d can have at most n significant places.

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

Use Jon Skeet's DoubleConverter to check for the cases where d isn't accurate enough to hold the decimal places you are looking for.

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

#2


5  

If your goal is to represent a number with exactly n significant figures to the right of the decimal, BigDecimal is the class to use.

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

Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).

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

scale can be set via setScale(int)

可以通过setScale(int)设置比例

#3


3  

As with all floating point arithmetic, you should not check for equality, but rather that the error (epsilon) is sufficiently small.

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

If you replace:

如果你更换:

return (d==check);

with something like

有类似的东西

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

it should work. Obviously, the epsilon should be selected to be small enough compared with the number of decimals you're checking for.

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

#4


1  

The double type is a binary floating point number. There are always apparent inaccuracies in dealing with them as if they were decimal floating point numbers. I don't know that you'll ever be able to write your function so that it works the way you want.

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

You will likely have to go back to the original source of the number (a string input perhaps) and keep the decimal representation if it is important to you.

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

#5


1  

If you can switch to BigDecimal, then as Ken G explains, that's what you should be using.

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

If not, then you have to deal with a host of issues as mentioned in the other answers. To me, you are dealing with a binary number (double) and asking a question about a decimal representation of that number; i.e., you are asking about a String. I think your intuition is correct.

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

#6


0  

I'm not sure that this is really doable in general. For example, how many decimal places does 1.0e-13 have? What if it resulted from some rounding error while doing arithmetic and really is just 0 in disguise? If on, the other hand you are asking if there are any non-zero digits in the first n decimal places you can do something like:

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

#7


0  

I think this is better Convert to string and interrogate the value for the exponent

我认为这更好转换为字符串并询问指数的值

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

#1


6  

The test fails, because you have reached the accuracy of the binary floating point representation, which is approximately 16 digits with IEEE754 double precision. Multiplying by 649632196443.4279 by 10000 will truncate the binary representation, leading to errors when rounding and dividing afterwards, thereby invalidating the result of your function completely.

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

For more details see http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

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

A better way would be to check whether the n+1 decimal places are below a certain threshold. If d - round(d) is less than epsilon (see limit), the decimal representation of d has no significant decimal places. Similarly if (d - round(d)) * 10^n is less than epsilon, d can have at most n significant places.

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

Use Jon Skeet's DoubleConverter to check for the cases where d isn't accurate enough to hold the decimal places you are looking for.

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

#2


5  

If your goal is to represent a number with exactly n significant figures to the right of the decimal, BigDecimal is the class to use.

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

Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).

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

scale can be set via setScale(int)

可以通过setScale(int)设置比例

#3


3  

As with all floating point arithmetic, you should not check for equality, but rather that the error (epsilon) is sufficiently small.

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

If you replace:

如果你更换:

return (d==check);

with something like

有类似的东西

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

it should work. Obviously, the epsilon should be selected to be small enough compared with the number of decimals you're checking for.

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

#4


1  

The double type is a binary floating point number. There are always apparent inaccuracies in dealing with them as if they were decimal floating point numbers. I don't know that you'll ever be able to write your function so that it works the way you want.

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

You will likely have to go back to the original source of the number (a string input perhaps) and keep the decimal representation if it is important to you.

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

#5


1  

If you can switch to BigDecimal, then as Ken G explains, that's what you should be using.

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

If not, then you have to deal with a host of issues as mentioned in the other answers. To me, you are dealing with a binary number (double) and asking a question about a decimal representation of that number; i.e., you are asking about a String. I think your intuition is correct.

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

#6


0  

I'm not sure that this is really doable in general. For example, how many decimal places does 1.0e-13 have? What if it resulted from some rounding error while doing arithmetic and really is just 0 in disguise? If on, the other hand you are asking if there are any non-zero digits in the first n decimal places you can do something like:

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

#7


0  

I think this is better Convert to string and interrogate the value for the exponent

我认为这更好转换为字符串并询问指数的值

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