big java precision division bigdecimal financial

java - ArithmeticException lanzada durante BigDecimal.divide



bigdecimal java (9)

Pensé que java.math.BigDecimal se supone que es The Answer ™ a la necesidad de realizar una aritmética de precisión infinita con números decimales.

Considere el siguiente fragmento de código:

import java.math.BigDecimal; //... final BigDecimal one = BigDecimal.ONE; final BigDecimal three = BigDecimal.valueOf(3); final BigDecimal third = one.divide(three); assert third.multiply(three).equals(one); // this should pass, right?

Espero que la assert pase, pero de hecho la ejecución ni siquiera llega: ¡ one.divide(three) hace que ArithmeticException sea ​​lanzada!

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide

Resulta que este comportamiento está documentado explícitamente en la java.math.BigDecimal :

En el caso de divide , el cociente exacto podría tener una expansión decimal infinitamente larga; por ejemplo, 1 dividido por 3. Si el cociente tiene una expansión decimal sin terminación y la operación se especifica para devolver un resultado exacto, se lanza una ArithmeticException . De lo contrario, se devuelve el resultado exacto de la división, como se hizo para otras operaciones.

Navegando alrededor de la API aún más, se encuentra que de hecho hay varias sobrecargas de divide que realizan una división inexacta , es decir:

final BigDecimal third = one.divide(three, 33, RoundingMode.DOWN); System.out.println(three.multiply(third)); // prints "0.999999999999999999999999999999999"

Por supuesto, la pregunta obvia ahora es "¿Cuál es el punto?". Pensé que BigDecimal es la solución cuando necesitamos una aritmética exacta , por ejemplo, para cálculos financieros. Si ni siquiera podemos divide exactamente, entonces, ¿qué tan útil puede ser esto? ¿En realidad sirve para un propósito general, o solo es útil en una aplicación de nicho donde afortunadamente simplemente no necesita divide ?

Si esta no es la respuesta correcta, ¿qué podemos usar para la división exacta en el cálculo financiero? (Quiero decir, no tengo un especial de finanzas, pero todavía usan división, ¿verdad?).


Por cierto, realmente agradecería la información de personas que han trabajado con software financiero. A menudo escuché a BigDecimal siendo defendido por más del doble

En los informes financieros usamos siempre BigDecimal con scale = 2 y ROUND_HALF_UP , ya que todos los valores impresos en un informe deben conducir a un resultado reproducible. Si alguien verifica esto usando una calculadora simple.

En Suiza redondean a 0.05 ya que ya no tienen 1 o 2 monedas de Rappen .


Si esta no es la respuesta correcta, ¿qué podemos usar para la división exacta en el cálculo financiero? (Quiero decir, no tengo un especial de finanzas, pero todavía usan división, ¿verdad?).

Luego estuve en la escuela primaria 1 , me enseñaron que cuando se divide por 1 por 3 obtienes un 0.33333 ... es decir, un decimal recurrente . La división de números representados en forma decimal NO es exacta. De hecho, para cualquier base fija habrá fracciones (el resultado de dividir un entero por otro) que no se pueden representar exactamente como un número de coma flotante de precisión finita en esa base. (El número tendrá una parte recurrente ...)

Cuando hace cálculos financieros que involucran división, debe considerar qué hacer con una fracción recurrente. Puedes redondearlo hacia arriba o hacia abajo, o al número entero más cercano, u otra cosa, pero básicamente no puedes olvidarte del problema.

El BigDecimal javadoc dice esto:

La clase BigDecimal le da a su usuario un control completo sobre el comportamiento de redondeo. Si no se especifica el modo de redondeo y no se puede representar el resultado exacto, se lanza una excepción; de lo contrario, los cálculos se pueden llevar a cabo en un modo de precisión y redondeo elegido suministrando un objeto MathContext apropiado a la operación.

En otras palabras, es su responsabilidad decirle a BigDecimal qué hacer con el redondeo.

EDITAR - en respuesta a estos seguimientos del OP.

¿Cómo detecta BigDecimal infinito decimal recurrente?

No detecta explícitamente el decimal recurrente. Simplemente detecta que el resultado de alguna operación no puede ser representado exactamente usando la precisión especificada; por ejemplo, se requieren demasiados dígitos después del punto decimal para una representación exacta.

Debe realizar un seguimiento y detectar un ciclo en el dividendo. PODRÍA HABER ELEGIDO manejar esto de otra manera, marcando dónde está la porción recurrente, etc.

Supongo que BigDecimal podría haber sido especificado para representar un decimal recurrente exactamente; es decir, como una clase BigRational . Sin embargo, esto haría la implementación más complicada y más costosa de usar 2 . Y dado que la mayoría de las personas esperan que los números se muestren en decimales, y el problema de recurrentes decimales se repite en ese punto.

La conclusión es que esta complejidad extra y el costo del tiempo de ejecución serían inapropiados para los casos de uso típicos de BigDecimal . Esto incluye cálculos financieros, donde las convenciones contables no le permiten usar decimales recurrentes.

1 - Fue una excelente escuela primaria ...

2 - O intenta eliminar los factores comunes del divisor y el dividendo (computacionalmente costosos), o les permite crecer sin límites (costosos en el uso del espacio ... y computacionalmente para las operaciones posteriores).


¿Hay una necesidad de

a=1/3; b=a*3; resulting in b==1;

en los sistemas financieros? Supongo que no. En los sistemas financieros se define, qué modo circular y escala se debe utilizar, al hacer los cálculos. En algunas situaciones, el modo circular y la escala se definen en la ley. Todos los componentes pueden confiar en un comportamiento definido. Devolver b == 1 sería una falla, porque no cumpliría el comportamiento especificado. Esto es muy importante cuando se calculan precios, etc.

Es como las especificaciones IEEE 754 para representar flotantes en dígitos binarios. Un componente no debe optimizar una representación "mejor" sin pérdida de información, porque esto romperá el contrato.


Acepto que Java no tiene un gran soporte para representar fracciones, pero debes darte cuenta de que es imposible mantener las cosas completamente precisas cuando trabajas con computadoras. Al menos en este caso, la excepción es decirle que se está perdiendo precisión.

Hasta donde yo sé, "la aritmética de precisión infinita con números decimales" simplemente no va a suceder. Si tienes que trabajar con decimales, lo que estás haciendo probablemente esté bien, solo capta las excepciones. De lo contrario, una búsqueda rápida en Google encuentra algunos recursos interesantes para trabajar con fracciones en Java:

http://commons.apache.org/math/userguide/fraction.html

http://www.merriampark.com/fractions.htm

¿La mejor forma de representar una fracción en Java?


Debería preferir BigDecimal para los cálculos financieros. El redondeo debe ser especificado por la empresa. Por ejemplo, una cantidad (100,00 $) tiene que dividirse equitativamente en tres cuentas. Tiene que haber una regla de negocios cuya cuenta tome el centavo extra.

Double, floats no son apropiados para su uso en aplicaciones financieras porque no pueden representar fracciones de 1 precisamente que no son exponenciales de 2. Por ejemplo, considere 0.6 = 6/10 = 1 * 1/2 + 0 * 1/4 + 0 * 1 / 8 + 1 * 1/16 + ... = 0.1001 ... b

Para los cálculos matemáticos puede usar un número simbólico, por ejemplo, almacenar el denominador y el numerador o incluso una expresión completa (por ejemplo, este número es sqrt (5) +3/4). Como este no es el caso de uso principal de la API de Java, no lo encontrará allí.


La clase es BigDecimal no BigFractional . De algunos de sus comentarios, parece que solo quiere quejarse de que alguien no compiló todos los algoritmos de manejo de números posibles en esta clase. Las aplicaciones financieras no necesitan precisión decimal infinita; solo valores perfectamente precisos a la precisión requerida (típicamente 0, 2, 4 o 5 dígitos decimales).

En realidad, he manejado muchas aplicaciones financieras que usan el double . No me gusta, pero esa fue la forma en que están escritos (no en Java tampoco). Cuando hay tipos de cambio y conversiones de unidades, existe el potencial de problemas de redondeo y hematomas. BigDecimal elimina el más tarde, pero todavía hay el primero para la división.


Para dividir el guardado, debes establecer el MATHcontext ,

BigDecimal bd = new BigDecimal(12.12, MathContext.DECIMAL32).divide(new BigDecimal(2)).setScale(2, RoundingMode.HALF_UP);


Si quieres trabajar con decimales, no números racionales, y necesitas aritmética exacta antes del redondeo final (redondeando a centavos o algo así), aquí hay un pequeño truco.

Siempre puede manipular sus fórmulas para que haya una sola división final. De esta manera, no perderá precisión durante los cálculos y siempre obtendrá el resultado redondeado correctamente. Por ejemplo

a/b + c

igual

(a + bc) / b.


Tenga en cuenta que estamos usando una computadora ... Una computadora tiene mucha ram y la precisión requiere ram. Entonces, cuando quieres una precisión infinita, necesitas
(infinite * infinite) ^ (infinite * Integer.MAX_VALUE) terrabyte ram ...

Sé que 0.333333... es 0.333333... y debería ser posible almacenarlo en ram como "uno dividido entre tres" y luego puedes multiplicarlo de nuevo y debes tener 1 . Pero no creo que Java tenga algo así ...
Tal vez tengas que ganar el Premio Nobel por escribir algo al respecto. ;-)