setscale round multiply decimals java bigdecimal

java - round - ¿Por qué se especifica BigDecimal.equals para comparar tanto el valor como la escala individualmente?



java bigdecimal round 2 decimals (6)

Me imagino que debe haber una buena razón para ignorar esta recomendación.

Tal vez no. Propongo la explicación simple de que los diseñadores de BigDecimal acaban de hacer una mala elección de diseño.

  1. Un buen diseño optimiza para el caso de uso común. La mayoría de las veces (> 95%), las personas desean comparar dos cantidades basadas en la igualdad matemática. Para la minoría del tiempo en el que realmente te importa si los dos números son iguales tanto en escala como en valor, podría haber habido un método adicional para ese propósito.
  2. Va en contra de las expectativas de las personas y crea una trampa en la que es muy fácil caer. Un buen API obedece al "principio de menos sorpresa".
  3. Rompe la convención habitual de Java de que Comparable es consistente con la igualdad.

Curiosamente, la clase BigDecimal de Scala (que se implementa utilizando BigDecimal Java bajo el capó) ha hecho la elección opuesta:

BigDecimal("2.0") == BigDecimal("2.00") // true

Esta no es una pregunta sobre cómo comparar dos objetos BigDecimal . Sé que puedes usar compareTo lugar de equals para hacer eso, ya que equals se documenta como:

A diferencia de compareTo, este método considera que dos objetos BigDecimal son iguales solo si tienen el mismo valor y escala (por lo tanto, 2.0 no es igual a 2.00 cuando se compara con este método).

La pregunta es: ¿por qué los equals han especificado de esta manera aparentemente contra-intuitiva? Es decir, ¿por qué es importante poder distinguir entre 2.0 y 2.00?

Parece probable que debe haber una razón para esto, ya que la documentación Comparable , que especifica el método compareTo , establece:

Se recomienda encarecidamente (aunque no es obligatorio) que los ordenamientos naturales sean consistentes con los iguales

Me imagino que debe haber una buena razón para ignorar esta recomendación.


El método compareTo sabe que los ceros finales no afectan el valor numérico representado por un BigDecimal , que es el único aspecto que le preocupa a compareTo . Por el contrario, el método equals generalmente no tiene manera de saber qué aspectos de un objeto a alguien le importa, y por lo tanto, solo debe devolver true si dos objetos son equivalentes en todas las formas en que un programador podría estar interesado. Si x.equals(y) es cierto, sería bastante sorprendente que x.toString().equals(y.toString()) para producir false.

Otro problema que quizás sea aún más significativo es que BigDecimal combina esencialmente un BigInteger y un factor de escala, de modo que si dos números representan el mismo valor pero tienen diferentes números de ceros finales, uno tendrá un gran bigInteger cuyo valor es una potencia de diez veces el otro. Si la igualdad requiere que la mantisa y la escala coincidan, entonces el hashCode() para BigDecimal puede usar el código hash de BigInteger . Sin embargo, si es posible que dos valores se consideren "iguales" a pesar de que contienen diferentes valores de BigInteger , eso complicará las cosas significativamente. Un tipo BigDecimal que usara su propio almacenamiento de respaldo, en lugar de un BigInteger , podría implementarse de varias maneras para permitir que los números se procesen rápidamente de tal manera que los valores que representan el mismo número se comparen de la misma manera (como un simple ejemplo, un versión que empaquetaba nueve dígitos decimales en cada valor long y siempre requería que el punto decimal se sentara entre grupos de nueve, podía calcular el código hash de una manera que ignoraría los grupos finales cuyo valor era cero) pero un BigDecimal que encapsula un BigInteger puede '' t hacer eso


En matemática 10.0 es igual a 10.00. En física, 10.0 my 10.00 m son posiblemente diferentes (diferente precisión), cuando se habla de objetos en un OOP, definitivamente diría que no son iguales.

También es fácil pensar en una funcionalidad inesperada si los iguales ignoraron la escala (por ejemplo: si a.equals (b), ¿no esperaría a.add (0.1) .equals (b.add (0.1)?).


Porque en algunas situaciones, una indicación de precisión (es decir, el margen de error) puede ser importante.

Por ejemplo, si está almacenando mediciones realizadas por dos sensores físicos, quizás uno sea 10 veces más preciso que el otro. Puede ser importante representar este hecho.


Si los números se redondean, muestra la precisión del cálculo, en otras palabras:

  • 10.0 podría significar que el número exacto estaba entre 9.95 y 10.05
  • 10.00 podría significar que el número exacto estaba entre 9.995 y 10.005

En otras palabras, está vinculado a la precisión aritmética .


Un punto que aún no se ha considerado en ninguna de las otras respuestas es que se requiere que equals sea ​​coherente con hashCode , y el costo de una implementación de hashCode que se requirió para producir el mismo valor para 123.0 que para 123.00 (pero aún así hacer un el trabajo razonable de distinguir diferentes valores sería mucho mayor que el de una implementación de código hash que no se requería para hacerlo. Bajo la semántica actual, hashCode requiere una multiplicación por 31 y se agrega por cada 32 bits de valor almacenado. Si se requirió que hashCode fuera consistente entre los valores con diferente precisión, tendría que calcular la forma normalizada de cualquier valor (costoso) o bien, como mínimo, hacer algo como calcular la raíz digital base-999999999 del valor y multiplicar esa , mod 999999999, basado en la precisión. El bucle interno de tal método sería:

temp = (temp + (mag[i] & LONG_MASK) * scale_factor[i]) % 999999999;

Reemplazo de una operación multiplicar por 31 con un módulo de 64 bits, mucho más costoso. Si uno desea una tabla hash que considere valores BigDecimal numéricamente equivalentes, y se encontrarán la mayoría de las claves que se buscan en la tabla, la manera eficiente de lograr el resultado deseado sería utilizar una tabla hash que almacene envoltorios de valor, más bien que almacenar valores directamente. Para encontrar un valor en la tabla, comience por buscar el valor en sí mismo. Si no encuentra ninguno, normalice el valor y busque eso. Si no se encuentra nada, cree un envoltorio vacío y almacene una entrada bajo las formas original y normalizada del número.

Buscar algo que no está en la tabla y que no se haya buscado previamente requeriría un paso de normalización costoso, pero buscar algo que se haya buscado sería mucho más rápido. Por el contrario, si HashCode tuviera que devolver valores equivalentes para números que, debido a la diferente precisión, se almacenaran de manera totalmente diferente, haría que todas las operaciones de tabla hash fueran mucho más lentas.