round_half_up - exception bigdecimal java
Bigdecimal.divide de Java y redondeo (5)
En el trabajo, encontramos un problema al tratar de dividir un número grande por 1000. Este número proviene de la base de datos.
Digamos que tengo este método:
private static BigDecimal divideBy1000(BigDecimal dividendo) {
if (dividendo == null) return null;
return dividendo.divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP);
}
Cuando hago la siguiente llamada
divideBy1000(new BigDecimal("176100000"))
Recibo el valor esperado de 176100. Pero si intento la línea de abajo
divideBy1000(new BigDecimal("1761e+5"))
Recibo el valor 200000. ¿Por qué ocurre esto? Ambos números son iguales con representación diferente y el último es lo que recibo de la base de datos. Entiendo que, de alguna manera, la JVM está dividiendo el número 1761 por 1000, redondeando hacia arriba y llenando con 0 al final.
¿Cuál es la mejor manera de evitar este tipo de comportamiento? Tenga en cuenta que el número original no está controlado por mí.
Como se especifica en javadoc, un BigDecimal
se define por un valor entero y una escala .
El valor del número representado por BigDecimal es por lo tanto (unscaledValue × 10 ^ (- scale)).
Entonces BigDecimal("1761e+5")
tiene escala -5 y BigDecimal(176100000)
tiene escala 0.
La división de los dos BigDecimal
se realiza utilizando las escalas -5 y 0 respectivamente porque las escalas no se especifican al dividir. La documentación de la divide
explica por qué los resultados son diferentes.
divide
public BigDecimal divide(BigDecimal divisor)
Devuelve un
BigDecimal
cuyo valor es(this / divisor)
, y cuya escala preferida es(this.scale() - divisor.scale())
; si el cociente exacto no se puede representar (porque tiene una expansión decimal sin terminación) se lanza unaArithmeticException
.Parámetros:
divisor
: valor por el que se divide este BigDecimal.Devoluciones:
this / divisor
Tiros
ArithmeticException
- si el cociente exacto no tiene una expansión decimal de terminaciónYa que:
1.5
Si especifica una escala al dividir, por ejemplo, dividendo.divide(BigDecimal.valueOf(1000), 0, RoundingMode.HALF_UP)
obtendrá el mismo resultado.
Las expresiones new BigDecimal("176100000")
y new BigDecimal("1761e+5")
no son iguales . BigDecimal
realiza un seguimiento de valor y precisión.
BigDecimal("176100000")
tiene 9 dígitos de precisión y se representa internamente como BigInteger("176100000")
, multiplicado por 1. BigDecimal("1761e+5")
tiene 4 dígitos de precisión y se representa internamente como BigInteger("1761")
, multiplicado por 100000.
Cuando se divide un BigDecimal
por un valor, el resultado respeta los dígitos de precisión, lo que da como resultado diferentes salidas para valores aparentemente iguales.
Sí, eso es una especie de problema con lo que estás experimentando. Si puedo, en una situación en la que solo tienes números exponentales, deberías lanzarlos y luego usar tu método. Mira lo que sugiero es este fragmento de código ahí abajo:
long longValue = Double.valueOf("1761e+5").longValue();
BigDecimal value= new BigDecimal(longValue);
Úselo en un método que convierta esas cadenas en un nuevo BigDecimal y devuelva este valor BigDecimal. Luego, puede usar esos valores devueltos con divideBy1000. Eso debería solucionar cualquier problema que tenga.
Si tiene muchos de estos, también puede almacenar BigDecimal en una estructura de datos como una lista. Luego use un bucle foreach en el que aplique divideBy1000 y cada nuevo valor se almacenará en una lista diferente. ¡Entonces solo tendría que acceder a esta lista para tener su nuevo conjunto de valores!
Espero eso ayude :)
Siempre que esté multiplicando un BigDecimal por una potencia de 10, en este caso, si multiplica por 10 -3 , puede usar dividendo.scaleByPowerOfTen(power)
que solo modifica la escala del objeto BigDecimal y los pasos laterales para cualquier problema de redondeo, o Al menos los mueve a un cálculo posterior.
Las otras respuestas aquí cubren el caso más general de dividir por cualquier número.
Trate de usar round()
.
private static BigDecimal divideBy1000(BigDecimal dividendo) {
if (dividendo == null) return null;
return dividendo.divide(BigDecimal.valueOf(1000)).round(new MathContext(4, RoundingMode.HALF_UP));
}
public static void main(String []args){
BigDecimal bigD = new BigDecimal("1761e5");
BigDecimal bigDr = divideBy1000(bigD);
System.out.println(bigDr);
}
La new MathContext(4, RoundingMode.HALF_UP))
devuelve la división a 4 lugares.
Esto produce:
1.761E+5
Que es lo que quieres. (: