java - setscale - ¿Por qué el orden natural de BigDecimal es inconsistente con los iguales?
java bigdecimal round 2 decimals (4)
Desde el Javadoc para BigDecimal
:
Nota: se debe tener cuidado si los objetos
BigDecimal
se utilizan como claves en unSortedMap
o elementos en unSortedSet
ya que elBigDecimal
natural deBigDecimal
es inconsistente con los iguales .
Por ejemplo, si crea un HashSet
y le agrega un new BigDecimal("1.0")
y un new BigDecimal("1.00")
, el conjunto contendrá dos elementos (porque los valores tienen diferentes escalas, por lo que no son iguales según los equals
y hashCode
), pero si hace lo mismo con un TreeSet
, el conjunto contendrá solo un elemento, porque los valores se comparan como iguales cuando usa compareTo
.
¿Hay alguna razón específica detrás de esta inconsistencia?
BigDecimal funciona al tener dos números, un entero y una escala. El número entero es el "número" y la escala es el número de dígitos a la derecha del lugar decimal. Básicamente un número de punto flotante base 10.
Cuando dice "1.0"
y "1.00"
estos son valores técnicamente diferentes en la notación BigDecimal:
1.0
integer: 10
scale: 1
precision: 2
= 10 x 10 ^ -1
1.00
integer: 100
scale: 2
precision: 3
= 100 x 10 ^ -2
En notación científica, no harías nada de eso, debería ser 1 x 10 ^ 0
o solo 1
, pero BigDecimal lo permite.
En compareTo
la escala se ignora y se evalúan como números ordinarios, 1 == 1
. En equals
se comparan los valores de entero y escala, 10 != 100
y 1 != 2
. El método BigDecimal equals ignora el object == this
comprobación, asumo porque la intención es que cada BigDecimal se trate como un tipo de número, no como un objeto.
Me gustaría compararlo con esto:
// same number, different types
float floatOne = 1.0f;
double doubleOne = 1.0;
// true: 1 == 1
System.out.println( (double)floatOne == doubleOne );
// also compare a float to a double
Float boxFloat = floatOne;
Double boxDouble = doubleOne;
// false: one is 32-bit and the other is 64-bit
System.out.println( boxInt.equals(boxDouble) );
// BigDecimal should behave essentially the same way
BigDecimal bdOne1 = new BigDecimal("1.0");
BigDecimal bdOne2 = new BigDecimal("1.00");
// true: 1 == 1
System.out.println( bdOne1.compareTo(bdOne2) );
// false: 10 != 100 and 1 != 2 ensuring 2 digits != 3 digits
System.out.println( bdOne1.equals(bdOne2) );
Debido a que BigDecimal permite una "precisión" específica, comparar el número entero y la escala es más o menos lo mismo que comparar el número y la precisión.
Aunque hay una advertencia parcial al hablar sobre el método precision () de BigDecimal que siempre devuelve 1 si BigDecimal es 0. En este caso, la precisión compareTo && evalúa true y es igual a false. Pero 0 * 10 ^ -1
no debe ser igual a 0 * 10 ^ -2
porque el primero es un número de 2 dígitos 0.0
y el último es un número de 3 dígitos 0.00
. El método de iguales es comparar tanto el valor como el número de dígitos.
Supongo que es extraño que BigDecimal permita ceros finales, pero esto es básicamente necesario. Realizar una operación matemática como "1.1" + "1.01"
requiere una conversión, pero "1.10" + "1.01"
no lo requiere.
Entonces compare para comparar BigDecimals como números e equals
compara BigDecimals como BigDecimals.
Si la comparación no es deseada, use una lista o matriz donde esto no importa. Por supuesto, HashSet y TreeSet están diseñados específicamente para contener elementos únicos.
Desde la implementación OpenJDK de BigDecimal:
/**
* Compares this {@code BigDecimal} with the specified
* {@code Object} for equality. Unlike {@link
* #compareTo(BigDecimal) compareTo}, this method considers two
* {@code BigDecimal} objects equal only if they are equal in
* value and scale (thus 2.0 is not equal to 2.00 when compared by
* this method).
*
* @param x {@code Object} to which this {@code BigDecimal} is
* to be compared.
* @return {@code true} if and only if the specified {@code Object} is a
* {@code BigDecimal} whose value and scale are equal to this
* {@code BigDecimal}''s.
* @see #compareTo(java.math.BigDecimal)
* @see #hashCode
*/
@Override
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflate().equals(xDec.inflate());
}
Más de la implementación:
* <p>Since the same numerical value can have different
* representations (with different scales), the rules of arithmetic
* and rounding must specify both the numerical result and the scale
* used in the result''s representation.
Es por eso que la implementación de equals
toma en cuenta la scale
. El constructor que toma una cadena como parámetro se implementa así:
public BigDecimal(String val) {
this(val.toCharArray(), 0, val.length());
}
donde el tercer parámetro se usará para la scale
(en otro constructor), por eso las cadenas 1.0
y 1.00
crearán BigDecimals diferentes (con diferentes escalas).
De Java efectiva por Joshua Bloch:
El párrafo final del contrato compareTo, que es una sugerencia fuerte en lugar de una disposición verdadera, simplemente establece que la prueba de igualdad impuesta por el método compareTo generalmente debe devolver los mismos resultados que el método equals. Si se cumple esta disposición, se dice que la ordenación impuesta por el método compareTo es consistente con iguales. Si se viola, se dice que el ordenamiento es inconsistente con los iguales. Una clase cuyo método compareTo impone un orden que es inconsistente con iguales todavía funcionará, pero las colecciones ordenadas que contienen elementos de la clase pueden no obedecer el contrato general de las interfaces de colección apropiadas (Colección, Conjunto o Mapa). Esto se debe a que los contratos generales para estas interfaces se definen en términos del método de iguales, pero las colecciones ordenadas utilizan la prueba de igualdad impuesta por compareTo en lugar de iguales. No es una catástrofe si esto sucede, pero es algo a tener en cuenta.
El comportamiento parece razonable en el contexto de la precisión aritmética, donde los ceros finales son cifras significativas y 1.0 no tiene el mismo significado que 1.00. Hacerlos desiguales parece ser una opción razonable.
Sin embargo, desde una perspectiva de comparación, ninguno de los dos es mayor o menor que el otro y la interfaz Comparable requiere un orden total (es decir, cada BigDecimal debe ser comparable con cualquier otro BigDecimal). La única opción razonable aquí era definir un orden total tal que el método compareTo consideraría los dos números iguales.
Tenga en cuenta que la inconsistencia entre igual y compareTo no es un problema mientras esté documentado. Incluso a veces es exactamente lo que uno necesita .
La respuesta es bastante corta. El método equals () compara objetos mientras que compareTo () compara valores. En el caso de BigDecimal diferentes objetos pueden representar el mismo valor. Es por eso que equals () puede devolver falso, mientras que compareTo () devuelve 0.
objetos iguales => valores iguales
valores iguales = /> objetos iguales
El objeto es solo una representación de computadora de algún valor del mundo real. Por ejemplo, la misma imagen podría estar representada en formatos GIF y JPEG. Es muy parecido a BigDecimal, donde el mismo valor puede tener representaciones distintas.