Как проверить, имеет ли двойное число не более n десятичных знаков? - PullRequest
9 голосов
/ 05 ноября 2008

В настоящее время у меня есть этот метод:

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-интерфейса Apache POI, который читает файлы 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 ]

6 голосов
/ 05 ноября 2008

Тест не пройден, потому что вы достигли точности двоичного представления с плавающей запятой, которое составляет приблизительно 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 s DoubleConverter, чтобы проверить случаи, когда d не достаточно точен для хранения десятичных знаков, которые вы ищете.

5 голосов
/ 05 ноября 2008

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

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

scale можно установить с помощью setScale (int)

3 голосов
/ 05 ноября 2008

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

Если вы замените:

return (d==check);

с чем-то вроде

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

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

1 голос
/ 06 ноября 2008

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

Если нет, то вам придется иметь дело с множеством вопросов, упомянутых в других ответах. Для меня вы имеете дело с двоичным числом (double) и задаете вопрос о десятичном представлении этого числа; то есть вы спрашиваете о строке. Я думаю, что ваша интуиция верна.

1 голос
/ 05 ноября 2008

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

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

0 голосов
/ 07 января 2011

я думаю это лучше Преобразовать в строку и запросить значение для показателя степени

 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);
 }
0 голосов
/ 05 ноября 2008

Я не уверен, что это действительно выполнимо в целом. Например, сколько десятичных знаков имеет 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;
   }
...