Подтвердить что ты не робот

Почему BigDecimal естественный порядок несовместим с равными?

Из Javadoc для BigDecimal:

Примечание: следует соблюдать осторожность, если BigDecimal объекты используются как ключи в SortedMap или в элементах в SortedSet, так как BigDecimal естественное упорядочение несовместимо с равными.

Например, если вы создадите HashSet и добавьте new BigDecimal("1.0") и new BigDecimal("1.00") к нему, набор будет содержать два элемента (поскольку значения имеют разные шкалы, поэтому они не равны в соответствии с equals и hashCode), но если вы сделаете то же самое с TreeSet, набор будет содержать только один элемент, потому что значения сравниваются как равные при использовании compareTo.

Есть ли какая-то конкретная причина этой несогласованности?

4b9b3361

Ответ 1

Из реализация OpenJDK в 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());
    }

Больше от реализации:

 * <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 representation.

Вот почему реализация equals учитывает scale. Конструктор, который берет строку как параметр, реализуется следующим образом:

    public BigDecimal(String val) {
        this(val.toCharArray(), 0, val.length());
    }

где третий параметр будет использоваться для scale (в другом конструкторе), поэтому строки 1.0 и 1.00 будут создавать разные BigDecimals (с разными шкалами).

Из Эффективная Java Джошуа Блох:

Последний абзац договора compareTo, который является сильным скорее, чем истинное положение, просто заявляет, что тест равенства, наложенный методом compareTo, должен, как правило, возвращаться те же результаты, что и метод equals. Если это положение соблюдается, порядок, наложенный методом compareTo, называется согласованным с равными. Если это нарушено, заказ считается непоследовательным с равными. Класс, метод compareTo которого налагает порядок, который несовместимые с равными, по-прежнему будут работать, но отсортированные коллекции содержащие элементы класса, могут не подчиняться общему договору соответствующие интерфейсы коллекции (Collection, Set или Map). Эта потому что общие контракты для этих интерфейсов определены в условия метода equals, но отсортированные коллекции используют равенство тест, налагаемый compareTo вместо равных. Это не катастрофа если это произойдет, но это то, о чем нужно знать.

Ответ 2

Поведение кажется разумным в контексте арифметической точности, где конечные нули значимые цифры и 1.0 не имеют того же значения, что и 1.00. Сделать их неравными, по-видимому, разумным выбором.

Однако с точки зрения сравнения ни одна из двух больше или меньше, чем другая, а интерфейс Comparable требует полного порядка (т.е. каждый BigDecimal должен быть сопоставим с любым другим BigDecimal). Единственным разумным вариантом здесь было определение полного порядка, так что метод compareTo рассмотрел бы два числа.

Обратите внимание, что несогласованность между равными и compareTo не является проблемой, если она задокументирована. Это даже иногда то, что вам нужно.

Ответ 3

BigDecimal работает, имея два числа, целое число и масштаб. Целое число - это "число", а масштаб - это число цифр справа от десятичной точки. В основном базовое число с плавающей запятой.

Когда вы говорите "1.0" и "1.00", это технически разные значения в формате 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

В научной нотации вы не сделали бы ни того, ни другого, это должно быть 1 x 10 ^ 0 или просто 1, но BigDecimal позволяет это.

В compareTo масштаб игнорируется и оценивается как обычные числа 1 == 1. В equals сравниваются значения целых чисел и шкалы, 10 != 100 и 1 != 2. Метод BigDecimal equals игнорирует проверку object == this, я предполагаю, потому что намерение состоит в том, что каждый BigDecimal рассматривается как тип числа, а не как объект.

Я бы упомянул об этом:

// 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) );

Поскольку BigDecimal допускает определенную "точность", сравнение как целого, так и шкалы более или менее такое же, как сравнение как числа, так и точности.

Несмотря на то, что это говорит о методе BigDecimal precision(), который всегда возвращает 1, если BigDecimal равен 0. В этом случае compareTo && & точность оценивает значение true и равно принимает значение false. Но 0 * 10 ^ -1 не должен равняться 0 * 10 ^ -2, потому что первый - это 2-значное число 0.0, а последнее - 3-значное число 0.00. Метод equals сравнивает как значение, так и количество цифр.

Я полагаю, это странно, что BigDecimal допускает завершение нулей, но это в основном необходимо. Для выполнения математической операции типа "1.1" + "1.01" требуется преобразование, но "1.10" + "1.01" нет.

Итак, compareTo сравнивает BigDecimals как числа и equals сравнивает BigDecimals как BigDecimals.

Если сравнение нежелательно, используйте список или массив, где это не имеет значения. HashSet и TreeSet, конечно, предназначены специально для хранения уникальных элементов.

Ответ 4

Ответ довольно короткий. Метод equals() сравнивает объекты, а compareTo() сравнивает значения. В случае BigDecimal различные объекты могут представлять одно и то же значение. То почему equals() может возвращать false, в то время как compareTo() возвращает 0.

равные объекты = > равные значения

равные значения =/" > равные объекты

Объект - это просто компьютерное представление некоторого реального значения. Например, одно изображение может быть представлено в форматах GIF и JPEG. Это очень похоже на BigDecimal, где одинаковое значение может иметь различные представления.