2015-04-16 5 views
3

Когда я выполнить следующий код:Java DecimalFormat потери точности при форматировании двойной

public class Test { 
    public static void main(String args[]){ 
     DecimalFormat format = new DecimalFormat(); 
     Double value = new Double(-1350825904190559999913623552.00); 

     StringBuffer buffer = new StringBuffer(); 
     FieldPosition position = new FieldPosition(0); 
     format.format(new BigDecimal(value), buffer, position); 
     System.out.println(buffer); 
    } 
} 

Это правильно печатает -1,350,825,904,190,559,999,913,623,552. У меня есть код, который проходит через много двойников, поэтому я не хочу, чтобы преобразование из double в bigdecimal. Я понял, что время обработки BigDecimal велико. Так что я делаю format.format(value, buffer, position) И я вижу, что точность потеряна. Выход я получаю -1,350,825,904,190,560,000,000,000,000.

Что я здесь делаю неправильно? Есть ли лучший способ справиться с этим и сохранить точность. Я не хочу иметь дело с BigDecimals, но просто работаю с десятичными знаками.

Любые предложения?

+0

может быть, имеет смысл размещать код, который обеспечивает неправильные результаты тоже? –

+0

Вы уверены, что это действительно имеет значение? Если у вас есть «Double value = new Double (0.1)», вы хотите увидеть полную точность (что «0,1000000000000000055511151231257827021181583404541015625')? – user2357112

+0

@ user2357112 umm im, говорящий о больших количествах, таких как 1350825904190559999913623552.00 здесь. Так что да, это имеет значение. –

ответ

2

double не имеет бесконечную точность, и вы не можете получить более высокую точность, чем double имеет путем преобразования double в BigDecimal (как вы не можете получить более высокую точность с int, когда вы делаете double r = 1/3; который 0.0 потому что он расширяет a int до double). Вместо этого вы можете использовать String. Что-то вроде

DecimalFormat format = new DecimalFormat(); 
String value = "-1350825904190559999913623552.00"; 
System.out.println(format.format(new BigDecimal(value))); 
+0

Но я думаю, что точность во время форматирования теряется. Если точность была утеряна при создании двойника, почему конвертируется в bigdecimal, сохраняя ее. Таким образом, он не попадает в тот же случай, что и «вы не можете получить больше точности, чем двойной, если преобразовать double в BigDecimal», не так ли? –

+0

Вы сказали «много парных». Что такое 0,1 + 0,2? –

0

Вы не можете представить 1350825904190559999913623552.00 точно с Double. Если вы хотите узнать, почему, изучите это article.

Если вы хотите представить это значение, я бы посоветовал использовать код, который вы использовали в своем вопросе: new BigDecimal(value), где value - это фактически String.

+0

Некоторые, но не все, цифры между 2^53 и Double.MAX_VALUE являются точно представимыми, и это кажется мне одним из них. –

+0

http://en.wikipedia.org/wiki/Floating_point#IEEE_754%3a_floating_point_in_modern_computers – Zyn

+1

Я знаком с этой веб-страницей, и в ней нет ничего, что противоречит утверждению о том, что некоторые большие числа точно представляются, и это один из их. Вы можете проверить это с помощью конвертера [Decimal to Floating-Point Converter] (http://www.exploringbinary.com/floating-point-converter/) –

1

Он не потерян во время форматирования. Он потерял здесь:

Double value = new Double(-1350825904190559999913623552.00); 

double имеет только около 15,9 значащих десятичных цифр. Это не подходит. Во время компиляции была прецизионная потеря, когда был преобразован литерал с плавающей запятой.

1

Проблема заключается в форматировании вывода, в частности о том, как удвоения преобразуются в строки по умолчанию. Каждое двойное число имеет точное значение, но оно также является результатом преобразования строки в двойное преобразование для диапазона десятичных дробей. В этом случае точное значение двойного значения составляет -1350825904190559999913623552, но диапазон составляет [-1350825904190560137352577024, -1350825904190559862474670080].

Преобразование Double toString выбирает номер из этого диапазона с наименьшими значащими цифрами, -1.35082590419056E27. Эта строка преобразует обратно в исходное значение.

Если вы действительно хотите увидеть точное значение, а не просто цифры, чтобы однозначно идентифицировать двойной, ваш текущий подход BigDecimal работает хорошо.

Вот программа, которую я использовал для расчета чисел в этом ответе:

import java.math.BigDecimal; 

public class Test { 
    public static void main(String args[]) { 
    double value = -1350825904190559999913623552.00; 
    /* Get an exact printout of the double by conversion to BigDecimal 
    * followed by BigDecimal output. Both those operations are exact. 
    */ 
    BigDecimal bdValue = new BigDecimal(value); 
    System.out.println("Exact value: " + bdValue); 
    /* Determine whether the range is open or closed. The half way 
    * points round to even, so they are included in the range for a number 
    * with an even significand, but not for one with an odd significand. 
    */ 
    boolean isEven = (Double.doubleToLongBits(value) & 1) == 0; 
    /* Find the lower bound of the range, by taking the mean, in 
    * BigDecimal arithmetic for exactness, of the value and the next 
    * exactly representable value in the negative infinity direction. 
    */ 
    BigDecimal nextDown = new BigDecimal(Math.nextAfter(value, 
     Double.NEGATIVE_INFINITY)); 
    BigDecimal lowerBound = bdValue.add(nextDown).divide(BigDecimal.valueOf(2)); 
    /* Similarly, find the upper bound of the range by going in the 
    * positive infinity direction. 
    */ 
    BigDecimal nextUp = new BigDecimal(Math.nextAfter(value, 
     Double.POSITIVE_INFINITY)); 
    BigDecimal upperBound = bdValue.add(nextUp).divide(BigDecimal.valueOf(2)); 
    /* Output the range, with [] if closed,() if open.*/ 
    System.out.println("Range: " + (isEven ? "[" : "(") + lowerBound + "," 
     + upperBound + (isEven ? "]" : ")")); 
    /* Output the result of applying Double's toString to the value.*/ 
    String valueString = Double.toString(value); 
    System.out.println("toString result: " + valueString); 
    /* And use BigDecimal as above to print the exact value of the result 
    * of converting the toString result back again. 
    */ 
    System.out.println("exact value of toString result as double: " 
     + new BigDecimal(Double.parseDouble(valueString))); 
    } 
} 

Выход:

Exact value: -1350825904190559999913623552 
Range: [-1350825904190560137352577024,-1350825904190559862474670080] 
toString result: -1.35082590419056E27 
exact value of toString result as double: -1350825904190559999913623552 
+0

Мне нравится ваше объяснение. программа немного консубирует для меня, хотя. Так что два вопроса у меня есть 1), что это за диапазон [-1350825904190560137352577024, -1350825904190559862474670080] 2) Из того, что вы говорите, форматирование двойника в таком случае показывает только цифры, достаточные для однозначно идентифицировать его из диапазона? даже если double находится в диапазоне 2^53 и Double.MAX_VALUE, форматирование не будет содержать все цифры правильно? –

+0

Извините, я думаю, что понял! это отличное объяснение. im заинтригован тем фактом, что строка для двойных преобразований для диапазона указанных вами децималброксов одинакова. Любые лучшие ссылки, чтобы прочитать об этом? –

+0

JLS относится к документации API для java.lang.Double [valueOf] (https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang. String-) для преобразования строки в двойное. В частности, «это точное числовое значение затем концептуально преобразуется в« бесконечно точное »двоичное значение, которое затем округляется, чтобы ввести double с помощью обычного правила округления к ближайшему правилу арифметики IEEE 754 с плавающей запятой». –

 Смежные вопросы

  • Нет связанных вопросов^_^