2016-02-17 4 views
3

я использую BigDecimal, но я все еще получаю разные результаты для двух различных (математически идентичных) выражений:Java BigDecimal - объяснение необходимости

Первое выражение: PI - (10^(- 14)/PI)

Второе выражение: (PI^2 - 10^(- 14))/PI

Выражаясь более просто, здесь есть уравнение: Equation with PI

package set1; 

import java.math.BigDecimal; 
import java.math.RoundingMode; 

public class FloatingLaws { 
    final static BigDecimal PI = BigDecimal.valueOf(Math.PI); 
    public static void main(String[] args) {   
     System.out.println(firstExpression()); 
     System.out.println(secondExpression()); 

    } 

    private static BigDecimal secondExpression() { 
     return PI.subtract((BigDecimal.valueOf(Math.pow(10, -14)).divide(PI,50,RoundingMode.CEILING))); 

    } 

    private static BigDecimal firstExpression() { 
     return (PI.multiply(PI).subtract(BigDecimal.valueOf(Math.pow(10, -14)))).divide(PI, 50,RoundingMode.CEILING); 
    } 

} 

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

3.14159265358978981690113816209304300915191180404867 
3.14159265358978981690113816209304300915191180404866 

Мой вопрос: почему это происходит и разрешимо ли это?

+1

Действительно ли это вас касается? – Idos

+0

Да, это действительно так :) – mrGreenBrown

+6

Я никогда не использовал 'BigDecimal', но я вижу много« округления »в вашем коде. Округление _ в середине вычисления не является хорошей идеей (в реальной математике, где точность не ограничена памятью), потому что вы не получите точного ответа. – Arc676

ответ

5

Это происходит потому, что Вы делаете это:

< pi - ((10^-4)/pi) - только часть в кронштейна ceiled,

который отличается от

< ((pi^2-10^-14)/pi) - где ceiled все выражение.

Вы используете BigDecimal, и у вас есть режим округления CEILING с точностью 50. В обоих выражениях потолок применяется, когда вы делите на номер PI. Поэтому, если вы разделите PI так же, как в первом выражении, то вы можете получить менее точные результаты - потому что вы достигли промежуточного значения CEIL, прежде чем ваша формула будет полностью выполнена, поэтому вы потеряете ПОТОЧНУЮ часть из разрыва с помощью операции PI, и это в дальнейших вычислениях создает эффект «ошибки». Когда вы делите на PI last, как во втором выражении, вы используете более точную формулу, которая представляет собой потолок только результат, а не промежуточное значение, как в первом выражении, поэтому он вычисляет более точное, округляя только результат, а не промежуточное значение.

3

Метод BigDecimal.subtract всегда производит точную разницу между двумя цифрами BigDecimal, без округления. С другой стороны, BigDecimal.divide обычно округляет результат. В вашем случае вы используете режим округления округления (до + бесконечности) CEILING. Когда вы вычисляете a-ceil(b/a), вы по существу округляете весь результат (при условии, что a уже закруглен), а при вычислении ceil((a*a-b)/a) вы округляете. Вот почему firstExpression() больше. Если бы вы использовали округление HALF_EVEN, результат был бы таким же. Вы использовали режим FLOOR, результат был бы противоположным.

Также обратите внимание на то, что BigDecimal.valueOf(Math.PI);:

System.out.println(BigDecimal.valueOf(Math.PI)); 
> 3.141592653589793 

Это даже не близко к фактическому числу ПИ (учитывая тот факт, что вам нужно 50 цифр). Вы должны определить PI явно, как это:

final static BigDecimal PI = new BigDecimal("3.14159265358979323846264338327950288419716939937510"); 

Теперь результат заключается в следующем:

3.14159265358979005536378154537278750652190194908786 
3.14159265358979005536378154537278750652190194908785 

который довольно сильно отличается от вашего одного.

+0

На самом деле, чтобы получить лучшие результаты, часто имеет смысл использовать точность немного больше, чем вам нужно, например. 'targetPrecision + 5', и только для раунда (предпочтительно' HALF_UP') в самом конце. Таким образом, вы можете даже вычислить косинус (с использованием серии Taylor с множеством дополнений и делений) до необходимой точности. –

0

Я изменил вашу программу, чтобы попробовать все варианты округления, которые java знает. Запуск под oracle jdk 8.72, я получаю равные результаты для режимов округления HALF_UP, HALF_DOWN и HALF_EVEN. Но Кшиштоф прав, поскольку вы не округляетесь в одном и том же месте, ошибки обязательно появятся.

public class FloatingLaws { 
    final static BigDecimal PI = BigDecimal.valueOf(Math.PI); 

    public static void main(String[] args) { 
     for (RoundingMode roundingMode : RoundingMode.values()) { 
      System.out.println(roundingMode); 
      System.out.println("Equal? "+firstExpression(roundingMode).equals(secondExpression(roundingMode))); 
      System.out.println(firstExpression(roundingMode)); 
      System.out.println(secondExpression(roundingMode)); 
     } 

    } 

    private static BigDecimal secondExpression(RoundingMode roundingMode) { 
    return PI.subtract((BigDecimal.valueOf(Math.pow(10, -14)).divide(PI, 50, roundingMode))); 

    } 

    private static BigDecimal firstExpression(RoundingMode roundingMode) { 
    return (PI.multiply(PI).subtract(BigDecimal.valueOf(Math.pow(10, -14)))).divide(PI, 50, roundingMode); 
    } 

} 
+0

UP, DOWN, CEILING и FLOOR имеют худшие ошибки округления, потому что они могут иметь значение почти 1 в последней сохраненной цифре. В режимах HALF_UP, HALF_DOWN и HALF_EVEN разница составляет не более половины из последних сохраненных цифр, поэтому они должны стремиться к лучшим результатам. –

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

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