Я наблюдаю какое-то странное поведение при умножении целых чисел в Java. Я выполнял некоторые упражнения по кодированию и наткнулся на следующее упражнение. Требования: Для заданного целого числа напишите функцию, которая находит произведение каждого кратного 3, которое меньше заданного целого числа, Кроме любого кратного 5. Например, учитывая 17, мы хотим вернуть 12*9*6*3 (= 1944). Я написал следующее:
public int findProduct(int limit) {
int product = 1;
for(int n = 3; n < limit; n = n + 3) {
if (n % 5 != 0) {
product = product * n;
}
}
return product;
}
Это прекрасно работает для небольших чисел. Однако при тестировании я обнаружил, что как только вы превысите 33, возвращаемое значение будет далеко. Например, если я вызываю функцию с ограничением 36, она возвращает -1,466221696E9. Вот тут я запутался. Я умножаю целые числа положительный, и результат почему-то отрицательный.
Однако я обнаружил, что если вы объявляете двойное значение, оно всегда возвращает правильный результат.
public double findProduct(int limit) {
double product = 1;
for(int n = 3; n < limit; n = n + 3) {
if (n % 5 != 0) {
product = product * n;
}
}
return product;
}
Мой вопрос: почему это происходит с целыми числами и чем отличается двойной тип, который заставляет его работать правильно?




Давайте рассмотрим это на примере Integer.
Integer.MAX_VALUE может быть представлен как 01111111111111111111111111111111, который представляет собой 32 битовую строку (включая знаковый бит). Теперь, если вы добавите 1 к приведенной выше строке, получится 10000000000000000000000000000000, что совпадает с Integer.MIN_VALUE. Это называется переполнением Integer.
System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));
// 1111111111111111111111111111111
Согласно Integer#toBinaryString:
The unsigned integer value is the argument plus 232 if the argument is negative; otherwise it is equal to the argument. This value is converted to a string of ASCII digits in binary (base 2) with no extra leading 0s.
Вот почему вы не можете видеть бит знака, но реальное значение Integer.MAX_VALUE равно 01111111111111111111111111111111. Теперь взгляните на этот код:
System.out.println(Integer.toBinaryString(Integer.MAX_VALUE + 1));
// 10000000000000000000000000000000
System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));
// 10000000000000000000000000000000
Вывод обоих чисел одинаков. Java не защищает от Integer переполнения. Об этом должен позаботиться разработчик. Так какое же может быть возможное решение этой проблемы? Вы можете использовать другие типы данных, такие как long или BigInteger. Вот максимальные значения, которые могут вас заинтересовать:
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Long.MAX_VALUE); // 9223372036854775807
System.out.println(Double.MAX_VALUE); // 1.7976931348623157E308
System.out.println(Float.MAX_VALUE); // 3.4028235E38
Как только Integer достигнет MAX_VALUE, оно начнет переполняться, и вы получите отрицательное значение.
Ой. Хм. Итак, что произойдет, если вы превысите этот предел, он просто вернется к наименьшему числу? Это объяснило бы, как я получаю отрицательный результат.
@C.Peck Дополнительную информацию можно получить здесь: stackoverflow.com/a/34704078/6099347
Это не объясняет, почему умножение больших чисел дает отрицательный результат.
Это называется целочисленным переполнением. По сути, ваши числа становятся слишком большими для типа данных, в котором вы храните решение. Когда это происходит, числа превращаются в отрицательные числа из-за дополнения до двух. Двойной тип данных является большим, но если вы станете достаточно большим, он тоже станет отрицательным.
См. эти статьи на дополнение до двух и целочисленное переполнение. Они все подробно объяснят.
Редактировать: упрощенное объяснение из вики-статьи. Я все же рекомендую вам прочитать это. Предположим, у нас 3-битная архитектура (от 000 до 111). В дополнении до двух мы говорим, что старший бит определяет, является ли число положительным или отрицательным. Если оно отрицательное, то следующие цифры складываются положительно с отрицательным числом. Вот простой пример подсчета:
+---------+--------+
| Decimal | Binary |
+---------+--------+
| 0 | 000 |
| 1 | 001 |
| 2 | 010 |
| 3 | 011 |
| -4 | 100 | <----- note this isn't negative 0, or +4.
| -3 | 101 | <----- note this isn't -1, or +5
| -2 | 110 | <----- note this isn't +6
| -1 | 111 |<----- note this isn't -3, or +7
| 0 | 000 |
+---------+--------+
И картина повторяется...
Таким образом, для более крупной системы применяется то же правило (32-битная или 64-битная). Числа будут переноситься в 2^(n-1), где n представляет, сколько битов имеет число (например, для 3-битной системы, как показано выше, оно переносится в 2 ^ (3-1) = 2 ^ 2 = 4). Обратите внимание, что использование дополнения до двух означает, что диапазон положительного пространства уменьшается вдвое. Для 3-битного целого числа без знака обычно вы можете считать от 0 до 7, теперь вы можете считать только между -4 и 3 (все еще 8 чисел).
Теперь попробуйте умножить два положительных числа:
+2 * +3 = ?
Обычно это +6 или 110. Но в дополнении до двух это на самом деле -2.
Не могли бы вы объяснить, что «числа превращаются в отрицательные числа из-за дополнения до двух»?
Я отредактировал свое объяснение, включив в него пример с небольшой системой подсчета, надеюсь, это объяснение достаточно подробное.
превышать диапазон Double.MAX_VALUE, поэтому результат становится отрицательным
пример:
для четырехзначных двоичных данных.
00001 здесь оставил большую часть бита зарезервированной для бита знака, [0 означает +, 1 означает -], а остальные 4 бита используются для хранения значения. поэтому 00001 двоичный = десятичный +1
поэтому для 4 бит мы сможем записать максимум 15 десятичных значений.
Теперь, если я хочу написать 16, это переполняет диапазон значений.
поэтому, если я преобразую десятичные 16 в двоичные данные, это будет 10000, поэтому бит знака теперь равен 1. и окончательный результат будет отрицательным
Если вы хотите полностью избежать проблемы целочисленного переполнения, попробуйте использовать BigInteger:
https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html
Это позволяет использовать целые числа произвольной длины.
(На самом деле, глядя на исходный код BigInteger, кажется, что существует максимальный размер, то есть int[], в котором хранится BigInteger:
Integer.MAX_VALUE / Integer.SIZE + 1
Гораздо больше, чем кто-либо мог разумно желать!)
Еще один хороший ответ, и сюрприз, теперь вы можете оставлять комментарии. Кроме того, небольшая заметка: сосредоточьтесь на качестве, а не на количестве ;-)
превышает Double.MAX_VALUE , поэтому самый левый бит равен «1», поэтому значение отрицательное