Как проверить, имеет ли 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) вероятно, потому, что я делаю математику базы 10 на базе 2 числа.

Итак, как эта проверка может быть выполнена правильно?

я думал получить строковое представление двойного значения, а затем проверить это с помощью регулярного выражения - но это было странно.

EDIT: Спасибо за ответы. Есть случаи, когда я действительно получаю двойной и для этих случаев Я реализовал следующее:

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 значение, которое я получаю, исходит из API POI Apache, который читает файлы excel. Я сделал несколько тестов и выяснил, что, хотя 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);

это также работает для значений, которые не могут быть точно представлены в двоичном формате.

7 ответов


тест терпит неудачу, потому что вы достигли точности двоичного представления с плавающей запятой, которое составляет приблизительно 16 цифр с IEEE754 двойная точность. Умножение на 649632196443.4279 на 10000 усечет двоичное представление, что приведет к ошибкам при округлении и делении впоследствии, тем самым полностью аннулируя результат вашей функции.

Подробнее см. http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

лучшим способом было бы проверить, есть ли n+1 десятичные знаки ниже определенного порога. Если d - round(d) меньше epsilon (см. ограничения), десятичное представление d не имеет значимых знаков после запятой. Аналогично, если (d - round(d)) * 10^n меньше epsilon, D может иметь не более n значимые места.

использовать Джон СкитDoubleConverter для проверки случаев, когда d недостаточно точно, чтобы удерживать десятичные знаки, которые вы ищете.


Если ваша цель состоит в том, чтобы представить число с точно n значимые цифры справа от десятичной дроби,BigDecimal - это класс.

неизменяемый, произвольно-прецизионный знак десятичное число. В bigdecimal состоит из целого числа произвольной точности немасштабируемое значение и 32-разрядное целое число масштаб. Если ноль или положительный, шкала - число цифр справа десятичной запятой. Если отрицательный, то unscaled значение номер умножить на десять в силу отрицание масштаба. Значение число, представленное Поэтому BigDecimal является (unscaledValue × 10-шкала).

scale можно установить через setScale (int)


Как и во всей арифметике с плавающей запятой, вы не должны проверять равенство, а скорее, что ошибка (epsilon) достаточно мала.

Если заменить:

return (d==check);

что-то вроде

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

он должен работать. Очевидно, что Эпсилон должен быть выбран достаточно маленьким по сравнению с количеством десятичных знаков, которые вы проверяете.


на double type-двоичное число с плавающей запятой. Всегда есть очевидные неточности в обращении с ними, как если бы они были десятичными числами с плавающей запятой. Я не знаю, сможете ли вы когда-нибудь написать свою функцию так, чтобы она работала так, как вы хотите.

вам, вероятно, придется вернуться к исходному источнику числа (возможно, строковый ввод) и сохранить десятичное представление, если это важно для вас.


Если вы можете переключиться на BigDecimal, то, как объясняет Кен Г, это то, что вы должны использовать.

Если нет, то вам придется иметь дело с множеством вопросов, как указано в других ответах. Для меня Вы имеете дело с двоичным числом (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);
 }