Ответ 1
Это связано с переполнением целых чисел. Когда thisVal
очень велико, а anotherVal
отрицательно, то вычитание последнего из первого дает результат, превышающий thisVal
, который может переполняться в отрицательный диапазон.
Я обнаружил, что реализация java.lang.Integer
метода compareTo
выглядит следующим образом:
public int compareTo(Integer anotherInteger) {
int thisVal = this.value;
int anotherVal = anotherInteger.value;
return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1));
}
Вопрос в том, почему вместо вычитания используйте сравнение:
return thisVal - anotherVal;
Это связано с переполнением целых чисел. Когда thisVal
очень велико, а anotherVal
отрицательно, то вычитание последнего из первого дает результат, превышающий thisVal
, который может переполняться в отрицательный диапазон.
Вычитается "трюк" для сравнения двух числовых значений!
int a = -2000000000;
int b = 2000000000;
System.out.println(a - b);
// prints "294967296"
Здесь a < b
, но a - b
положителен.
НЕ ИСПОЛЬЗУЙТЕ эту идиому. Это не работает.
Более того, даже если он действительно работает, он не будет оказывать существенного улучшения производительности и может, по сути, иметь удобство чтения.
В этой головоломке есть несколько уроков. Наиболее характерным является: Не используйте компаратор с вычитанием, если вы не уверены, что разница между значениями никогда не будет больше
Integer.MAX_VALUE
. В более общем плане, остерегайтесь переполненияint
. Еще один урок состоит в том, что вам следует избегать "умного" кода. Стремитесь писать четкий, правильный код и не оптимизировать его, если это не доказывает необходимость.
Проще говоря, тип int
недостаточно велик, чтобы сохранить разницу между двумя произвольными значениями int
. Например, разница между 1,5 миллиардами и -1,5 миллиардами составляет 3,0 миллиарда, но int
не может содержать значения, превышающие 2,1 миллиарда.
Возможно, чтобы избежать переполнения/недогрузки.
В дополнение к проблеме переполнения следует заметить, что версия с подстановкой не дает одинаковых результатов.
Если вы знаете, что переполнения не будет, вы можете использовать что-то вроде этого:
public int compareTo(Integer anotherInteger) {
return sign(this.value - anotherInteger.valuel);
}