Java: почему умножение большого положительного числа приводит к отрицательным результатам?

Я наблюдаю какое-то странное поведение при умножении целых чисел в 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;
}

Мой вопрос: почему это происходит с целыми числами и чем отличается двойной тип, который заставляет его работать правильно?

превышает Double.MAX_VALUE , поэтому самый левый бит равен «1», поэтому значение отрицательное

Mahamudul Hasan 24.03.2019 12:21
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
1
3 162
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Давайте рассмотрим это на примере 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 24.03.2019 12:20

@C.Peck Дополнительную информацию можно получить здесь: stackoverflow.com/a/34704078/6099347

Aniket Sahrawat 24.03.2019 12:20

Это не объясняет, почему умножение больших чисел дает отрицательный результат.

Wijay Sharma 29.01.2020 10:33

Это называется целочисленным переполнением. По сути, ваши числа становятся слишком большими для типа данных, в котором вы храните решение. Когда это происходит, числа превращаются в отрицательные числа из-за дополнения до двух. Двойной тип данных является большим, но если вы станете достаточно большим, он тоже станет отрицательным.

См. эти статьи на дополнение до двух и целочисленное переполнение. Они все подробно объяснят.

Редактировать: упрощенное объяснение из вики-статьи. Я все же рекомендую вам прочитать это. Предположим, у нас 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.

Не могли бы вы объяснить, что «числа превращаются в отрицательные числа из-за дополнения до двух»?

Wijay Sharma 29.01.2020 10:39

Я отредактировал свое объяснение, включив в него пример с небольшой системой подсчета, надеюсь, это объяснение достаточно подробное.

jsonV 13.05.2020 17:43

превышать диапазон 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

Гораздо больше, чем кто-либо мог разумно желать!)

Еще один хороший ответ, и сюрприз, теперь вы можете оставлять комментарии. Кроме того, небольшая заметка: сосредоточьтесь на качестве, а не на количестве ;-)

GhostCat 24.03.2019 19:38

Другие вопросы по теме